Lazy Foo' Productions


State Machines

Last Updated 12/27/11
It's important to know how to move between the various modes of a game. State machines allow you to do just that.
Let me start off by saying this is a huge article. The source code for the demo program is over 1000 lines of code. We use motion, collision detection and scrolling so make sure you know do how to these things. You'll also need a good understanding of game loops. State machines are all about changing how your game loop functions.

You're also going to have to know inheritance and polymorphism. If you're not familiar these concepts in C++ I recommend you learn them. Besides needing them for this article, encapsulation, inheritance, and polymorphism are the key concepts of object oriented programming. If you ever want to use C++ effectively, you're going to have to learn them eventually. We also use operator overloading in the example program, but it's not vital to your understanding of state machines so don't worry about it too much. Now on with the lesson.

Say you have various modes in your game like a title screen, menus, and the levels of your game. For each of those modes the events, logic, and rendering work differently. To change the way your game loop works, we use a state machine to transition between the different states.

The particular type of state machine used here is called a finite state machine. There's two common ways to implement this type of state machine.
//Do logic switch( gameState ) { case STATE_INTRO: intro_logic(); break; case STATE_TITLE: title_logic(); break; case STATE_OVERWORLD: overworld_logic(); break; }
Here's what a state machine's logic module would look like if it was used implementing the switch/case method. As you can see, the behavior varies depending on the current game state.

Then there's the object oriented method.
//Create game state object GameState currentState = new myState(); //Run main loop while( quit == false ) { //Do events currentState->events(); //Do logic currentState->logic(); //Change state if needed change_state(); //Render currentState->render(); }
Here's what the object oriented method would look like.

Now with the object oriented method, we create a base game state class with virtual functions for each part of the game loop. Then we have game state classes that inherit from the base class and override the functions to work as the game state needs them to. To switch the game state, we simply change the game state object as needed.

In our demonstration, we use the object oriented method as opposed to the switch/case method. Why, you ask?
while ( quit == false ) { //Do events switch( gameState ) { case STATE_INTRO: intro_events(); break; case STATE_TITLE: title_events(); break; case STATE_MENU: menu_events(); break; case STATE_STAGE01: stage01_events(); break; case STATE_STAGE02: stage02_events(); break; case STATE_STAGE03: stage03_events(); break; case STATE_STAGE04: stage04_events(); break; case STATE_STAGE05: stage05_events(); break; case STATE_BONUS_STAGE: bonus_stage_events(); break; } //Do logic switch( gameState ) { case STATE_INTRO: intro_logic(); break; case STATE_TITLE: title_logic(); break; case STATE_MENU: menu_logic(); break; case STATE_STAGE01: stage01_logic(); break; case STATE_STAGE02: stage02_logic(); break; case STATE_STAGE03: stage03_logic(); break; case STATE_STAGE04: stage04_logic(); break; case STATE_STAGE05: stage05_logic(); break; case STATE_BONUS_STAGE: bonus_stage_logic(); break; } //Change the state if needed change_state(); //Do Rendering switch( gameState ) { case STATE_INTRO: intro_render(); break; case STATE_TITLE: title_render(); break; case STATE_MENU: menu_render(); break; case STATE_STAGE01: stage01_render(); break; case STATE_STAGE02: stage02_render(); break; case STATE_STAGE03: stage03_render(); break; case STATE_STAGE04: stage04_render(); break; case STATE_STAGE05: stage05_render(); break; case STATE_BONUS_STAGE: bonus_stage_render(); break; } }
The problem with the switch/case method is that it can get cluttered pretty quickly. There's only 9 states represented here and just look how many lines of code there are. It's also a pain having to keep track of all those case statements.

With the object oriented method in our demo, the state manager only needs 3 lines more of code per state compared to the 9 the switch/case method would need. So it's best to keep the switch/case method to small scale stuff like changing a sprite's animation.

So let's get started with our state machine demo.
//Game states enum GameStates { STATE_NULL, STATE_INTRO, STATE_TITLE, STATE_GREEN_OVERWORLD, STATE_RED_ROOM, STATE_BLUE_ROOM, STATE_EXIT, };
Here are the constants we're going to use to represent the various states in our application.

First we have the intro screen. It's the state we start in.


Then when the user presses a key we move to the title screen.


When the user presses a key again we move to the overworld.


When the user moves the dot over the red house in the upper left corner, we move into the red room.

When they go through the door, they go back to the overworld.

When the user moves the dot over the blue house in the bottom right corner of the overworld, we move into the blue room.

We they go through the door, they go back to the overworld just like with the red room.

The exit state is used when the user wants to quit the program. Setting the state to exit is like setting quit to true in our other applications.

The NULL state is used when we want to say that there is no state we want to change to. You'll see it in action in the change_state() function later on.
/*Classes*/ //Game state base class class GameState { public: virtual void handle_events() = 0; virtual void logic() = 0; virtual void render() = 0; virtual ~GameState(){}; };
Here's the base class for all our game state classes. You can see we have virtual functions for each part of a game loop and a virtual destructor.
//State status manager void set_next_state( int newState ); //State changer void change_state();
Here are our functions for managing our game state. We use set_next_state() to set the state we want to change to and change_state() changes the actual game state object. We'll go into more detail on how these work later.
//State variables int stateID = STATE_NULL; int nextState = STATE_NULL; //Game state object GameState *currentState = NULL;
These are the variables we use to manage our state machine. "stateID" tells us what the current state is, and "nextState" obviously tells us what the next state is going to be when we want to change it.

Last but certainly not least is "currentState" which points to the current state object.
bool load_files() { //Load the dot image dot = load_image( "dot.bmp" ); //If there was a problem in loading the dot if( dot == NULL ) { return false; } //Open the font font = TTF_OpenFont( "lazy.ttf", 36 ); //If there was an error in loading the font if( font == NULL ) { return false; } //If everything loaded fine return true; }
Here's our main file loading function which loads the dot graphic and the font. We'll want these resources available in whatever state we're in.
class Intro : public GameState { private: //Intro background SDL_Surface *background; //Intro message SDL_Surface *message; public: //Loads intro resources Intro(); //Frees intro resources ~Intro(); //Main loop functions void handle_events(); void logic(); void render(); }; class Title : public GameState { private: //Title screen background SDL_Surface *background; //Title screen message SDL_Surface *message; public: //Loads title screen resources Title(); //Frees title resources ~Title(); //Main loop functions void handle_events(); void logic(); void render(); };
Here are the class declarations for the intro and title screen states which inherit from the GameState class. They both just show a message on a background.

They have a background and message surfaces, constructors and destructors, and their functions for the game loop.
Intro::Intro() { //Load the background background = load_image( "introbg.png" ); //Render the intro message message = TTF_RenderText_Solid( font, "Lazy Foo' Productions Presents...", textColor ); } Intro::~Intro() { //Free the surfaces SDL_FreeSurface( background ); SDL_FreeSurface( message ); }
In the intro state constructor we load the background image and render the message which we're going to need while in the intro game state. Naturally, in the destructor we free these resources.
void Intro::handle_events() { //While there's events to handle while( SDL_PollEvent( &event ) ) { //If the user has Xed out the window if( event.type == SDL_QUIT ) { //Quit the program set_next_state( STATE_EXIT ); } //If the user pressed enter else if( ( event.type == SDL_KEYDOWN ) && ( event.key.keysym.sym == SDLK_RETURN ) ) { //Move to the title screen set_next_state( STATE_TITLE ); } } }
Here's the event handling function for the intro game state.

When the user tries to quit we set the next state to exit using set_next_state(). When the user presses enter, we want to move to the title screen so we set the next state to the title screen.

You're probably wondering how set_next_state() works. Well then let's take a look.
void set_next_state( int newState ) { //If the user doesn't want to exit if( nextState != STATE_EXIT ) { //Set the next state nextState = newState; } }
All set_next_state() does is set the "nextState" variable.

Remember that a good game loop is modular so we can't be changing game state objects all over the place. All the actual game state object changing happens in the change_state() function (which we'll get to in a little bit). So we use set_next_state() to request what state we want to change to.

You might be asking yourself why we check if the user wants to exit. It can happen where there's two or more requests to change states before getting to the change_state() function.

Say if the user tries to x out in the events module and "nextState" gets set to STATE_EXIT and then dot moves over the red house in the logic module and "nextState" gets set to STATE_RED_HOUSE. This means the request to exit the application will be ignored.

But since the function checks if the user wanted to exit before changing the next state, the user quit will always go through.
void Intro::logic() { } void Intro::render() { //Show the background apply_surface( 0, 0, background, screen ); //Show the message apply_surface( ( SCREEN_WIDTH - message->w ) / 2, ( SCREEN_HEIGHT - message->h ) / 2, message, screen ); }
In the intro's logic module, you can see that nothing is going there. The rendering module simply shows the background with the message on top.

Now you've seen how a game state class works and how we request state changes, but where and how do we change state objects? Let's take a look at the main loop.
//While the user hasn't quit while( stateID != STATE_EXIT ) { //Start the frame timer fps.start(); //Do state event handling currentState->handle_events(); //Do state logic currentState->logic(); //Change state if needed change_state(); //Do state rendering currentState->render(); //Update the screen if( SDL_Flip( screen ) == -1 ) { return 1; } //Cap the frame rate if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND ) { SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() ); } }
In the main loop, you see the standard event, logic and rendering modules. Changing of the game state object happens at the end of the logic module, after we call the game state object's logic() function.

Also notice that change_state() is called before the rendering module. Remember that because it's important when we look next at how change_state() works.
void change_state() { //If the state needs to be changed if( nextState != STATE_NULL ) { //Delete the current state if( nextState != STATE_EXIT ) { delete currentState; }
Here's the top of our state changing function.

First thing we do is check if "nextState" is not STATE_NULL. If it is NULL, it means that there's no state to change to and change_state() doesn't do anything.

If we need to change the state, we check if the next state is STATE_EXIT. If it's not, we delete the game object so the destructor is called and frees all the resources for that game state. Now we can have a new game state object which we'll get in the next bit of code.

But why don't we want to delete the object if the user wants to exit? It's because there's no game state class for exit and STATE_EXIT is just there to break the game loop. If we delete the current game state object, after change_state() is done, the game loop will try to call render() from the game state object that was just deleted which isn't good. So we just skip the deletion in change_state(), render using the current game state object one last time and end the game loop so we can exit the program.
//Change the state switch( nextState ) { case STATE_TITLE: currentState = new Title(); break; case STATE_GREEN_OVERWORLD: currentState = new OverWorld( stateID ); break; case STATE_RED_ROOM: currentState = new RedRoom(); break; case STATE_BLUE_ROOM: currentState = new BlueRoom(); break; } //Change the current state ID stateID = nextState; //NULL the next state ID nextState = STATE_NULL; } }
After we delete our old game state object it's time to get a new one. This switch/case block gives us our new object depending on what "nextState" is. When we create the new game state object, the constructor is called and the constructor of our game state loads the resources it needs.

Notice how the constructor for the OverWorld game state takes in "stateID". You can see "stateID" doesn't change to the new state until after the switch/case block. So this means the OverWorld object is getting the ID of the last active state. You'll see why the OverWorld object needs to know the previous state when we get to its class definition.

Lastly in the change_state() function we set "nextState" to STATE_NULL because we're done changing the state.

Now let's see how the rest of the state classes work.
void Title::handle_events() { //While there's events to handle while( SDL_PollEvent( &event ) ) { //If the user has Xed out the window if( event.type == SDL_QUIT ) { //Quit the program set_next_state( STATE_EXIT ); } //If the user pressed enter else if( ( event.type == SDL_KEYDOWN ) && ( event.key.keysym.sym == SDLK_RETURN ) ) { //Go to the overworld set_next_state( STATE_GREEN_OVERWORLD ); } } }
If you've seen the intro class, you already know how the title screen works. They both just show messages on a background. They just show different messages on different backgrounds.

The biggest difference is that in the title screen when the user presses enter, we set the next state to be the overworld.
//The dot class Dot { private: //The collision box of the dot SDL_Rect box; //The velocity of the dot int xVel, yVel; //The current level dimensions int curLvlWidth; int curLvlHeight; public: //Initializes the variables Dot(); //Sets the dot void init( int x, int y, int lvlWidth, int lvlHeight ); //Handles keypress void handle_input(); //Moves the dot void move(); //Shows the dot void show(); //Sets the camera over the dot void set_camera(); //Gets the dot's collision box operator SDL_Rect(); };
Here's our friend the Dot class we modified for our state machine demo.

The dot sets the camera but the overworld, red room, and blue room have different sizes. This means the dot needs to know the dimensions of the level it's in and that's why it has the "curLvlWidth" and "curLvlHeight" variables. It also needs to know the size of the level to keep inside of it.

We have the init() so when we change the game state, we can set the dot's position and get the current level's dimensions.
class OverWorld : public GameState { private: //Level dimensions const static int LEVEL_WIDTH = 1280; const static int LEVEL_HEIGHT = 960; //Overworld background SDL_Surface *background; //The houses House redHouse; House blueHouse; public: //Loads resources and intializes objects OverWorld( int prevState ); //Frees resources ~OverWorld(); //Main loop functions void handle_events(); void logic(); void render(); };
Here's the breakdown of the overworld game state.

First we have the constants defining the level dimensions. Then we have the graphic for the background.

Then we have the two house objects that the dot is going to interact with. The dot itself is a global object. Using globals is something to avoid unless the way around it is uglier and believe me in this case it's ugly.

Then we have the constructor, destructor, and game loop functions.
OverWorld::OverWorld( int prevState ) { //Load the background background = load_image( "greenoverworld.png" ); //Set the houses redHouse.init( 0, 0, load_image( "red.bmp" ) ); blueHouse.init( 1240, 920, load_image( "blue.bmp" ) );
First thing we do in the overworld constructor is load the background, then place the houses and load their images.
//If the last state was the red room if( prevState == STATE_RED_ROOM ) { //Show up in front of the red house myDot.init( 10, 40, LEVEL_WIDTH, LEVEL_HEIGHT ); } //If the last state was the blue room else if( prevState == STATE_BLUE_ROOM ) { //Show up in front of the blue house myDot.init( 1250, 900, LEVEL_WIDTH, LEVEL_HEIGHT ); } //If the last state was something else else { //Show up in the center of the overworld myDot.init( 630, 470, LEVEL_WIDTH, LEVEL_HEIGHT ); } }
After we're done loading, it's time to set the dot depending on what the previous state was.

If the dot just came out of the red room, we place it in front of the door of the red house. If it came out of the blue house, we place it in front of the door of the blue house. If it came from any other state (like the title screen) we put the dot in the center of the overworld.
OverWorld::~OverWorld() { //Free the resources SDL_FreeSurface( background ); }
Like with the intro and title screens, we free our resources in the destructor.

You may be wondering where we free the graphics we loaded for the houses in the constructor. When the OverWorld object is deleted, the house objects go out of scope and their destructors are called. That's where we free the house surface and you can look in the source code if you want to.
void OverWorld::handle_events() { //While there's events to handle while( SDL_PollEvent( &event ) ) { //Handle events for the dot myDot.handle_input(); //If the user has Xed out the window if( event.type == SDL_QUIT ) { //Quit the program set_next_state( STATE_EXIT ); } } }
For the overworld event handling, we just do Dot input and we handle the user quit.
void OverWorld::logic() { //If the dot touches the red house if( check_collision( myDot, redHouse ) == true ) { //Move to the red room set_next_state( STATE_RED_ROOM ); } //If the dot touches the blue house else if( check_collision( myDot, blueHouse ) == true ) { //Move to the blue room set_next_state( STATE_BLUE_ROOM ); } //Move the dot myDot.move(); }
For the overworld logic we check if the dot is touching one of the houses. If it touches the red house we set the next state to the red room and if it touches the blue house we set the next state to the blue room.

After we're done checking collision we move the dot as usual.
void OverWorld::render() { //Set the camera myDot.set_camera(); //Show the background apply_surface( 0, 0, background, screen, &camera ); //Show the rooms redHouse.show(); blueHouse.show(); //Show the dot on the screen myDot.show(); }
Then in the rendering module we set the camera, show the background, show the houses. and show the dot on the screen.
class RedRoom : public GameState { private: //Level dimensions const static int LEVEL_WIDTH = 640; const static int LEVEL_HEIGHT = 480; //Red room background SDL_Surface *background; //The exit door Door exit; public: //Loads resources and initializes objects RedRoom(); //Frees resources ~RedRoom(); //Main loop functions void handle_events(); void logic(); void render(); }; class BlueRoom : public GameState { private: //Level dimensions const static int LEVEL_WIDTH = 640; const static int LEVEL_HEIGHT = 480; //Blue room background SDL_Surface *background; //The exit door Door exit; public: //Loads resources and intializes objects BlueRoom(); //Frees resources ~BlueRoom(); //Main loop functions void handle_events(); void logic(); void render(); };
Here the are red and blue room game states. They pretty much work the same except that they have different backgrounds and the door is in different place.
RedRoom::RedRoom() { //Load the background background = load_image( "redroom.png" ); //Set the exit exit.init( 310, 440 ); //Set the dot myDot.init( 310, 420, LEVEL_WIDTH, LEVEL_HEIGHT ); }
In the red room's constructor we load the background, place the exit door and place the dot in front of the door it came in through.
void RedRoom::logic() { //If the dot went to the exit if( check_collision( myDot, exit ) == true ) { //Go to the overworld set_next_state( STATE_GREEN_OVERWORLD ); } //Move the dot myDot.move(); }
For the logic module we check if the dot went through the exit. If it did we set the next state so we can go back to the overworld.

Then we move the dot as usual.
void RedRoom::render() { //Set the camera myDot.set_camera(); //Show the background apply_surface( 0, 0, background, screen, &camera ); //Show the door exit.show(); //Show the dot on the screen myDot.show(); }
When we render, we set the camera, show the background, show the exit and show the dot.
BlueRoom::BlueRoom() { //Load the background background = load_image( "blueroom.png" ); //Set the exit exit.init( 310, 0 ); //Set the dot myDot.init( 310, 40, LEVEL_WIDTH, LEVEL_HEIGHT ); }
Again, the blue room is pretty much the same story as with the red room. We load the background, place the exit and place the dot in front of the exit.
//Set the current state ID stateID = STATE_INTRO; //Set the current game state object currentState = new Intro();
In the main() function after we intialize and load our files, we set the state ID to the intro screen and we create our intro game state object.
//While the user hasn't quit while( stateID != STATE_EXIT ) { //Start the frame timer fps.start(); //Do state event handling currentState->handle_events(); //Do state logic currentState->logic(); //Change state if needed change_state(); //Do state rendering currentState->render(); //Update the screen if( SDL_Flip( screen ) == -1 ) { return 1; } //Cap the frame rate if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND ) { SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() ); } }
Here's our game loop once again. This will keep looping until the user sets the next state to STATE_EXIT.

Hopefully you remember how change_state() won't delete the game state object when the user exits because it needs something to render with. Well where does it delete the old object?
void clean_up() { //Delete game state and free state resources delete currentState; //Free the surfaces SDL_FreeSurface( dot ); //Close the font that was used TTF_CloseFont( font ); //Quit SDL_ttf TTF_Quit(); //Quit SDL SDL_Quit(); }
The object gets deleted in the clean up function so the object's destructor can free its resources. We also free the global resources and quit SDL.

There are other types of state machines besides the finite state machine, like stack or array based state machines. Different types of state machines are more useful depending on your game, but the fundamentals are the same. If you understand how to make this type of state machine, the others shouldn't be too hard to figure out.
Download the media and source code for this article here.