Part 6: Finite State Machine
Creating a system to handle and switch between our game's states
We have some core functionality down with our game loop and we can take input and draw images to the screen; now we can create the system with which the game will run.
A finite state machine is a system by which you have several states and you can switch between them, destroy them, add new ones and so on, all the while only 1 is ever being updated, rendered and handling input at one time. This maximises efficiency when swapping states (states are things like your main menu or your settings screen or the levels in your game). To start we first need to create an abstract base class called GameState, which will act as a sort of blueprint or parent, if you like, for all our game states (an abstract base class is one which doesn’t have a definition itself i.e. no .cpp file, but can be used as a parent class of another class which will then inherit all of its public and protected attributes).
So go ahead and create a new header file and call it GameState.h, clear it out and write out the following in there:
A finite state machine is a system by which you have several states and you can switch between them, destroy them, add new ones and so on, all the while only 1 is ever being updated, rendered and handling input at one time. This maximises efficiency when swapping states (states are things like your main menu or your settings screen or the levels in your game). To start we first need to create an abstract base class called GameState, which will act as a sort of blueprint or parent, if you like, for all our game states (an abstract base class is one which doesn’t have a definition itself i.e. no .cpp file, but can be used as a parent class of another class which will then inherit all of its public and protected attributes).
So go ahead and create a new header file and call it GameState.h, clear it out and write out the following in there:
#pragma once
#include <string> #include <SDL.h> class GameState { public: protected: }; |
Here we have a standard setup class with a few includes, you should know by now what’s going on here so I’ll just explain the includes.
We’re including <string> because our game states will have an ID which we can use to call our states to switch to them and manipulate them in our game state machine.
We’re including <SDL.h> so we can use SDL functions in our game states.
Now we have the barebones of our base class setup we can go ahead and start introducing some functions for core functionality. The basics we want to have in our game state is include every stage in the standard game loop; update, handleEvents and render. We’re going to include just update and render for now so go ahead and add those to functions in the public section like so:
We’re including <string> because our game states will have an ID which we can use to call our states to switch to them and manipulate them in our game state machine.
We’re including <SDL.h> so we can use SDL functions in our game states.
Now we have the barebones of our base class setup we can go ahead and start introducing some functions for core functionality. The basics we want to have in our game state is include every stage in the standard game loop; update, handleEvents and render. We’re going to include just update and render for now so go ahead and add those to functions in the public section like so:
public:
virtual void update() = 0; virtual void render() = 0; |
You may not have seen this type of function declaration before, so I’ll explain it to you briefly. A virtual function is one which can be overridden by a class of the same signature (meaning it derives from said class, like our game states will derive from the base class GameState). By putting “= 0” after the last bracket and before the semi colon, we are saying that it is equal to nothing if not overridden by the child class.
NOTE: All the functions in the base class will be virtual as they will all be overridden in each sub class of GameState.
The next thing we want to handle is what happens when we enter and exit our states, for this we create two new functions of type bool (so we can return false in case of an error); put them after the update and render functions:
NOTE: All the functions in the base class will be virtual as they will all be overridden in each sub class of GameState.
The next thing we want to handle is what happens when we enter and exit our states, for this we create two new functions of type bool (so we can return false in case of an error); put them after the update and render functions:
virtual bool onEnter() = 0;
virtual bool onExit() = 0; |
We will cover this more when creating an example state later to test our finite state machine but for now just know that their names are fairly descriptive of what their purpose is.
Next we want to handle input so we’re going to add to our states some functions to handle input which will be called using our InputHandler later on:
Next we want to handle input so we’re going to add to our states some functions to handle input which will be called using our InputHandler later on:
virtual void onKeyDown(SDL_Event* e) = 0;
virtual void onKeyUp(SDL_Event* e) = 0; virtual void onMouseButtonUp(SDL_Event& e) = 0; virtual void onMouseButtonDown(SDL_Event& e) = 0; virtual void onMouseMove(SDL_Event& e) = 0; |
And finally, we want a way to get the ID of a state so we create a function which we will override as a getter in our individual game state classes:
virtual std::string getStateID() const = 0;
|
You’re probably wondering why we haven’t declared a protected (protected is similar to private but protected has the added capability of being accessible in derived classes, i.e. our individual state classes) variable for ID, and that’s because we’re going to create individual ones for each state.
So that is out abstract base class GameState all done with now, so we can move onto actually making our finite state machine which we’re going to call GameStateMachine. Go ahead and create a new class called GameStateMachine then head into the GameStateMachine.h file and clear it all out; enter the following code into our now empty GameStateMachine.h:
So that is out abstract base class GameState all done with now, so we can move onto actually making our finite state machine which we’re going to call GameStateMachine. Go ahead and create a new class called GameStateMachine then head into the GameStateMachine.h file and clear it all out; enter the following code into our now empty GameStateMachine.h:
#pragma once
#include "GameState.h" #include <vector> #include <SDL.h> class GameStateMachine { public: private: }; |
The includes we have are fairly standard, nothing really new here; we include GameState.h so we can use game states in our machine (vital). We include <vector> so that we can have a vector array to store all our game states in. Finally, we include <SDL.h> so we can have SDL functionality.
First things first let’s add our vector array for storing our game states. This is going to be private, so in the private section add a vector array of type GameState* called gameStates (or something relevant).
First things first let’s add our vector array for storing our game states. This is going to be private, so in the private section add a vector array of type GameState* called gameStates (or something relevant).
#pragma once
#include "GameState.h" #include <vector> #include <SDL.h> class GameStateMachine { public: private: std::vector<GameState*> gameStates; }; |
We will be using this array to store our game states, pushing new states to the back, erasing states from it and swapping states out for new ones.
Let’s go ahead and add some functions to our game state machine; along with the constructor and deconstructor, let’s first add the functions associated with the game states themselves:
Let’s go ahead and add some functions to our game state machine; along with the constructor and deconstructor, let’s first add the functions associated with the game states themselves:
public:
GameStateMachine() {} ~GameStateMachine() {} void update(); void render(); void onKeyDown(SDL_Event* e); void onKeyUp(SDL_Event* e); void onMouseButtonUp(SDL_Event& e); void onMouseButtonDown(SDL_Event& e); void onMouseMove(SDL_Event& e); |
We used “{}” instead of “;” for our constructor and descontrustor because nothing happens when the class is initialised or destroyed so we don’t need to trouble ourselves setting this up in the .cpp file. We then add the functions from the game state base class so that we can call each individual function in the state appropriately.
Next we want to add some unique functions to our game state machine; these functions are to do with the adding, removing and changing of game states in the vector array:
Next we want to add some unique functions to our game state machine; these functions are to do with the adding, removing and changing of game states in the vector array:
public:
GameStateMachine() {} ~GameStateMachine() {} void pushState(GameState* state); void changeState(GameState* state); void popState(); void update(); void render(); void onKeyDown(SDL_Event* e); void onKeyUp(SDL_Event* e); void onMouseButtonUp(SDL_Event& e); void onMouseButtonDown(SDL_Event& e); void onMouseMove(SDL_Event& e); |
The first function, “pushState”, takes a game state as the parameter (let it be noted that any class that derives from game state can also be passed into the parameters, this is the case for any parent and child classes). It then pushes that parameter to the back of the game state vector array and thus set it as the current state (the one that will be rendered, updated and have it’s input handled), or at least it will when we define it in GameStateMachine.cpp.
The second function, “changeState”, also takes a game state as the parameter but instead of just going to the back of the array and becoming the current state, it “overwrites” the state active before you change the state. For example, let’s the play button in your menu takes you to level 1 state, if you used the changeState function it would remove the menu state and replace it with the level 1 state. The different between these two functions is that with pushState you can switch back to the previous state using popState.
Now, “popState”, pushes the active (or last) state and removes it from the array. Useful if you need a quick nifty pause menu state in your game.
That’s our GameStateMachine.h all setup now so let’s head over to GameStateMachine.cpp and define those functions starting with the three state altering functions in the order we declared them just now. If the .cpp file has anything in it, clear it out and include the header file we just filled (#include “GameStateMachine.h”); first up though, pushState:
The second function, “changeState”, also takes a game state as the parameter but instead of just going to the back of the array and becoming the current state, it “overwrites” the state active before you change the state. For example, let’s the play button in your menu takes you to level 1 state, if you used the changeState function it would remove the menu state and replace it with the level 1 state. The different between these two functions is that with pushState you can switch back to the previous state using popState.
Now, “popState”, pushes the active (or last) state and removes it from the array. Useful if you need a quick nifty pause menu state in your game.
That’s our GameStateMachine.h all setup now so let’s head over to GameStateMachine.cpp and define those functions starting with the three state altering functions in the order we declared them just now. If the .cpp file has anything in it, clear it out and include the header file we just filled (#include “GameStateMachine.h”); first up though, pushState:
void GameStateMachine::pushState(GameState *state)
{ gameStates.push_back(state); gameStates.back()->onEnter(); } |
Here we see two very simple lines; the first “pushes” the state to the back/end of the array using the inbuilt vector function push_back() which takes a variable of the delcared vector type (in our case, GameState*). The second line uses the vector function back() which gets the last element in the array and because it’s a GameState we can call the onEnter() function to perform any and all operations needed before the state can be used.
Moving on now to the second and more complex function that is changeState:
Moving on now to the second and more complex function that is changeState:
void GameStateMachine::changeState(GameState *state)
{ if (!gameStates.empty()) { if (gameStates.back()->getStateID() == state->getStateID()) { return; } } gameStates.push_back(state); if (!gameStates.empty()) { if (gameStates.back()->onExit()) { gameStates.erase(gameStates.end() - 2); } } gameStates.back()->onEnter(); } |
Let’s run through the functionality of this particular function. First we have an if statement to check first whether the array of game states isn’t empty, because the next if statement checks to see if the state you’re trying to change to is the same as the current state and if so exit the function (return), and if the game state array is empty then this will pull up an error. Let’s assume that it’s either empty or the state we want to change to isn’t the same as the current one, so the code continues; the next line, you’ll remember from the last function, pushes the state we want to change to to the back of the array. Next we perform the same check as before just to make sure and then we call the onExit function of the previous state and if succesful we then erase the second to last state in the array (the last being the one we just added, don’t want to erase that now). When all that’s done, the function finally calls the onEnter function of the newly inserted state. The state has been changed.
The final function of the three, “popState” simple removes the current state from the vector array:
The final function of the three, “popState” simple removes the current state from the vector array:
void GameStateMachine::popState()
{ if (!gameStates.empty()) { if (gameStates.back()->onExit()) { gameStates.erase(gameStates.end() - 1); } } } |
We first check to make sure the vector array isn’t empty, if it isn’t then we take the current state and call it’s onExit function before finally erasing it from the array. Be careful when you use this last function because if there is only one state in the array and you pop it, then you’re going to get errors because it’ll try and update a non existent state.
Now we can move onto to implementing our other functions, the ones that make the states work.
Now we can move onto to implementing our other functions, the ones that make the states work.
void GameStateMachine::update()
{ if (!gameStates.empty()) { gameStates.back()->update(); } } void GameStateMachine::render() { if (!gameStates.empty()) { gameStates.back()->render(); } } void GameStateMachine::onKeyDown(SDL_Event* e) { if (!gameStates.empty()) { gameStates.back()->onKeyDown(e); } } void GameStateMachine::onKeyUp(SDL_Event* e) { if (!gameStates.empty()) { gameStates.back()->onKeyUp(e); } } void GameStateMachine::onMouseButtonDown(SDL_Event& e) { if (!gameStates.empty()) { gameStates.back()->onMouseButtonDown(e); } } void GameStateMachine::onMouseButtonUp(SDL_Event& e) { if (!gameStates.empty()) { gameStates.back()->onMouseButtonUp(e); } } void GameStateMachine::onMouseMove(SDL_Event& e) { if (!gameStates.empty()) { gameStates.back()->onMouseMove(e); } } |
I hope you can see a pattern from function to function here, and if I explain just one function you should be able to get the rest incredibly easily, so let’s go with the update function. This checks to make sure that the game states array isn’t empty and if it isn’t then go ahead and get the state at the end of the array (current state) and call it’s update function. Now, I could explain every single one of the, but the only difference between them all, is the function it calls in the end so if you can’t figure it out and you know me, contact me.
That is now our GameStateMachine class done with, we don’t need to do anymore inside the class. What we need to do now is create an example state and push it into the array and see if the state machine handles it properly. Go ahead and create a new class and call it MenuState (every programmer has created a menu simulator once or twice… or three times… or more… you get the picture).
Once you’ve created that, go into the header file, clear it out and then write the following lines:
That is now our GameStateMachine class done with, we don’t need to do anymore inside the class. What we need to do now is create an example state and push it into the array and see if the state machine handles it properly. Go ahead and create a new class and call it MenuState (every programmer has created a menu simulator once or twice… or three times… or more… you get the picture).
Once you’ve created that, go into the header file, clear it out and then write the following lines:
#pragma once
#include "GameState.h" #include <string> class MenuState : public GameState { public: private: }; |
Now, the new bit here for you will be the “ : public GameState” after the class name declaration. This means that MenuState extends GameState or is a child of GameState. This means that it inherits all of GameState’s attributes and can/must override it’s functions. So with that said, we need to overide the functions, you’ll remember them from GameState.h:
public:
virtual void update(); virtual void render(); virtual bool onEnter(); virtual bool onExit(); virtual void onKeyDown(SDL_Event* e); virtual void onKeyUp(SDL_Event* e); virtual void onMouseButtonUp(SDL_Event& e); virtual void onMouseButtonDown(SDL_Event& e); virtual void onMouseMove(SDL_Event& e); virtual std::string getStateID() const { return menuID; } |
Here we write them exactly as we did in GameState.h except without the “ = 0” on the end, because these won’t equal nothing, we’re going to define them in MenuState.cpp. The slightly odd one out is the final one, the getStateID function. This overrides the one in GameState.h but instead of a semi colon ending it, we can actually define this function here as all it does it return menuID which we’ll create now in the private section of MenuState.h:
private:
static const std::string menuID; |
All this is, is a string variable which acts as an identifier for the state, and it’s private because we don’t want anyone changing it outside of the class itself.
Now that our header file for our example state is setup, let’s declare our functions in the MainMenu.cpp; we start first with the includes and one line you need at the top before all your functions:
Now that our header file for our example state is setup, let’s declare our functions in the MainMenu.cpp; we start first with the includes and one line you need at the top before all your functions:
#include "MenuState.h"
#include <iostream> #include "Game.h" #include "GameStateMachine.h" const std::string MenuState::menuID = "MENU"; |
First of all, we include the header file for the class, then we include <iostream> so we can print to the console for debugging purposes. Next we include Game.h so we can access the renderer and more attributes/functions from it. Finally we include GameStateMachine.h so we can change states from here (e.g. a play button).
The last line is defining the ID of the state, remember the private variable from the header file? We’re going to give it the ID “MENU” for simplicity.
Next we’re going to define our functions, let’s just go ahead and define all our functions and leave them empty like so (because you probably have your head around defining functions by now:
The last line is defining the ID of the state, remember the private variable from the header file? We’re going to give it the ID “MENU” for simplicity.
Next we’re going to define our functions, let’s just go ahead and define all our functions and leave them empty like so (because you probably have your head around defining functions by now:
void MenuState::update()
{ } void MenuState::render() { } bool MenuState::onEnter() { return true; } bool MenuState::onExit() { return true; } void MenuState::onKeyDown(SDL_Event* e) { } void MenuState::onKeyUp(SDL_Event* e) { } void MenuState::onMouseButtonDown(SDL_Event& e) { } void MenuState::onMouseButtonUp(SDL_Event& e) { } void MenuState::onMouseMove(SDL_Event& e) { } |
So here we have a lot of empty functions, simply because we don’t actually need anything in here for the state machine to accept it, it just won’t do anything if we don’t (the bool functions have return true; in them because as a function other than the type void it needs a return statement).
So let’s add a couple of console print outs to the onEnter and onExit functions so we know when the state is being loaded and unloaded:
So let’s add a couple of console print outs to the onEnter and onExit functions so we know when the state is being loaded and unloaded:
bool MenuState::onEnter()
{ std::cout << "entering MenuState..." << std::endl; return true; } bool MenuState::onExit() { std::cout << "exiting MenuState..." << std::endl; return true; } |
We know what this does by now, printing to the console should be fairly natural at this point (urgh… debugging…).
Next up we can use one line in our render function to change the background colour so we can tell that it’s working:
Next up we can use one line in our render function to change the background colour so we can tell that it’s working:
void MenuState::render()
{ SDL_SetRenderDrawColor(_Game::Instance()->getRenderer(), 0, 0, 0, 255); } |
The SDL function SDL_SetRenderDrawColor takes a renderer as it’s first parameter, and the last 4 parameters pertain to R, G, B and A; Red, Green, Blue and Alpha Transparency (see-throughness). All 4 values are integers and their maximum value as colour codes is 255, their lowest being 0. Google RGBA colour codes if you aren’t very familiar with them.
So now we have our example state set up, let’s head over to the Game.cpp file and setup our state machine before we finally test this state.
In the Game.cpp file, you want to include the MenuState.h file (#include “MenuState.h”) but not the game state machine. This is because we include the game state machine in our state classes so we don’t need to “re-include” it in our Game class. Once you’ve included that you’ll want to go to the bottom of your init function and add the following line after running = true:
So now we have our example state set up, let’s head over to the Game.cpp file and setup our state machine before we finally test this state.
In the Game.cpp file, you want to include the MenuState.h file (#include “MenuState.h”) but not the game state machine. This is because we include the game state machine in our state classes so we don’t need to “re-include” it in our Game class. Once you’ve included that you’ll want to go to the bottom of your init function and add the following line after running = true:
gameStateMachine = new GameStateMachine();
|
This will bring up an error, so go into your Game.h file, include “GameStateMachine.h” and add this line to your private section:
GameStateMachine* gameStateMachine;
|
Now, back to the .cpp file, the error should now be gone. Next, after that definition of our new gameStateMachine object we want to push the menu state into the game state machine (we use push because at this point, the array is empty):
gameStateMachine = new GameStateMachine();
gameStateMachine->pushState(new MenuState()); |
You’ll see the two TextureManager functions here too, the two load functions. We can cut these from here and paste them somewhere useful (or comment them out if you know how) because we can plonk these in our onEnter function for MenuState if we wanted.
Now what we need to do is call all our game state machine functions from the Game class’ functions. Let’s go ahead and call render in render and update in update:
Now what we need to do is call all our game state machine functions from the Game class’ functions. Let’s go ahead and call render in render and update in update:
void Game::render()
{ SDL_RenderClear(renderer); gameStateMachine->render(); SDL_RenderPresent(renderer); } void Game::update() { gameStateMachine->update(); } |
You will see that in the render function we had the two draw functions in there, you might want to cut them and paste them somewhere you can get them again (or comment them out if you know how) because we can plonk them straight into the render function for our MainMenu state if we wanted.
All we have done is is use the gameStateMachine object to call the update and render functions in their appropriate functions in Game.cpp.
You’ll be thinking “but what about the event handling functions” well, let’s first add a public getter for our Game’s game state machine at the bottom of the public section in Game.h:
All we have done is is use the gameStateMachine object to call the update and render functions in their appropriate functions in Game.cpp.
You’ll be thinking “but what about the event handling functions” well, let’s first add a public getter for our Game’s game state machine at the bottom of the public section in Game.h:
GameStateMachine* getStateMachine() { return gameStateMachine; }
|
Now we can head over to our InputHandler.cpp and call those input handling functions from our game state machine.
onKeyDown:
onKeyDown:
void InputHandler::onKeyDown(SDL_Event* event)
{
std::cout << "Key Pressed: " << SDL_GetKeyName(event->key.keysym.sym)
<< std::endl;
_Game::Instance()->getStateMachine()->onKeyDown(event);
}
{
std::cout << "Key Pressed: " << SDL_GetKeyName(event->key.keysym.sym)
<< std::endl;
_Game::Instance()->getStateMachine()->onKeyDown(event);
}
onKeyUp:
void InputHandler::onKeyUp(SDL_Event* event)
{
std::cout << "Key Released: " << SDL_GetKeyName(event->key.keysym.sym)
<< std::endl;
_Game::Instance()->getStateMachine()->onKeyUp(event);
}
{
std::cout << "Key Released: " << SDL_GetKeyName(event->key.keysym.sym)
<< std::endl;
_Game::Instance()->getStateMachine()->onKeyUp(event);
}
onMouseButtonDown:
void InputHandler::onMouseButtonDown(SDL_Event* e)
{
if (e->button.button == SDL_BUTTON_LEFT)
{
mouseButtonStates[LEFT] = true;
std::cout << "Left Mouse Button Pressed." << std::endl;
}
else if (e->button.button = SDL_BUTTON_MIDDLE)
{
mouseButtonStates[MIDDLE] = true;
std::cout << "Middle Mouse Button Pressed." << std::endl;
}
else if (e->button.button = SDL_BUTTON_RIGHT)
{
mouseButtonStates[RIGHT] = true;
std::cout << "Right Mouse Button Pressed." << std::endl;
}
_Game::Instance()->getStateMachine()->onMouseButtonDown(*e);
}
{
if (e->button.button == SDL_BUTTON_LEFT)
{
mouseButtonStates[LEFT] = true;
std::cout << "Left Mouse Button Pressed." << std::endl;
}
else if (e->button.button = SDL_BUTTON_MIDDLE)
{
mouseButtonStates[MIDDLE] = true;
std::cout << "Middle Mouse Button Pressed." << std::endl;
}
else if (e->button.button = SDL_BUTTON_RIGHT)
{
mouseButtonStates[RIGHT] = true;
std::cout << "Right Mouse Button Pressed." << std::endl;
}
_Game::Instance()->getStateMachine()->onMouseButtonDown(*e);
}
onMouseButtonUp:
void InputHandler::onMouseButtonUp(SDL_Event* e)
{
if (e->button.button == SDL_BUTTON_LEFT)
{
mouseButtonStates[LEFT] = false;
std::cout << "Left Mouse Button Released." << std::endl;
}
else if (e->button.button == SDL_BUTTON_MIDDLE)
{
mouseButtonStates[MIDDLE] = false;
std::cout << "Middle Mouse Button Released." << std::endl;
}
else if (e->button.button == SDL_BUTTON_RIGHT)
{
mouseButtonStates[RIGHT] = false;
std::cout << "Right Mouse Button Released." << std::endl;
}
_Game::Instance()->getStateMachine()->onMouseButtonUp(*e);
}
{
if (e->button.button == SDL_BUTTON_LEFT)
{
mouseButtonStates[LEFT] = false;
std::cout << "Left Mouse Button Released." << std::endl;
}
else if (e->button.button == SDL_BUTTON_MIDDLE)
{
mouseButtonStates[MIDDLE] = false;
std::cout << "Middle Mouse Button Released." << std::endl;
}
else if (e->button.button == SDL_BUTTON_RIGHT)
{
mouseButtonStates[RIGHT] = false;
std::cout << "Right Mouse Button Released." << std::endl;
}
_Game::Instance()->getStateMachine()->onMouseButtonUp(*e);
}
onMouseMove:
void InputHandler::onMouseMove(SDL_Event* e)
{
mPos->setX(e->motion.x);
mPos->setY(e->motion.y);
std::cout << "Mouse position = x: " << mPos->getX() << ", y:" <<
mPos->getY() << std::endl;
_Game::Instance()->getStateMachine()->onMouseMove(*e);
}
{
mPos->setX(e->motion.x);
mPos->setY(e->motion.y);
std::cout << "Mouse position = x: " << mPos->getX() << ", y:" <<
mPos->getY() << std::endl;
_Game::Instance()->getStateMachine()->onMouseMove(*e);
}
Again that process is fairly simple, it’s just a case of calling the game state machine functions from their matching input handler functions using the _Game singleton and it’s state machine getter function.
Now we can run our program and instead of whatever colour you chose to paint the background before, it should now be replaced by the colour you chose to put in your MenuState’s render function (in my case (0, 0, 0, 255) or black):
Now we can run our program and instead of whatever colour you chose to paint the background before, it should now be replaced by the colour you chose to put in your MenuState’s render function (in my case (0, 0, 0, 255) or black):
So if you want to add more states you can do by following the same way we did creating MenuState. We’ll cover moving from state to state in the next part of the tutorial which will introduce you to SDLGameObject, another base class for any and all objects you want to use in your game such as player, menu buttons etc. So look forward to that if it isn’t already available by the button below and as always, the source code is there too. Had fun? I sure did… no really, I did… I promise…