When you design a game, even if it only has the scope of Pac-Man, your game invariably switches between different, unrelated modes. At one point, it’s drawing the main menu, at another the player is steering his avatar through the game world, eventually it is rolling the credits.
These different modes can be implemented directly into the main loop like so:
void runGame() {
while(!this->quitRequested) {
if(this->currentState == STATE_MAINMENU) {
// Update & draw main menu
} else if(this->currentState == STATE_GAMEPLAY) {
// Update & draw game world
} else if(this->currentState == STATE_CREDITS) {
// Update & draw credit scroller
}
runMessagePump();
}
}
But that quickly becomes unwieldy because first, about ten dozen additional lines will
quickly sneak themselves into those if
blocks and just when you reign them in
by moving each case into a separate method, overlapping resource management and references
to just about every other header in the entire project ruin the fun.
The tried-and-true solution to this problem is the State Pattern. It works like so:
Or if you prefer code, here is the same thing in C++:
class Game {
private: void runGame() {
this->currentState = new MenuGameState();
while(!this->QuitRequested) {
this->currentState->Update();
this->currentState->Draw();
}
}
private: GameState *currentState;
};
class GameState {
public: virtual ~GameState() {}
public: virtual void Update() {}
public: virtual void Draw() {}
};
class MenuGameState : public GameState {
public: ~MenuGameState() {}
public: void Draw() {
// Draw menu to screen
}
};
Designing a Game State Manager
Managing the active game state in the main loop or Game
class
is still no good because then any GameState
that wants to
switch to another game state would need a pointer to the Game
instance or some global method.
This kind of upstream dependency is never desirable (the purpose of
the Game
class is to run the game, not to serve as a facility for
switching between game states), so the right thing to do is to move game state
management into its own class.
Up to this point, it’s all common knowledge. What motivated me to write this article and to roll my own game state system was that I’ve seen countless implementations try to wed the concept of game state management with completely unrelated things:
Undesirable Attributes
- Providing an enum/string to game state mapping – This can be done externally without locking the game state manager to one specific use case.
- Creating new game states – Now either the game state manager has to know about all the subsystems any game states may ever want to consume, or all of the subsystems need to be singletons.
-
Usage-specific methods in the state interface – Methods like
Resume()
/Pause()
,Draw()
/Update()
or *shudder*Init()
/Shutdown()
(the dreaded two-stage construction anti-pattern forced down the throat of any game state)
So let’s start with a design that excludes all this clutter:
That’s wondefully simple. The game state manager simply stores the active game state and lets us switch to another game state. This can be either a game state known to the other game state (eg. the game play state might know the inventory screen state) or a game state provider could be used that serves as an interface to a state repository or factory.
Stacked Game States
Let’s extend this concept into something more useful: a stacked game state manager that lets you stack game states on top of each other. This is a popular concept which lets game states return to the previous game state without requiring knowledge about which game state they’re returning to. Examples are things like aforementioned inventory screens, maps, option menus or cutscenes.
As you can see, the GameState
now has four methods:
-
Entered()
– called after the game state has been placed in the game state manager -
Exiting()
– as indicated by using present tense, called right before the game state is removed from the game state manager -
Obscuring()
– also present tense, this method is called right before another game state is stacked on top of this one -
Revealed()
– called after the game state has become the topmost game state on the stack again
Also notice that this stacked game state manager is a direct extension of
the minimal game state manager presented earlier. In fact, you could derive a
StackedGameState
from the earlier GameState
class
and a StackedGameStateManager
from the earlier
GameStateManager
class without any problems.
Usage-Specific Methods
That lack of methods like Draw()
and Update()
in
this design are intentional. If you added such methods to the game state manager,
it would expose them to places where they aren’t supposed to be used: a state
could call Draw()
on the game state manager, causing either
redundant drawing to occur or producing a stack overflow when it is part of
the stack.
They are also highly implementation-specific: one game might have
an Update
method that is passed the elapsed time as a float,
another may just perform a fixed time step. One game might provide some graphics
drawing interface in the Draw()
method, while another passes
a camera from which the view is being rendered while yet another doesn’t have
a Draw()
method at all but expects the game state to update the contents
of a scene graph instead.
It is possible to keep a clean interface that only lets game states do what they are supposed to do while still providing the ability to update and render game states by deriving the usage-specific part of the game state manager from the general purpose interface. The only portion of the game that has access to these methods (by knowing the game state manager as its implementation class) can be the game class:
As you can see, the Update()
and Draw()
methods are now contained
in separate interfaces than can be implemented – optionally – by game states.
This requires a dynamic_cast
in the game state manager to find out if
a game state actually is drawable or updateable.
But isn’t dynamic_cast
slow? No, that’s just the yelling of micro-optimizing
novice programmers. A dynamic_cast
takes about as much time as 5 to 10 method
calls, which is hardly relevant for something that happens once per frame. Still, by
trading against a few more bytes of memory, we can make this dynamic_cast
faster even than adding the Update()
/Draw()
methods into
the GameState
class would be:
void DirectRenderingGameStateManager::Push(GameState *gameState) {
this->currentStates.push_back(gameState);
Updateable *updateable = dynamic_cast<Updateable *>(gameState);
if(updateable) {
this->activeUpdateables.push_back(updateable);
}
Drawable *drawable = dynamic_cast<Drawable *>(gameState);
if(drawable) {
this->activeDrawables.push_back(drawable);
}
}
Instead of doing the dynamic_cast
inside the
DirectRenderingGameStateManager::Draw()
and
DirectRenderingGameStateManager::Update()
calls, we can do it when
the game state is added to the game state manager. During drawing or updating,
all the game state manager has to do is go over the list of drawables or
updateables once. No casts:
void DirectRenderingGameStateManager::Update(float elapsedTime) {
for(std::size_t index = 0; index < this->activeUpdateables.size(); ++index) {
this->activeUpdateables[index]->Update(elapsedTime);
}
}
void DirectRenderingGameStateManager::Draw(float elapsedFrameTime) {
for(std::size_t index = 0; index < this->activeDrawables.size(); ++index) {
this->activeDrawables[index]->Draw(elapsedFrameTime);
}
}
This is 100.0% as fast as it would be if the Draw()
and Update()
methods were part of the GameState
class and faster if you have states
that do not implement the Drawable
or Updateable
interfaces
since it won’t have to call the empty Update()
or Draw()
method.