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.
| Approach | Adding a state | Testing a state | Transitions defined by |
|---|---|---|---|
switch on enum | Modify existing switch | Hard to isolate | Centralised in switch |
| State pattern | New class, no edits to machine | Each class independently | The 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)
OOP benefits
The pattern relies on three OOP principles (Prof Charles, course lecture series):
| Principle | How it applies |
|---|---|
| Encapsulation | Each state contains its own behaviour and transition logic |
| Inheritance / Interface | All states implement IState, enforcing a uniform API |
| Polymorphism | The 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
Related
- game-ai-agent-design — agent architectures, Behaviour Trees, GOAP
- unity-animator-scripting — Unity’s animation state machine (separate concern from AI logic)
- csharp-interfaces —
IStaterelies on C# interface contracts - csharp-inheritance — alternative: abstract base class
Statewith virtual methods - monobehaviour-lifecycle — the machine’s
Update()drives state execution each frame - source-game-ai-lectures