Controlling Gamestate

Virtually all programs have this, a central ‘game’ loop that grabs user input, updates game logic, redraws the screen. At it’s simplest possible level, it usually looks something like this:

repeat
   Input;
   Update;
   Redraw;
until Quit;

The problem mainly comes in that middle section, Update. For any game, there are a variety of different states you can be in (main menu, options screen, credits, playing game) and for most games, “playing game” breaks down into a whole lot more states by itself. For my simple 500 Card Game, I had states “bidding”, “dealing”, “choosing kitty”, as well as playing. If you’re not careful, your Update procedure can end up looking like this:

Procedure Update;
begin
   case State of
      STATE_MENU:
         // wait for a menu click
      STATE_OPTIONS:
         // handle user input
      STATE_CREDITS:
         // roll some credits
      STATE_GAME:
         case GameState of
            GS_PLAYING:
               // handle user input
               // update some monsters
            // etc
         end;
      // STATE_ even more stuff:
   end;
end;

So far, so big and clunky. It gets worse when you have to handle nested state transitions: in my card game, someone could be playing, then go to the Menu and come back. So I had to have a variable ‘LastState’ that remembered where they were, so they could go back to it. But it’s worse than that, because they might go to the Menu, then go to the Options screen: and when they came back, they’d still want their current game to not be lost! My card game was simple, so I managed this as simply as I could: when entering the ‘menu’ state, the menu grabbed it’s own copy of LastState, and it restored this value when the other screens (Options, Credits etc) returned to it, so there was always a way back to the game. This was the simplest case, because every state had exactly one other state that it could be transitioned from (you couldn’t get to Options from Game without going through Menu).

For Icefall though, the stakes were raised. I wanted the Options screen accessible from in-game, as well as in the initial menu. And there are many more states that the game can be in, and some of them should not be returned to (e.g the “create character” screen), so rather than a gigantic and ugly Update loop, I went back to good old OOP and implemented a class-based state stack.

Icefall has a central “State Engine” that looks after the current state stack, handles transitions between them, and calls whichever state is ‘on top’ during the main loop. The State class looks something like this:

TIceState = class (TIceClass)
   constructor Create
      (StateEngine: TIceStateEngine; 
       Transition: TStateTransition);
   procedure OnEnter; virtual;
   procedure OnLeave; virtual;
   procedure Update; virtual;
   procedure Redraw;
   // other useful state methods
   destructor Destroy; override;
end;

Essentially, for every state the game can be in, I have a corresponding class that defines it’s specific behaviour in Update. I can also do other ‘maintenence’ (e.g. ensure the right music is playing) by overriding the OnEnter and OnLeave procedures. The game can change states by constructing a new TIceState instance, passing it a reference to the state engine and describing the type of transition to use (e.g. whether to nest the state [for a trip to a Menu] or just progress to the new state with no way back [like a character create screen]). You can also leave a state just by destructing the class instance: the state engine will pop back to the next highest state on the state stack, or quit the game if there are no states left (e.g., you just hit ‘Exit’ from the Main Menu). 

The state engine takes care of nested states and transitions, and someone’s exact state in the game can be saved (if needed) by storing the entire state stack: while I’m unlikely to want the game to be saved while on the Character Create screen, it’s good to know I can safely save the game while in shops, or at any other point, without even worrying about special handling code to cover all the situations.

Another interesting thing I have done is move the game’s “loader” (the part that loads textures, sounds, music, fonts, databases) into it’s own state, and just made it the very first state that’s pushed onto the stack, and it takes a parameter to the state to transition to when it’s done. It takes the whole ‘loading’ step out of the main game code, which makes that much simpler, and I can make the loader just load the resources I’m going to need right now: some other state (like the credits) can always call it again later to load specific credit-y images.

I can also use the the same game engine to build other tools like the Map Editor: the only difference between the map editor and the game itself is that it compiles in a different set of states: when the map editor has loaded, it launches itself into a TMapEditorState instead of a TMainMenuState.

Overall, having a state stack is very flexible, quite simple to use and maintain, and overall a vast improvement from the ‘giant case statement’ I was using previously.

2 thoughts on “Controlling Gamestate

  1. sounds pretty good, is this how states are commonly handled in games? are there any problems with this method?

  2. There’s a few different ways: ‘giant case statement’, ‘distributed functions’, ‘state stack’ and ‘cooperative co-routines’ (in rough popularity order) are probably the main four.

    I think state stack is the best choice (for turn-based RPGs at any rate), no real downside to it if you’re already using classes for everything. The type of game, the language you’re using (and what aspects of your language you’re most comfortable with) and the architecture (single, multithreaded? using XYZ game engine?) may influence your choice – e.g. if you’re running multithreaded, cooperative co-routines become a lot more attractive.

Leave a Reply

Your email address will not be published. Required fields are marked *