State Machines.

Games are complex pieces of software. Several things are usually occurring at once on the screen and they all need their state tracking. A relatively new coder trying to write a game will invariably produce blobby, spaghetti code. This kind of code is problematic:

  1. It's hard to extend.
  2. It's hard to reason about.
  3. It's hard to debug.

How can we write better code? Know how to use state machines and you're 80% of the way there.

State machines are great for pulling the high level logic, that exists in your game, out of the murky swamp of your coding and forcing it into the light, so it's more explicit.

Bob, Journeyman Coders Problem

Bob is a newish programmer and he's making a platformer. His code might look as below.

bool _isFalling = false;
bool _isJumping = false;
bool _isInvunerable = false;
bool _isRunning = false;
bool _isWalking = false;
bool _isStanding = true;
float _speed = 0.0f;
const float FallSpeed = 10.0f; // 10m/s
const float JumpSpeed = 20.0f; // 20m/s
const float JumpAftertouchSpeed = 5.0f;
Vector2 _pos;

void Update(float dt)
{
    if(_isFalling)
    {
        float floorY = GetFloorY(pos);
        pos.y -= FallSpeed * dt;
        pos.y = math.max(floorY, pos.y)
        if(pos.y == floorY)
        {
            _isFalling = false;
            _isStanding = true;
        }
        return;
    }

    if(_isJumping)
    {
        float ceilingY = GetCeiling(pos);
        pos.y += JumpSpeed * dt;
        pos.y = math.min(ceilingY, pos.y)

        if(pos.y == ceiling)
        {
            _isJumping = false;
            _isFalling = true;
        }

        // Aftertouch
        if(_leftIsPressed)
        {
            HandleMove(-1, JumpAfterTouchSpeed * dt);
        }
        else(_rightIsPressed)
        {
            HandleMove(1, JumpAfterTouchSpeed * dt);
        }
        return;
    }

    if(_isStanding) { /* blah blah */ }

    /* and so on */
}

