Managing Game States in C++
The first time I became fully aware of different states in games was while watching a demo years ago. Not a demo as in "a preview of an upcoming game", but a demo as in "old-school, from the scene". Anyway, these demos have a way of moving seemlessly from one effect to another. They would go from some swirling 2D effect, straight into a 3D rendered environment. I remember thinking it was like they took several different programs and tied them together.
Multiple states are not only important in demos, but also in games in general. Every game starts off in an introduction state, then moves to a menu of some kind, a finally play begins. When you're finally defeated, the game moves to a game-over state, usually followed by a return to the menu. In most games it is possible to be in more than one state at a time. For example, you can usually bring up the menu during game play.
The traditional way of handling multiple states is with a series of if statements, switches, and loops. The program begins in the intro state and loops until a key is pressed. Then the menu is displayed until a selection is made. Then the game begins, and loops until the game is over. Everytime through the game loop, the program must check to see if it should display the menu or simply draw the next frame. Also, the part of the program that handles events must check to see if your input should affect the menu or the game. All of this combines to make a main loop that is hard to follow, and therefore hard to debug and maintain.
What's a state?
As I hinted at earlier, a state is almost like a seperate program within the game. Each state will handle events differently, and draw something different on the screen. Each state will handle its own events, update the game world, and draw the next frame on the screen. So, we have now identified three methods that our state class should contain.
A game state also needs to be able to load graphics and initialize itself, as well as clean up resources when it's done. Also, there are times when we will want to pause a state, and resume it at a later time. An example of this would be pausing the game state to bring up a menu on top of it. So far our game state class is shaping up like this:
class CGameState { public: void Init(); void Cleanup(); void Pause(); void Resume(); void HandleEvents(); void Update(); void Draw(); };
A layout like this should easily meet our needs for a game state. This will make a nice base class from which we can inherit classes for each state needed in the game - intro state, menu state, play state, etc.
The State Manager
Next we need to develop a way to control these states - a state manager. In my code, the state manager is part of the game engine itself. Others may choose to create a seperate state manager class, but for me it was easier to just add it directly to the engine. Again, we can look at what a game engine needs to do and then develop a game engine class to handle these functions.
For our simple example, all the engine really needs to do is initialize SDL, and clean up when it's done. Since we will use the engine in our main loop, we also need to check if the engine is still running, ask it to quit, and handle the usual process events, update the world, draw the frame sequence.
The state manager part of the engine is actually quite simple. In order to allow states to exist on top of one another, we will need a "stack of states". I will use an STL vector to implement this stack. In addition we will need methods to change states, as well as push and pop states on the stack.
So far, the game engine class is shaping up something like this:
class CGameEngine { public: void Init(); void Cleanup(); void ChangeState(CGameState* state); void PushState(CGameState* state); void PopState(); void HandleEvents(); void Update(); void Draw(); bool Running() { return m_running; } void Quit() { m_running = false; } private: // the stack of states vector<CGameState*> states; bool m_running; };
Several of these functions will be very easy to write. HandleEvents(), Update(), and Draw() will all simply call the corresponding function from the state at the top of the stack. Since these will often need access to game engine data, I will go back to the game state class and add a pointer to the game engine as a parameter to each of these member functions.
The final consideration is changing between states. How does the engine know when to change from one state to another? The answer is - it doesn't. Only the current state can know when it's time to change to the next state. So we will again return to the game state class and add a function for changing states.
While we're at it, we'll make this an abstract base class, and make most of the members pure virtual functions. This guarantees that inherited classes will implement them. With all of these changes, the final game state class looks like this:
class CGameState { public: virtual void Init() = 0; virtual void Cleanup() = 0; virtual void Pause() = 0; virtual void Resume() = 0; virtual void HandleEvents(CGameEngine* game) = 0; virtual void Update(CGameEngine* game) = 0; virtual void Draw(CGameEngine* game) = 0; void ChangeState(CGameEngine* game, CGameState* state) { game->ChangeState(state); } protected: CGameState() { } };
Now adding states to your game will be as easy as inheriting from this base class and defining the seven pure virtual functions. Since we'll never need more than one instance of any particular state, it's a good idea to implement them as Singletons. If you're not familiar with the Singleton pattern, it's just a way of making sure that there's only one copy of an object. This is done by making the constructor protected, and then providing a function that returns a pointer to a static instance of the class.
To give you an idea of just how much this method can simplify your game, this listing contains the entire main.cpp file:
#include "gameengine.h" #include "introstate.h" int main ( int argc, char *argv[] ) { CGameEngine game; // initialize the engine game.Init( "Engine Test v1.0" ); // load the intro game.ChangeState( CIntroState::Instance() ); // main loop while ( game.Running() ) { game.HandleEvents(); game.Update(); game.Draw(); } // cleanup the engine game.Cleanup(); return 0; }
Downloads
This example contains three different states - an introduction state that fades in from black, a playing state, and an in game menu that pauses the play state and resumes it when done. Each state is represented by a simple background image.
- stateman.zip - Tutorial Source, Graphics, and Project files for Visual C++ 6.
- stateman.tar.gz - Tutorial Source, Graphics, and Makefile for Linux.
This sample code uses SDL. If you are not familiar with SDL, please see my Getting Started with SDL tutorial. If your computer is not set up with SDL, you will not be able to compile and run this example.
Resources
If you're just getting started with C++, you should definitely check out Beginning C++ Game Programming. It's a great introduction to the C++ programming language using simple games as examples. For intermediate programmers, I recommend C++ For Game Programmers. This book will help you move beyond the basics of C++. Finally, for an in depth understanding of patterns, you'll want to pick up Design Patterns by the "Gang of Four".
Ports and Translations
The source code for this article has been ported to Object Pascal. You can download the port here:
- statemanpas.tar.gz - Object Pascal Source.
Thanks to Marko Peric for porting the code and sharing it with everyone.
This tutorial has also been translated to French, Chinese, and Korean.
The French version of the tutorial is available here:
Thanks to Ceacy for the French translation.
The Chinese version of the tutorial is available here:
Thanks to Mythma for the Chinese translation.
The Korean version of the tutorial is available here:
Thanks to Lee Ung Ju for the Korean translation.
Note
This tutorial was inspired by the State Pattern in C++ tutorial at The Code Project. Unfortunately, that tutorial implemented the game states within an MFC program that I found difficult to follow. Also, the author of that tutorial did not implement a stack of states.