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, CRE341 Wk 3.r1, see source-cre341-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
[Idle] --enemy detected--> [Attack]
[Attack] --enemy lost------> [Idle]
[Attack] --low health------> [Flee]
[Flee] ---safe environment-> [Idle]
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, CRE341 Wk 3.r1):
| 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
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-cre341-lectures