How do you model application states?

2020-05-21 05:50发布

问题:

I'm writing a game, and I want to model its different states (the Game Maker analogy would be frames, I guess) in a clean, object-oriented way. Previously, I've done it in the following way:

class Game
{
  enum AppStates
  {
    APP_STARTING,
    APP_TITLE,
    APP_NEWGAME,
    APP_NEWLEVEL,
    APP_PLAYING,
    APP_PAUSED,
    APP_ENDED
  };

  typedef AppState(Game::*StateFn)();
  typedef std::vector<StateFn> StateFnArray;

  void Run()
  {
    // StateFn's to be registered here

    AppState lastState(APP_STARTING);
    while(lastState != APP_ENDED)
    {
      lastState = GetCycle_(lastState);
    }
    // cleanup
  }

protected:
  // define StateFn's here

  AppState GetCycle_(AppState a)
  {
    // pick StateFn based on passed variable, call it and return its result.
  }

  StateFnArray states_;
};

This was hardly manageble for a smaller project. All the variables that the states were using were dumped in the Game class, however I'd want to keep object-orientedness to a maximum, only exposing variables that are shared by more than one state. I also want to be able to initialize a new state when switching to it rather than having to do it in the state that's just finishing (as it might have multiple outcomes - APP_PLAYING can switch to APP_PAUSED, APP_GAMEOVER, APP_NEWLEVEL, etc.).

I thought of something like this (CAUTION! FUZZY STUFF!):

struct AppState
{
  enum { LAST_STATE = -1; }
  typedef int StateID;
  typedef std::vector<AppState*> StateArray;

  static bool Add(AppState *state, StateID desiredID);
  // return false if desiredID is an id already assigned to

  static void Execute(StateID state)
  {
    while(id != LAST_STATE)
    {
      // bounds check etc.
      states_[id]->Execute();
    }
  }

  AppState() {};
  virtual ~AppState() {};

  virtual StateID Execute() =0; // return the ID for the next state to be executed

protected:
  static StageArray stages_;
};

The problem here is that the class and instance levels are getting jumbled up (static vs virtual). The states need to inherit from AppState, but - how I'd imagine - most of them would be classes with all-static members, or, at least I won't need more than one instance from one class (TitleState, LevelIntroState, PlayingState, GameOverState, EndSequenceState, EditorState... - pausing would no longer be a state, rather than being taken care of in the states where it makes sense).

How can it be done elegantly and efficiently?

回答1:

The following article gives a nice, simple way to manage game states:

http://gamedevgeek.com/tutorials/managing-game-states-in-c/

Basically, you maintain a stack of game states, and just run the top state. You're right that many states would only have one instance, but this isn't really a problem. Actually, though, many of the states you're talking about could have multiple instances. E.g.:

push TitleState
push MenuState
push LevelIntroState
change_to PlayingState
change_to GameOverState
pop (back to MenuState)

... and you can start over with a new instance of LevelIntroState, and so on.



回答2:

I'm using some type of factory pattern combined with a state pattern in my soon-to-be game.

The code might be a bit messy but I'll try to clean it up.

This is the class you'll derive all states from, like the menu, the game or whatever.

class GameState {
public:
    virtual ~GameState() { }

    virtual void Logic() = 0;
    virtual void Render() = 0;
};

This class will be your interface for handling the different states. You can dynamically add and id dynamically.

class State {
public:
    State();
    virtual ~State();

    void Init();
    void Shutdown();
    void SetNext( std::string next_state );
    void Exit();

    bool Logic();
    void Render();
protected:
    bool Change();

    std::string state_id;
    std::string next_state;

    GameState *current_state;
    std::vector<std::string> state_ids;

    StateFactory *state_factory;

    bool is_init;
};

I'm using a functor to handle the creation of different GameState derivatives.

class BasicStateFunctor {
public:
    virtual GameState *operator ()() = 0;
};

template<class T>
class StateFunctor : public BasicStateFunctor {
public:
    StateFunctor() { }
    GameState *operator ()() {
        return new T;
    }
    typedef T type;
};

