Finite-State Machines: How Games Simulate an Opponent's Decisions

How a seven-state finite-state machine (Idle, Patrol, Alert, Chase, Attack, Retreat, Die) turns a handful of if-statements into an enemy that feels like it is deciding what to do next.

▶ Open the simulation

What a finite-state machine actually is

A finite-state machine (FSM) is one of the oldest and most dependable tools for simulating behaviour: an entity can only be in exactly one of a fixed set of named states at any moment, and it moves between states only along explicit, pre-defined transitions. There is no hidden middle ground and no ambiguity about what the entity is currently doing — it is deterministic by construction, which is exactly why FSMs remain the default choice for real-time NPC behaviour, traffic-light controllers, elevator logic, and vending machines alike.

A concrete Unity prototype (an enemy combat AI built for a third-person action game) makes the idea tangible with seven states: Idle, Patrol, Alert, Chase, Attack, Retreat and Die. Each state owns three behaviours — what happens on entry, what happens every frame while active, and what happens on exit — and the controller stores only a single current-state variable plus a small amount of context (current target, last known position, a state timer).

The seven-state enemy loop

The transition logic maps closely onto how a cautious opponent actually behaves:

  • Idle → Patrol: after a short pause, the enemy resumes a scripted patrol route between waypoints.
  • Patrol → Alert: a target enters detection range or line of sight; the enemy stops and turns toward the disturbance rather than reacting instantly, which is what makes it read as noticing rather than teleporting into combat.
  • Alert → Chase: after confirming the target for a short window, the enemy commits and starts closing the distance using a navigation-mesh path.
  • Chase → Attack: once within weapon range and off cooldown, the enemy stops moving and fires.
  • Attack → Retreat: if health drops below a threshold, the state machine interrupts combat and moves the enemy to a fallback position instead of fighting to the death on the spot.
  • Any state → Die: health reaching zero short-circuits every other transition and locks the machine into a terminal state.

Note that transitions are conditional on simple, cheap checks — distance, line-of-sight, a timer, a health percentage — not on complex planning. That cheapness is the entire point: an FSM can evaluate dozens of agents per frame without becoming the performance bottleneck of the simulation.

Why not just use one big script?

The alternative to an FSM is a monolithic Update() method full of nested if/else blocks checking every possible condition every frame. That works for a handful of behaviours, but it degrades badly: conditions start interacting in ways nobody predicted, a fix for one bug reopens another, and reasoning about "what can happen from here" requires reading the entire function. An explicit FSM constrains the reachable behaviour space — from Attack, the only legal next states are Retreat and Die, full stop. That constraint is a debugging superpower: if an enemy is behaving oddly, you only need to inspect the current state and its defined transitions, not the whole codebase.

The pattern generalises far beyond games. A finite-state machine is the natural model whenever a system has a small number of qualitatively distinct modes and well-defined rules for moving between them: a traffic-signal controller cycling red/amber/green, a washing-machine cycle, a network connection's handshake states, or a simplified epidemiological model moving individuals between susceptible, infected and recovered states. Any simulation that can be described as "the system is in exactly one of these states, and here is what causes it to change" is a candidate for an FSM.

Where finite-state machines run out of road

FSMs scale badly once the number of states or transition rules grows large, because the transition table grows roughly with the square of the state count — every state can potentially transition to every other state, and someone has to specify (or rule out) each pairing. Games with genuinely complex reasoning — "should I flank, call for backup, or fall back and heal?" — usually graduate to a behaviour tree, which composes small reusable decision nodes hierarchically instead of enumerating every state pair, or to a utility system, which scores each possible action by a weighted combination of factors and picks the highest-scoring one each tick. Both are more expressive, and both are also more expensive to reason about and debug, which is why production games often mix approaches: an FSM for the outer loop (alive/dead, in-combat/out-of-combat) with a behaviour tree or utility system nested inside the "in-combat" state to choose tactics.

The lesson for anyone building a simulation is to match the tool to the branching factor: if the entity you are modelling genuinely only has a handful of modes and clear triggers between them, an FSM is simpler, faster, and easier to verify than anything more elaborate — reach for a heavier tool only once the state table itself becomes the bug.

Frequently Asked Questions

Is a finite-state machine the same as a behaviour tree?

No. An FSM stores one current state and a fixed transition table between states, while a behaviour tree evaluates a hierarchy of condition and action nodes from the root every tick. Behaviour trees compose more easily as complexity grows, but FSMs are cheaper to compute and far easier to visualise and debug for a small, well-understood set of modes.

Why use a NavMeshAgent alongside an FSM instead of writing custom movement code?

A navigation-mesh agent handles pathfinding around obstacles and terrain, which is a separate problem from deciding what the agent should be doing. Separating the two means the FSM only ever has to say 'move toward this point' or 'stop moving', while the pathfinding system worries about how to actually get there.

Can a finite-state machine model more than one entity's behaviour at once?

Each agent typically runs its own independent FSM instance, and a simulation with many agents (enemy spawners, crowd systems, traffic models) just runs many small state machines in parallel. That per-agent independence, combined with the low per-tick cost of an FSM, is exactly why the pattern scales to hundreds of agents in real time.

What triggers the transition to a Retreat state instead of just dying?

A health-percentage threshold, typically checked once per tick while in the Attack or Chase states. Once current health falls under that threshold, the transition overrides whatever else was happening, which produces the readable, non-suicidal behaviour of an opponent that disengages before it is destroyed.

What did you find?

Add reproduction steps (optional)