Game State Management

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:

UML diagram of a simple implementation of the state pattern for games

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:

UML diagram of a minimal game state manager implementation

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.

UML diagram of a stacked game state manager implementation

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:

UML diagram of a game state manager gaining game-specific capabilities through polymorphism

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.

Download

Nuclex.GameStates.Demo.7z (11 KiB)

(Contains the complete source code of the game state manager of the final UML diagram and an example console application that uses it to simulate a game’s main loop)