Craft·5 min read

The One Variable That Runs the Whole Game

Our platformer has eight states and one variable holding which one it is in. Every bug we did not have is because of that.

The opening position in Fighting Chess, with the two armies facing each other across a wooden board.

Our platformer has one variable that decides what the entire game is doing. It holds one of eight words: booting, loading, the title screen, a level card, playing, the help screen, won, or game over.

Almost every question the code asks is downstream of that variable, and almost every category of bug it does not have is because that variable exists.

The problem with flags

The alternative, and the thing everyone writes first, is a set of separate true-or-false variables. Is the game started. Is it paused. Is the player dead. Is the level finished. Is the help screen open.

Five of those allow thirty-two combinations, and perhaps six of them mean anything. The rest are nonsense the code has to be defended against: paused and dead at once, help open while the level is finishing, started but also on the title screen.

The bugs this produces are the worst kind. They are rare, because they need a specific sequence of events. They are hard to reproduce, because that sequence is usually something like pressing pause while a level completes. And they present as something visually absurd, like two interfaces on screen simultaneously, which makes them look like a rendering problem rather than a logic one.

One variable holding one of eight words cannot enter an impossible combination. Not because it is checked, but because there is nowhere for the impossible combination to live.

Transitions are where things happen

The second half of the idea, and the more useful one, is that all the work goes into the moment of changing state rather than into the states themselves.

There is one function that changes it, and everything that must happen exactly once on entering a mode happens there. In our build: the interface is cleared on every transition, so no screen can leave a button behind. Touch controls are shown only when entering play. Entering the loading screen starts the tape screech. Entering a level card resets the level. Entering the win state fires the confetti, plays the birthday tune and records the score.

Because that is the only route in, none of those can happen twice and none can be forgotten. The confetti fires once because there is one line that fires it and it is on the edge into that state.

There is a concrete bug this fixed. The touch controls used to be visible for the whole session, which meant they sat on top of the title screen's own buttons and made them unpressable on a phone. The fix was one line in the transition: show them when entering play, hide them otherwise. If the visibility had been decided in five different places, it would have been five fixes and a sixth place somebody missed.

The opening position in Fighting Chess, with the two armies facing each other across a wooden board.
Choosing a piece, choosing a square, watching a fight and waiting for the opponent are four different states of the same board.

What each state changes

Once the variable exists, the rest of the game gets simpler rather than more complicated, because every part of it can ask one question.

The loop only advances the world while playing. On the title screen it still draws, which is why the title screen can animate. Paused draws but does not update, which is the entire implementation of pausing and it is one condition.

Input means different things. Space is jump while playing and start on the title screen. The same key, two behaviours, no ambiguity, because the state decides which.

Drawing is a straightforward branch on the state, and each branch knows exactly what belongs on screen because nothing else can be true at the same time.

The other kind of state machine

The same idea appears at a smaller scale inside a single character, and it is worth separating the two because they are often confused.

A character is idle, or walking, or in the air. Those are states too, with rules about which can follow which: you can go from walking to airborne, and from airborne to idle on landing. The drawing code picks a sprite from that state rather than working it out from velocities each frame.

Our chess build has a third example again: choosing a piece, choosing a destination, watching a capture play out, and waiting for the opponent to think. Clicks mean different things in each, and a click during a fight animation has to do nothing at all rather than start a second move.

Same structure, three different scales. It is one of the few patterns in game code that keeps being the right answer.

Where it stops being enough

To be fair about the limits, because "use a state machine" is not universal advice.

It handles things that are exclusive: you are either playing or paused, never both. It handles things that are simultaneous badly, and forcing them into it produces combinatorial names like playing-while-invulnerable-and-underwater, which is the flags problem wearing a disguise.

The rule of thumb we use is that the big mode of the game is a state, and the small independent facts about a character are separate values alongside it. Invulnerability after being hit is a countdown, not a state. Which way the character is facing is a number, not a state. Only the exclusive things get to be states.

What this means for a commission

It is invisible and it is why the interface behaves. Screens that clear properly, buttons that appear when they should, a pause that pauses.

It is also what makes a game testable. A test can put the game into a known state and assert what happens next, which is a large part of proving a game is actually finished.

And it is where the polish hangs. Level cards, confetti, a tune on winning: all of it lives on a transition, which is why adding a flourish to a well-structured game is an hour and adding it to a badly structured one is a fortnight.

◆ Questions

Common questions

What is a state machine in a game?+

A single variable holding which mode the game is currently in, plus one function that is the only thing allowed to change it. Title, loading, playing, paused and finished are states, and the game behaves completely differently in each.

Why not just use several true or false flags?+

Because flags allow combinations that make no sense. With separate booleans for playing and paused and finished, the game can be in all three at once, and eventually it will be. One variable can only hold one value.

What goes in a state transition?+

Everything that must happen exactly once on entering that mode. Clearing the interface, starting a sound, resetting a timer, firing confetti. Putting it in the transition means it cannot happen twice or be forgotten.

Does a small game need one?+

Any game with a title screen and a game over screen already has states, whether or not it has named them. Naming them is what stops the interface from two different screens appearing at once.

How does this relate to pausing?+

Pausing is a state, and it is why a paused game can keep drawing without updating. It is the clearest example of the same screen behaving differently depending on which mode it is in.

  • craft
  • code
  • fundamentals
◆ Keep reading

Related from the journal.

The bedroom level of the Tape Loader birthday game, with the player mid-run between shelves.
Craft·5 min read

Reading the Controls Properly

A game does not respond to a key being pressed. It asks, every frame, which keys are currently down. The difference is everything.

Read