25
\$\begingroup\$

First off, I'm not referring to scene management; I'm defining game state loosely as any sort of state in a game which has implications about whether or not user input should be enabled, or if certain actors should be temporarily disabled, etc.

As a concrete example, let's say it's a game of the classic Battlechess. After I make a move to take another player's piece, a short battle sequence plays. During this sequence, the player shouldn't be allowed to move pieces. So how would you track this sort of state transition? A finite state machine? A simple boolean check? It seems the latter would only work well for a game with very few state changes of this sort.

I can think of a lot of straightforward ways of handling this using finite state machines, but I can also see them quickly getting out of hand. I'm just curious if there's a more elegant way to keep track of game states/transitions.

\$\endgroup\$
2

4 Answers 4

24
\$\begingroup\$

I once came across an article that solves your problem quite elegantly. It is a basic FSM implementation, that is called in your main loop. I have outlined the basic rundown of the article in the rest of this answer.

Your basic game state looks like this:

class CGameState
{
    public:
        // Setup and destroy the state
        void Init();
        void Cleanup();

        // Used when temporarily transitioning to another state
        void Pause();
        void Resume();

        // The three important actions within a game loop
        void HandleEvents();
        void Update();
        void Draw();
};

Each game state is represented by an implementation of this interface. For your Battlechess example, this could mean these states:

  • intro animation
  • main menu
  • chess board setup animation
  • player move input
  • player move animation
  • opponent move animation
  • pause menu
  • endgame screen

States are managed in your state engine:

class CGameEngine
{
    public:
        // Creating and destroying the state machine
        void Init();
        void Cleanup();

        // Transit between states
        void ChangeState(CGameState* state);
        void PushState(CGameState* state);
        void PopState();

        // The three important actions within a game loop
        // (these will be handled by the top state in the stack)
        void HandleEvents();
        void Update();
        void Draw();

        // ...
};

Note that each state needs a pointer to the CGameEngine at some point, so the state itself can decide whether a new state should be entered. The article suggest passing in the CGameEngine as a parameter for HandleEvents, Update and Draw.

In the end, your main loop only deals with the state engine:

int main ( int argc, char *argv[] )
{
    CGameEngine game;

    // initialize the engine
    game.Init( "Engine Test v1.0" );

    // load the intro
    game.ChangeState( CIntroState::Instance() );

    // main loop
    while ( game.Running() )
    {
        game.HandleEvents();
        game.Update();
        game.Draw();
    }

    // cleanup the engine
    game.Cleanup();
    return 0;
}
\$\endgroup\$
5
  • 19
    \$\begingroup\$ C for Class? Ew. However, that is a good article - +1. \$\endgroup\$ Commented Jun 6, 2011 at 14:53
  • \$\begingroup\$ From what I can gather, this is the sort of thing that the question is explicitly -not- asking about. That's not to say you couldn't handle it this way, as you certainly could, but if all you wanted to do was disable the input temporarily I think it's both overkill and bad for maintenance to derive a new subclass of CGameState which is going to be 99% identical to another subclass. \$\endgroup\$
    – Kylotan
    Commented Jun 6, 2011 at 17:59
  • \$\begingroup\$ I would think this greatly depends on how the code coupled together. I can imagine a clean separation between selecting a piece and a destination (mainly UI indicators and input handling), and an animation of the chess piece towards that destination (a whole board animation where other pieces move out of their way, interact with the moving piece etc), making the states far from identical. This separates responsibility, allowing for easy maintenance and even reusability (intro demo, replay mode). I think this also answers the question in showing that using a FSM does not need to be a hassle. \$\endgroup\$
    – ghost
    Commented Jun 6, 2011 at 19:38
  • \$\begingroup\$ This is really great, thank you. A key point you made was in your latest comment: "using a FSM does not need to be a hassle." I had erroneously imagined that using a FSM would involve using switch statements, which isn't necessarily true. Another key confirmation is that each state needs a reference to the game engine; I wondered how this would work otherwise. \$\endgroup\$
    – vargonian
    Commented Jun 7, 2011 at 7:00
  • \$\begingroup\$ Excellent write up here: gameprogrammingpatterns.com/… \$\endgroup\$
    – fionbio
    Commented Feb 5, 2020 at 12:06
2
\$\begingroup\$

I start by handling this sort of thing the simplest way possible.

bool isPieceMoving;

Then I'll add the checks against that boolean flag in the relevant places.

If I later find I need more special cases than this - and only it - I re-factor into something better. There are usually 3 approaches I'll take:

  • Refactor any exclusive substate-representing flags into enums. eg. enum { PRE_MOVE, MOVE, POST_MOVE } and add the transitions wherever needed. Then I can check against this enum where I used to check against the boolean flag. This is a simple change but one which reduces the number of things you have to check against, allows you to use switch statements to manage behaviour effectively, etc.
  • Switch off individual subsystems as you need to. If the only difference during the battle sequence is that you can't move pieces, you can call pieceSelectionManager->disable() or similar at the start of the sequence, and pieceSelectionManager->enable(). You still essentially have flags, but now they're stored closer to the object they control, and you don't need to maintain any extra state in your game code.
  • The previous part implies the existence of a PieceSelectionManager: more generally, you can factor out parts of your game state and behaviour into smaller objects that handle a subset of the overall state in a coherent way. Each of these objects will have some of its own state which determines its behaviour but it's easy to manage since it's isolated from the other objects. Resist the urge to allow your gamestate object or main loop to become a dumping ground for pseudo-globals and factor that stuff out!

Generally speaking I never need to go any further than this when it comes to special-case substates, so I don't think there is a risk of it 'quickly getting out of hand'.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Yes, I imagine there's a line between going all-out with states and just using a bool/enums when appropriate. But knowing my pedantic tendencies, I'm probably going to end up making almost every state its own class. \$\endgroup\$
    – vargonian
    Commented Jun 7, 2011 at 7:02
  • \$\begingroup\$ You make it sound like a class is more correct than the alternatives, but remember that is subjective. If you start creating too many small classes for things that can be represented more easily by other language constructs then it can obscure the intent of the code. \$\endgroup\$
    – Kylotan
    Commented Jun 7, 2011 at 10:39
2
\$\begingroup\$

http://www.ai-junkie.com/architecture/state_driven/tut_state1.html is a lovely tutorial to game state management! You can use it either for game entities or for a menu system like above.

He starts teaching about the State Design Pattern, and then goes on to implement a State Machine, and successively extends it further and further. It's a very good read! Will give you a solid understanding of how the whole concept works and how to apply it to new types of problems!

\$\endgroup\$
1
\$\begingroup\$

I try not to use state machine and booleans for this purpose, because both are not scalable. Both turn into mess when number of states grow.

I usually design the gameplay as a sequence of actions and consequences, any game state just come naturally without a need to define it separately.

For example in your case with disabling player input: you have some user input handler and some ingame visual indication that input is disabled, you should make them a one object or component, so to disable input you just disable the whole object, no need to synchronize them in some state machine or react to some boolean indicator.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .