Multi State Management in Games

Multi State Management in Games

Simple C++ implementation of game states

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

shooter.png

Video here. Setup could be like here

states.png

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

statemanager.png

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:

states2.png

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.

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

Thank you!