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?
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.
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.
Here is my solution:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With