Summary

The AI state machine pattern applies the Gang of Four State design pattern to NPC/agent behaviour. Each behaviour (Idle, Attack, Flee, Patrol) is encapsulated in its own class. A central StateMachine holds a reference to the current state and delegates to it — the current state runs its logic and decides whether to transition to another state. This gives you a modular, testable, extendable AI architecture. It remains one of the core teaching architectures in classic game-AI texts. (Millington, Artificial Intelligence for Games, see source-artificial-intelligence-for-games; Buckland, Programming Game AI by Example, see source-programming-game-ai-by-example)

(Prof Charles, course lecture series, see source-game-ai-lectures)


Why not a switch statement?

The naive approach — a large switch on a currentState enum — works until you need to add states. Adding a new state means editing the switch, risking regressions in existing branches. The State pattern instead puts each behaviour in its own class: new states are new classes, not new branches.

ApproachAdding a stateTesting a stateTransitions defined by
switch on enumModify existing switchHard to isolateCentralised in switch
State patternNew class, no edits to machineEach class independentlyThe state class itself

Implementation

The interface

public interface IState
{
    void Enter(StateMachine context);
    void Update(StateMachine context);
    void Exit(StateMachine context);
}

Enter and Exit allow setup/teardown when a state is activated or left — play an animation, reset a timer, etc.

The state machine

public class StateMachine : MonoBehaviour
{
    IState currentState;
 
    public void SetState(IState newState)
    {
        currentState?.Exit(this);
        currentState = newState;
        currentState.Enter(this);
    }
 
    void Update()
    {
        currentState?.Update(this);
    }
}

The machine holds no behaviour itself. It delegates entirely to currentState.

Concrete states — Unity example

public class IdleState : IState
{
    public void Enter(StateMachine ctx)  { /* play idle animation */ }
    public void Exit(StateMachine ctx)   { }
 
    public void Update(StateMachine ctx)
    {
        if (ctx.CanSeeEnemy())
            ctx.SetState(new AttackState());
    }
}
 
public class AttackState : IState
{
    public void Enter(StateMachine ctx)  { /* play attack animation */ }
    public void Exit(StateMachine ctx)   { }
 
    public void Update(StateMachine ctx)
    {
        if (!ctx.CanSeeEnemy())
            ctx.SetState(new IdleState());
        else if (ctx.HealthLow())
            ctx.SetState(new FleeState());
        else
            ctx.AttackTarget();
    }
}
 
public class FleeState : IState
{
    public void Enter(StateMachine ctx)  { /* play flee animation */ }
    public void Exit(StateMachine ctx)   { }
 
    public void Update(StateMachine ctx)
    {
        ctx.MoveAwayFromEnemy();
        if (ctx.IsSafe())
            ctx.SetState(new IdleState());
    }
}

State transition diagram

The three states map directly onto the concrete classes above: IdleState transitions to AttackState on CanSeeEnemy(), AttackState returns to idle when the enemy is lost or drops to FleeState on low health, and FleeState returns to idle once IsSafe(). Each transition is decided inside the state’s own Update, never by the machine.


Trade-offs

Use this pattern when:

  • The agent has 3+ distinct behaviours with different update logic
  • You expect to add new behaviours during development
  • You want to test individual behaviours in isolation
  • Transitions have meaningful side effects (animation changes, sound cues)

Alternatives to consider:

  • Behaviour Trees (see game-ai-agent-design) — better for complex hierarchical decisions and reactive priority systems; more composable than state machines but heavier to implement
  • Simple enum + switch — acceptable for 2–3 states with simple logic and no anticipated extension
  • Unity Animator Controller — handles animation state machines but is not general AI logic (see unity-animator-scripting)
  • A planner above a small FSM — goal-oriented-action-planning chooses a sequence of actions, and each action drives a very small state machine (move, play animation)

For states that are not AI (a player controller’s grounded, jumping and dashing states, or a door’s open and locked states), the same idea is covered in state-pattern.


OOP benefits

The pattern relies on three OOP principles (Prof Charles, course lecture series):

PrincipleHow it applies
EncapsulationEach state contains its own behaviour and transition logic
Inheritance / InterfaceAll states implement IState, enforcing a uniform API
PolymorphismThe machine calls currentState.Update() without knowing which state it is

This means states can be:

  • Unit tested independently (inject a mock StateMachine)
  • Added without touching the machine or other states
  • Combined across different game objects with different machine configurations

Worked example

A ghost driven by a finite state machine in the Pac-Man-lite teaching example (see overview-arcade-classics-as-learning-projects):

  • Ghost.cs — Scatter, Chase, and Frightened states, each choosing a different movement target
  • PacManGame.cs — the timers and power pellets that switch every ghost between states

The ghost deliberately uses the lighter variant from the table above: a GhostState enum and a switch that picks a target, with three states and no plans to add more. Two details are worth noticing. First, the transitions are made from outside the ghost: PacManGame alternates Scatter and Chase on a timer and sets Frightened when a power pellet is eaten, so the states never decide their own exits. Second, SetState() has a side effect on entry, forcing the ghost to reverse, which is exactly the job an Enter() method does in the State pattern. The Practice task below asks you to feel the difference between the two styles.

Practice

Build a guard with the State pattern from this page, using a cube or sprite for the guard and another for the player.

  1. Create IState, StateMachine, IdleState, AttackState and FleeState as shown above.
  2. Implement the helper methods on StateMachine with simple fields: CanSeeEnemy() is true when the player is within 5 units, HealthLow() reads a health field you can edit in the Inspector, IsSafe() is true beyond 10 units, AttackTarget() logs “Attack!”, and MoveAwayFromEnemy() uses Vector3.MoveTowards away from the player.
  3. Log the state name in every Enter().
  4. Start in IdleState from Start().

Success test:

  • moving the player close logs AttackState entering, and moving it away logs IdleState
  • lowering health in the Inspector during an attack logs FleeState, and the guard backs off until it is safe
  • StateMachine.Update() contains no if or switch about states
  • every transition is written inside a state class

Extension:

  • add a PatrolState that walks between two points and replaces IdleState. Count the files you had to touch. Then open the Pac-Man-lite Ghost.cs and list every place you would edit to add an Eaten state (a ghost that runs home after being eaten). Compare the two counts.

Self-test

  1. What does the StateMachine class do in its own Update()?
  2. What are Enter() and Exit() for?
  3. Why is adding a state easier with the State pattern than with an enum and switch?
  4. When is the enum and switch version still a reasonable choice?
  5. In Pac-Man-lite, which script decides when a ghost changes state?
  6. Which wiki page covers the State pattern for things that are not AI, such as a player controller?

Answers

  1. Nothing about behaviour: it calls Update() on the current state and lets that state run its logic and decide transitions.
  2. Set-up and tidy-up when a state starts or stops, such as playing an animation or resetting a timer.
  3. A new state is a new class; the machine and the existing states do not need editing, except where another state transitions into it.
  4. For two or three states with simple logic and no expected extension, as with the Pac-Man-lite ghost.
  5. PacManGame, which runs the Scatter and Chase timers and sets Frightened when a power pellet is eaten.
  6. state-pattern.