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,
#ifndef GAME_STATE_HPP
#define GAME_STATE_HPP
#include "game.hpp"
class GameState
{
public:
Game* game;
virtual void draw(const float dt) = 0;
virtual void update(const float dt) = 0;
virtual void handleInput() = 0;
};
#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.
#ifndef GAME_HPP
#define GAME_HPP
#include <stack>
#include <SFML/Graphics.hpp>
class GameState;
class Game
{
public:
std::stack<GameState*> states;
sf::RenderWindow window;
void pushState(GameState* state);
void popState();
void changeState(GameState* state);
GameState* peekState();
void gameLoop();
Game();
~Game();
};
#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
#include <stack>
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include "game.hpp"
#include "game_state.hpp"
void Game::pushState(GameState* state)
{
this->states.push(state);
return;
}
void Game::popState()
{
delete this->states.top();
this->states.pop();
return;
}
void Game::changeState(GameState* state)
{
if(!this->states.empty())
popState();
pushState(state);
return;
}
GameState* Game::peekState()
{
if(this->states.empty()) return nullptr;
return this->states.top();
}
void Game::gameLoop()
{
sf::Clock clock;
while(this->window.isOpen())
{
sf::Time elapsed = clock.restart();
float dt = elapsed.asSeconds();
if(peekState() == nullptr) continue;
peekState()->handleInput();
peekState()->update(dt);
this->window.clear(sf::Color::Black);
peekState()->draw(dt);
this->window.display();
}
}
Game::Game()
{
this->window.create(sf::VideoMode(800, 600), "City Builder");
this->window.setFramerateLimit(60);
}
Game::~Game()
{
while(!this->states.empty()) popState();
}
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.restart
conveniently 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
#include "game.hpp"
int main()
{
Game game;
game.gameLoop();
return 0;
}
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!
Author: Daniel Mansfield