✚ Game development

Finite state machines: giving enemies their behaviour

How to use finite state machines for enemy AI: states, transitions, enter and exit actions, perception, hysteresis and hierarchical state machines.

Many video game enemies seem intelligent while following very simple rules: they patrol, notice the player, chase them, attack, flee when wounded. The most widely used tool for organising these behaviours is the finite state machine (FSM): a model in which the entity is always in exactly one of a limited number of states, and moves from one to another according to precise conditions.

The elements of a state machine

  • States: the modes of behaviour, such as «patrol», «chase», «attack», «flee».
  • Transitions: the rules that lead from one state to another, for example «if it sees the player, switch to chase».
  • Actions: what the enemy does while in a state, on every update.

An example for a guard:

State Behaviour Transitions
Patrol Walks between set points Sees the player → Chase
Chase Moves towards the player Within attack range → Attack; loses the player → Search
Attack Strikes at intervals Player far away → Chase; low health → Flee
Search Goes to the last known position Sees the player → Chase; time up → Patrol
Flee Moves away from the player Health recovered → Patrol

Drawing this table, or a diagram of circles and arrows, before writing any code helps uncover forgotten cases: what does the guard do if it is hit from behind while patrolling?

The simplest version

For a few states, a variable and a switch statement are enough:

switch (state) {
  case PATROL:
    move_towards(next_point)
    if (sees_player()) state = CHASE
    break
  case CHASE:
    move_towards(player)
    if (distance(player) < range) state = ATTACK
    else if (!sees_player()) state = SEARCH
    break
  ...
}

It is readable and fast. The limit appears as states multiply: the block grows long and the operations to perform when entering or leaving a state get scattered.

Enter, update, exit

A more robust structure treats each state as an object with three methods:

  • enter(): run once on entry, for example to start the animation, play an alarm sound or reset a timer;
  • update(dt): run on every step, it holds the behaviour and checks transitions;
  • exit(): run on exit, to clean up, for example stopping a looping sound effect.

The machine simply calls exit() on the current state, replaces it and calls enter() on the new one. That way every state is self-contained, testable and reusable across different enemies.

Perception: seeing and hearing

Transitions depend on what the enemy «perceives». The most common checks:

  • distance: is the player within a certain radius?
  • view cone: is the angle between the gaze direction and the direction to the player less than half the cone's width? It is computed with the dot product of normalised vectors.
  • line of sight: does a ray from the enemy to the player hit walls? This is checked with a raycast against the map.
  • hearing: noises such as gunshots or running footsteps generate events with a radius within which enemies react.

These checks can be expensive. There is no need to run them every frame: doing them a few times per second, spreading enemies across different frames, is almost always enough and also makes behaviour more human, with a small reaction time.

Avoiding oscillation

A typical problem: the player is exactly at the edge of attack range and the enemy flips between attack and chase every frame. Two remedies:

  • hysteresis: different thresholds for entering and leaving, for example enter attack below 2 metres and leave above 2.5;
  • minimum duration: a state cannot be left before a certain time has passed.

Hierarchical state machines

When an enemy has many states, some share transitions. For example, from any combat state, if health drops below a threshold, it must flee. Repeating the same transition everywhere is awkward and fragile.

A hierarchical state machine groups states into superstates. The «combat» superstate contains «chase», «attack» and «dodge», and defines the transition to «flee» just once. The inner states inherit the shared behaviour.

When to move on

State machines work very well for enemies with clear behaviours and a reasonable number of states. When combinations explode, or you want to give dynamic priorities to many goals, alternatives such as behavior trees or utility-based systems are worth considering. Often, though, a well-designed FSM is all you need, and its predictability is a virtue: players learn enemy behaviours and exploit them, which is part of the fun.

In short

  • A finite state machine keeps the enemy in one state at a time, with explicit transitions.
  • A table or diagram of states helps uncover forgotten cases.
  • Enter, update and exit methods make each state self-contained.
  • Perception by distance, view cone and line of sight drives transitions.
  • Hysteresis and hierarchical states prevent oscillation and duplication.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also