Lastly a factory which will store and manage the different states.

class StateFactory {
public:
    StateFactory();
    virtual ~StateFactory();

    bool CheckState( std::string id );
    GameState *GetState( std::string id );
    template<class T> void AddState( std::string id );
private:
    typedef std::map<std::string, BasicStateFunctor*>::iterator StateIt;
    std::map<std::string, BasicStateFunctor*> state_map;
};

In your definition file: Here I did leave out a lot of stuff, but hopefully you'll get the idea.

bool StateFactory::CheckState( std::string id )
{
    StateIt it = state_map.find( id );
    if( it != state_map.end() )
        return true;
    else
        return false;
}

GameState *StateFactory::GetState( std::string id )
{
    StateIt it = state_map.find( id );
    if( it != state_map.end() )
    {
        return (*(*it).second)();
    } else {
        //handle error here
}

template<class T> void StateFactory::AddState( std::string id )
{
    StateFunctor<T> *f = new StateFunctor<T>();
    state_map.insert( state_map.end(), std::make_pair( id, f ) );
}

void State::Init()
{
    state_factory = new StateFactory();
    state_factory->AddState<Game>( "game" );
    current_state = state_factory->GetState( "game" );
    is_init = true;
}

void State::SetNext( std::string new_state )
{
    //if the user doesn't want to exit
    if( next_state != "exit" ) {
        next_state = new_state;
    }
}

bool State::Change()
{
    //if the state needs to be changed
    if( next_state != "" && next_state != "exit" ) 
    {

        //if we're not about to exit( destructor will call delete on current_state ),
        //call destructor if it's a valid new state
        if( next_state != "exit" && state_factory->CheckState( next_state ) ) 
        {
            delete current_state;

            current_state = state_factory->GetState( next_state );

        } 
        else if( next_state == "exit" ) 
        {
                return true;
        }

        state_id = next_state;

        //set NULL so state doesn't have to be changed
        next_state = "";
    }
    return false;
}

bool State::Logic()
{
    current_state->Logic();
    return Change();
}

And here's how you use it: Initialize and add the different states, I'm doing it in the Init().

State.Init();

//remember, here's the Init() code:
state_factory = new StateFactory();
state_factory->AddState<Game>( "game" );
current_state = state_factory->GetState( "game" );
is_init = true;

For the frame function

State.Logic(); //Here I'm returning true when I want to quit

And for the rendering function

State.Render();

This may not be perfect but it works fine for me. To further advance the design you'd want to add Singleton for State and maybe make the StateFactory as a hidden class inside State.



回答3:

Here is my solution:

  • Every state is like a small game, so I manage a set of games on a stack.
  • Events bubble the stack up until someone stops them (so "games" further up don't see them anymore). This allows me to zoom the map via plus/minus while in a menu. OTOH, Esc stops the bubbling early since the first open menu swallows it.
  • Each "game" on the stack has the same methods: handleUserEvent(), keyDown(), keyUp(), mousePressed(), mouseReleased(), mouseMotion(), update() (internal calculations before rendering), draw() (rendering), prepare() (optimize rendering by caching something in a texture that's just stamped on the target surface in draw())

For rendering, I'm using layers with priorities. So each game will render on a transparent canvas and the layer renderer will render them in the correct order. This way, each game can update its own layer without bothering what everyone else is doing.



回答4:

I use a Game State manager with a list of GameStates, where each Item in the list is a "GameState Object" that implements IGameState and has two methods .render() and .HandleInput()

This GameStateManager is implemented as a singleton so any state can jump to any another state by calling

 GameStateManager.gi().setState("main menu")

And the main loop looks something like this

while(isRunning)
{
   GameStateManager.gi().getCurrentState().handleKeyboard(keysobject);
   GameStateManager.gi().getCurrentState().handleMouse(mouseobject);

   GameStateManager.gi().getCurrentState().render(screenobject);

}

That way to create states, just create a new class that implements IGameState, and add it to the GameStateManager.

(Note: This is a really handy way to make mini-games within your main game)



标签: c++ states