binpress

Creating a City Building Game with SFML Part 1: State Manager

Get started with this tutorial series here!

Let’s begin! The core of almost any game is a state manager, which unsurprisingly handles each of the states the game can be in (e.g. the main menu, the game world, or the options menu). By using a state manager we can nicely partition the game into these states, instead of them just being nice logical blocks that remain solely in our head. We will do this by creating a base GameState class which our actual states will inherit from, and then creating a Game class that will handle the changing of the states as well as storing the information that is universal to (almost) every state. Creating a game_state.hpp file then,

  1. #ifndef GAME_STATE_HPP
  2. #define GAME_STATE_HPP
  3.  
  4. #include "game.hpp"
  5.  
  6. class GameState
  7. {
  8.     public:
  9.  
  10.     Game* game;
  11.  
  12.     virtual void draw(const float dt) = 0;
  13.     virtual void update(const float dt) = 0;
  14.     virtual void handleInput() = 0;
  15. };
  16.  
  17. #endif /* GAME_STATE_HPP */

Ignoring the #include for a non-existent file (we’ll fix that soon) we have a small class definition. game is just a pointer back to the state manager mentioned earlier, the functions are the interesting part. They are all pure virtual functions and so are not defined within the GameState class; they must be overridden by an inherited class in order for them to be used. This is why we haven’t defined or even declared a constructor, the compiler wouldn’t allow you to create an instance of this class! The functions themselves are pretty self-explanatory, they’ll just be called by the state manager in order to draw the state to the screen, update its logic, or handle its input. We’ve used virtual functions like this so that the state manager does not need to know what kind of state it has active, whatever kind it is (main menu, game world, or otherwise) it will just call the same functions.

Fixing that preemptive #include, create a game.hpp file.

  1. #ifndef GAME_HPP
  2. #define GAME_HPP
  3.  
  4. #include <stack>
  5. #include <SFML/Graphics.hpp>
  6.  
  7. class GameState;
  8.  
  9. class Game
  10. {
  11.     public:
  12.  
  13.     std::stack<GameState*> states;
  14.  
  15.     sf::RenderWindow window;
  16.  
  17.     void pushState(GameState* state);
  18.     void popState();
  19.     void changeState(GameState* state);
  20.     GameState* peekState();
  21.  
  22.     void gameLoop();
  23.  
  24.     Game();
  25.     ~Game();
  26. };
  27.  
  28. #endif /* GAME_HPP */

Here you might note that we have a bit of a loop with our class usage; Game needs to know about GameState, but GameState needs to know about Game! This would be a problem except for the little declaration of the GameState class within the file, which tells Game about GameState‘s existence and allows us to include game.hpp in game_state.hpp and fix the cycle! Looking in the class itself, there’s an std::stack data structure for storing the states and a few helper functions to push and pop states to and from the stack. We then have the gameLoop function, which will act much like main in our program. This is purely a design decision, the game loop (which will call those virtual functions) could be placed in main if you wanted. We then have a simple constructor and destructor and finally, our first bit of SFML code! Well, if you count a variable declaration of course… As the name implies window is just the window that our game will be drawn in. With the header file defined, let’s create the source file for the Game class, game.cpp

  1. #include <stack>
  2.  
  3. #include <SFML/Graphics.hpp>
  4. #include <SFML/System.hpp>
  5.  
  6. #include "game.hpp"
  7. #include "game_state.hpp"
  8.  
  9. void Game::pushState(GameState* state)
  10. {
  11.     this->states.push(state);
  12.  
  13.     return;
  14. }
  15.  
  16. void Game::popState()
  17. {
  18.     delete this->states.top();
  19.     this->states.pop();
  20.  
  21.     return;
  22. }
  23.  
  24. void Game::changeState(GameState* state)
  25. {
  26.     if(!this->states.empty())
  27.         popState();
  28.     pushState(state);
  29.  
  30.     return;
  31. }
  32.  
  33. GameState* Game::peekState()
  34. {
  35.     if(this->states.empty()) return nullptr;
  36.     return this->states.top();
  37. }
  38.  
  39. void Game::gameLoop()
  40. {
  41.     sf::Clock clock;
  42.  
  43.     while(this->window.isOpen())
  44.     {
  45.         sf::Time elapsed = clock.restart();
  46.         float dt = elapsed.asSeconds();
  47.  
  48.         if(peekState() == nullptr) continue;
  49.         peekState()->handleInput();
  50.         peekState()->update(dt);
  51.         this->window.clear(sf::Color::Black);
  52.         peekState()->draw(dt);
  53.         this->window.display();
  54.     }
  55. }
  56.  
  57. Game::Game()
  58. {
  59.     this->window.create(sf::VideoMode(800, 600), "City Builder");
  60.     this->window.setFramerateLimit(60);
  61. }
  62.  
  63. Game::~Game()
  64. {
  65.     while(!this->states.empty()) popState();
  66. }

The state changing functions are all similar and simple; pushState takes a pointer to a GameState(or one of its derived classes) and pushes it on to the state stack, popState removes the top state from the stack, changeState pops the previous state (if there was one) and then pushes the new one on, and finally peekState returns a pointer to whatever state is on top of the stack. But why do we use a stack, and not just a currentState pointer, or similar? Well with a stack we can add new states over the top of existing ones whilst still keeping track of the old one, so we could add a pause game state which can be removed when the game is unpaused, but which keeps the progress of the game itself! Very handy, and I think worth the few extra lines it takes. The destructor is also state related, and just deletes all the states on the stack. Because of this, any states the state manager handles should be allocated using new.

Finally we get into some ‘proper’ SFML code! In the constructor we create a new render window (sf::Window cannot be drawn to, so we use sf::RenderWindow instead) which is 800 pixels wide, 600 pixels high, and is named “City Builder” (creativity is not my strong suit…). We then cap the framerate to 60fps, to stop the game from running too fast and eating too many cpu cycles!

Now let’s look at the gameloop function. Our game will use a variable timestep, here called dt, in order to make everything work at a smooth rate. There are many different ways of controlling the speed of the logic in a program, but since we want time to progress at the same rate inside the game, regardless of the framerate, we will use this method. (We could just assume that the game was running at 60fps, but that’s not a very safe assumption.) It works by starting a timer at the beginning of each frame, and stopping it at the end, before storing the time elapsed inside a timestep variable. This is then passed to any functions that need to know the speed of the game. So if we want a ball to move at 100 pixels per second, and the frame took 0.1 seconds, then the ball needs to move 10 pixels this frame. If the next frame takes twice as long, the ball should move twice as far. clock.restartconveniently returns the time since clock started, so we can combine the stopping and starting into one line. We then use the virtual functions that we defined earlier, making sure that we clear the screen before drawing the new one and updating the window.

We have one more file to define for now, main.cpp! It’ll be rather simple (our simplest file even) since the heavy lifting is done by the Game class

  1. #include "game.hpp"
  2.  
  3. int main()
  4. {
  5.     Game game;
  6.  
  7.     game.gameLoop();
  8.  
  9.     return 0;
  10. }

Make the state manager, and then start the loop, trivial! We’ll be back later to add another line or too, but for now we’ve finished our state manager! If you run the program, an empty window should appear, but sadly you’ll have to close the poor thing using a task manager or equivalent — hitting close will do nothing until we actually create a state!

Source code for this section

Author: Daniel Mansfield

Scroll to Top