This code has the problems mentioned in the introduction.

  • Hard to reason about - Are there any times when two boolean flags get set that shouldn't? Like walking and falling set to true? The code is really long but at least it's all in the same place, the flow isn't terrible but it's a lot to parse as a human. It's not even doing half of what a real game would have to do - animations, sound effects, dynamic collision detection etc) so it's all going to get longer and hairier. (Braid Creator Jonathan Blow is in favor of longer functions with less function calls because, I assume, all the context you need to read the code is right there infront of you. It's worth considering.)
  • Hard to extend We should be able to double jump, including from a fall. We should slide if the platform is slanted. See how quickly this code will become messy and increasingly hard to read? Diagonal jumping from running, walking and standing please.
  • Hard to debug One of the values gets the wrong number at some point but where? It's like finding a needle in a haystack

Alice's solution using a state machine.

Bob is trying to add a new feature but he's having trouble keeping track of what's happening so asks Alice to come over. Alice suggests refactoring his current code using a state machine. This produces a few more files but they're short, simple to understand, easy to extend and debug.

StateMachine _stateMachine = new StateMachine();
bool _isInvunerable = false;
Vector2 _pos;
float _speed;

void Init()
{
    _stateMachine:Add("move", new MoveState(this));
    _stateMachine:Add("stand", new StandState(this));
    _stateMachine:Add("jump", new JumpState(this));
    _stateMachine:Add("fall", new FallState(this));

    _stateMachine:Change("stand")
}

void Update(float dt)
{
    _stateMachine:Update(dt)
}

void HandleInput()
{
    _stateMachine:HandleInput()
}

Alice's version of the code has broken everything up in state chunks and these are managed by a state machine. At a glance it's easier to see what's happening at a high level. The character has four states and each updates itself and manages the transitions to other states. Let's briefly consider the FallState.

public class FallState : IState
{
    public const FallSpeed = 10.0f; // m/s

    StateMachine _stateMachine;
    Character _character;

    public FallState(Character char)
    {
        _character = char;
        _stateMachine = char.StateMachine;
    }

    public Enter(params object[] args)
    {
        _character.Speed = FallSpeed;
    }

    public void Update(float dt)
    {
        Vector2 pos = _character.pos;
        float floorY = GetFloorY(pos);
        pos.y -= FallSpeed * dt;
        pos.y = math.max(floorY, pos.y)

        _character.pos = pos;

        if(pos.y == floorY)
        {
            _stateMachine:Change("stand");
        }
    }

    public void Exit() {}
    public void HandleInput() {}
}

This FallState is only concerned with making the character fall. There's no chance of it accidentally affecting other states variables, or running their code, it's clear and distinct. It would be easy to extend the fall state with an optional double jump interaction, aftertouch, etc. If there's a bug with falling in the game, it's probably in the few lines of code that make up this state.

Alice has saved the day. Together she and Bob can move all the code over to a state machine and then extend cleanly. This way we achieve a nice seperation of concerns for each state of the character.

Jumping in Castlevania.

I don't know if Castlevania uses state machines but if it did it might look like the illustration above.

State Machine Implementation

Hopefully you have an idea how the state machine is used to organize code flow and separation. Next let's look at a concrete useable implementation. In this case I'm going to write a StateMachine class in C# but the concept is easy to translate to other languages. Even in C# you might like to customize the exact details of the implementation, depending on what codebase you're working with.

Any state that's used by the state machine needs to implement a certain number of functions. In C# the best way to do this is with an interface.

public interface IState
{
    void Update(float dt);
    void HandleInput();

    void Enter(params object[] args);
    void Exit();
}

The Update and HandleInput functions get called every frame when the state is active. Update is expected to update the inner workings of the state, for instance updating a character falling until he lands. HandleInput checks if the player has pressed any buttons and responds appropriately for the state. In our example if the character is in the stand state and the player presses the left button then that causes a transition to the MoveState.

The StateMachine class stores the states and has functions to move between them and update them. Here's the code.

public class EmptyState : IState
{
    public void Update(float dt) {}
    public void HandleInput() {}
    public void Enter(params object[] args) {}
    public void Exit() {}
}

public class StateMachine
{
    Dictionary<string, IState> _stateDict
        = new Dictionary<string, IState>();
    IState _current = new EmptyState();

    public IState Current { get { return _current; } }
    public void Add(string id, IState state) { _stateDict.Add(id, state); }
    public void Remove(string id) { _stateDict.Remove(id); }
    public void Clear() { _stateDict.Clear(); }


    public void Change(string id, params object[] args)
    {
        _current.Exit();
        IState next = _stateDict[id];
        next.Enter(args);
        _current = next;
    }

    public void Update(dt)
    {
        _current.Update(dt)
    }

    public void HandleInput()
    {
        _current.HandleInput();
    }
}

Rather than deal with a lot null checks we just create an EmptyState to begin with. Any StateMachine created runs the EmptyState until it's changed.

Sources

Uses

Statemachines are a tool that can be used in many, many places. Here's a list of the more common uses.

AI

State machines work quite well for AI. They're great for simple behavior like pre-set patterns. For instance 2d patrolling behavior

[walk_left] <-> [walk_right]

Or set behaviors

[wait] -> [attack] -> [cool_off] -> [wait]

These are simple but behaviors can be far more complicated.

[patrol] -> [chase] -> [attack]
                    -> [patrol]
                    -> [special_attack]

Each state in the machine represents some action an entity in the game is taking. Code to handle transitions between states usually exists outside the state machine.

When AI gets very complicated and requires a lot of state and control code, it's better to progress to a behavior tree (or some other method entirely depending on the specific requirements of the project).

Menus

State machines are great for representing menu screens. Each screen in the menu is represent by it's own state. The front end menu might look a little like this.

[start_menu]    - new game -> [inner_game]
                - contine -> [choose_save] -> [inner_game]
                - options -> [options]  -> [controls]
                                        -> [tutorial]
                                        -> [sound]
                                        -> [credits]

Then an equally complicated state machine would exist for the in-game menus such as the inventory, quest log and so on.

These states often share a lot of code. Don't duplicate the code, also resist inheritance because it makes things less explicit. Instead favour composable objects.

Game Entities

General non-enemy entities are useful to control with a state machine.

  • Bomb
  • Door (especially with opening effects)
  • Hidden Passage
  • Repairable bridge
  • Elevators
  • Traps (crushing devices, trapdoors, darts etc)
  • Chests

Consider a bomb, depending on the complexity it could have a number of states.

[inactive] -> [count_down] -> [explode] -> [exploded]

The bomb might activate automatically after X seconds but it could also trigger immediately if some creature approaches it. States work great to break out that kind of logic.

UI

Simple user interface can be coded directly but when fades, translations, scales, sound effects, particles effect etc are incorporated the UI elements become stateful and the complexity is tamed best with a state machine. If coded cleverly UI code should be able to be interrupted and reversed, so it's more responsive to the state in the game.

Tracking Game State

If progress in your game is linear, or pretty linear a single field will do fine. Such as

current_level = 0 -- increment after each level

If you're making an RPG, it can become hard to track all the data involved in quest progression. Quests can be converted into state machines, to make them more explicit and less error prone. I've got a lot more to say about this, too much for this post so perhaps in a future blog post! Here I'll just flag it up as something you might want to consider.

## Next Steps

Experiment with a state machine and see if they work for you. A good jumping off point might be the menu system as that tends to be one of the simplest uses. Then you'll get a better feel for how their use and can try them out to see if they work for you in other places.