Game State Manager
This small project implements a system which can handle multiple different game states in a centralised way. It most surely could be used in certain kind of applications a well. I will introduce the problem, then show the basic design of the system and finally dive in to the code in more details. The full source code of the GameStateSystem can be found in my git .
I originally implemented this in C# back in 2013 when I was working with my C# .NET 2-D game engine called smithNgine. This is one of the many components, but being small and rather simple it still was a quite important part of the engine. Although this is a complete rewrite it still follows the exact same design as the 2013 version.
Description
Most games are not single screen (or state as I call them). One example could be for example a simple 2-D top down space shooter game
Video here. Setup could be like here
You could of course implement everything in a single controller class or other custom sub-classes but that is quite hard to maintain, unreadable and quite messsy. That is just my opinion so I liked to do it in more controlled way. And more importantly, in more Object Oriented way.
By separating the game logic and rendering into states and layers makes controlling the game flow more easier. GameStateManager owns and manages all sub states. A game state can be made active using the manager. Each game state can have zero or more layers via the game state layers.
So we can come up with a design like this
The core framework classes are the StateManager, State and Layer and rest are few example cases for a concrete game setup. And this is what I implemented and came to this. To preserve some clarity in the above design picture I did not detail the the relation and ownership of the concrete state and layer objects to the concrete game object so I omitted that completely.
Implementation
According to the design I implemented the following classes
- GameStateManager
- GameState
- GameStateLayer
Their relations are like described in the design above.
You implement your your concrete states by inheriting from GameState
and implement the necessary features. Then you add your states in GameStateManager
instance. You have to make one state active before entering to the game loop where you call the state updating method in GameStateManager::update(float fTimeElapsed)
to do stuff in your
active state. To switch to another state, call activateState(id)
whenever needed.
Currently the state switching is immediate but in the future I will add state transitions
controlled by the GameStateManager
. This could be used for example to fade
in/out the current and incoming new state, or what ever you need to do.
Simple setup
1) Create your states inheriting from GameState
class, for example:
class GSDStatePrimary : public GameState
{
public:
GSDStatePrimary(uint16_t id, olc::PixelGameEngine* pge) :
GameState(id), m_pge(pge) {
m_background.Load("assets/desert.png");
LOG_INFO() << "Constructed state " << id;
};
GSDStatePrimary() = delete;
~GSDStatePrimary() = default;
bool update(float fElapsedTime) {
m_pge->Clear(olc::RED);
// Cause update for all owned layers, if any
GameState::update(fElapsedTime);
std::string txt = "Handling state: " + std::to_string(id());
m_pge->DrawStringDecal(olc::vf2d(10.0f, 10.0f), txt);
return true;
}
private:
olc::PixelGameEngine* m_pge;
olc::Renderable m_background;
};
class GSDStateSecondary : public GameState
{
...
}
...
In the above code, please notice this important thing
// Cause update for all owned layers, if any
GameState::update(fElapsedTime);
If you have layers in a state and forget to call the base State class update() your layers will not do anything. So do not forget it, and call it in an appropriate place in your GameState::update()
method. Mainly thinking the rendering order.
In your application setup, create a statemanager and states and add them to the manager:
m_stateManager = std::make_unique<GameStateManager>();
// Pass a state id and what ever other information you need for your
// concrete state class. In here I pass "this" pointer which in the
// included demo project is the instance to Pixel Game Engine for
// rendering stuff.
std::shared_ptr<GameState> state1 = std::make_shared<GSDStatePrimary>(
0, this);
std::shared_ptr<GameState> state2 = std::make_shared<GSDStateSecondary>(
1, this);
m_stateManager->addState(state1, true);
m_stateManager->addState(state2);
In your active game loop, do the following (your own game loop might be different :). In this example the game engine (PGE) calls this whenever possible as fast as possible giving a time difference (float) from the last call to this same handler.
bool OnUserUpdate(float fElapsedTime) override
{
// Update state(s). Pass the elapsed time to the statemanager
// which then calls the active state.
// GameStateManager::update() will return true or false, depending
// on should the game application be terminated or continued.
// The concrete active state you have implemented has to have logic
// to use this feature if you decide to utilize it. You do not have to
bool continue_loop = m_stateManager->update(fElapsedTime);
// This example just switches a state when F1 or F2 is pressed.
// If state 0 is active and state 0 is tried to activate again,
// nothing happens
if(GetKey(olc::Key::F1).bPressed) {
m_stateManager->activateState(0);
} else if(GetKey(olc::Key::F2).bPressed) {
m_stateManager->activateState(1);
}
else if(GetKey(olc::Key::ESCAPE).bPressed) {
continue_loop = false;
}
return continue_loop;
}
That's it. Usage is rather simple and in my opinion it makes controlling your game logic more straightforward, understandable and simple. Especially when concerning different states your game can be in.
In the above example I did not use layers at all because this was a simple application example. But if you had layers in one or more states, you would create the layers in similar way than creating your states and then add the new layers in the state you want.
Final notes
Oh, one more thing, a note about the rendering order. Currently everything rendered by the state manager is done in the order you have added sub components in there. For example if you do this:
If your State1 is active then the state will render S1Layer1 first, then S1Layer2. Only the active state will be rendered when GameStateManager::update()
is called.
Future improvements, feel free to do so if you want :)
This is a first version of this component so there are todo's:
Transition period
activateState()
is immediate when called. There is no transition
period enforced by the statemanager. I will add this feature
later. You can for example call activateState like this:
m_stateManager->activateState(nextStateId, 2.0f);
to cause the current state have a 2 second transition period where you can for example do a fadeout for all your graphics or what ever.
I have not yet decided should I have the current state and the next state active at the same time when this transition period is active.This might be of course be made optional and configurable.
Resources are kept in memory all the time for states and layers
I might add support where the resources (images, sprites, audio...) could be freed when needed and reloaded back when needed. But currently there is no such feature.
Rendering order improvements
Other fine and tandy improvements
I hope this proves to be useful for you, if not, do not use it. Comments and improvement ideas are welcome.
Legal notice
Although the demo project uses Pixel Game Engine (by javidx9) for the game application setup and rendering, the GameStateSystem and it's subclasses have no dependencies nor they use any components from Pixel Game Engine.
See the appropriate license for Pixel Game Engine in the subfolder pge/olcPixelGameEngine.h