Finite State Machines (actor states)
A small set of named, mutually-exclusive states (idle, walk, jump, attack, hurt, dead) with explicit transitions, used to drive both Claw’s controller and every enemy’s AI.
Why it matters
“Is Claw allowed to swing his sword right now?” is a state question. Encoding it as a pile of booleans (isJumping, isDucking, isHurt) creates illegal combinations — ducking and jumping — and tangled if chains that nobody can extend. A finite state machine makes each behaviour exactly one state, makes transitions explicit, and guarantees the actor is always in exactly one. This is the practical face of automata-and-formal-languages and the logic core of player-controller-claw-movement and enemy-ai-behaviours.
How it works
The FSM is an object owned by the actor’s logic component (see component-logic-separation):
- One current state. The component holds a
currentState; each state definesOnEnter,OnUpdate(dt),OnExit. On a transition:cur.OnExit(), swap,next.OnEnter()—OnEnteris where you kick the animation clip and where one-shot setup belongs. - Transitions are guarded edges. A state’s
OnUpdateevaluates conditions and returns the next state id (or stays). The legal edges are the design:
| From | Event/condition | To |
|---|---|---|
| Idle | move pressed | Walk |
| Walk / Idle | jump + grounded | Jump |
| Jump | vy>0 and grounded | Idle |
| any (not Dead) | hp⇐0 | Dead |
| Idle / Walk | attack pressed | Attack |
- Hierarchical states for “any”. “Take damage from any non-dead state” is a super-state edge so you don’t duplicate the transition on every leaf. Keeps the table small.
- Enemy AI reuses the machine. A rat is
Patrol → Chase → Attack → Retreat, driven by distance-to-Claw checks in eachOnUpdate— same FSM engine, different states. See enemy-ai-behaviours. - Event-driven entry. Many transitions fire off events (an
ActorHurtEventforces→ Hurt) rather than per-frame polling, tying the FSM into the engine’s event bus.
Example
Claw’s grounded-attack guard, expressed as a transition rather than scattered flags:
state Walk:
OnUpdate(dt):
if !grounded: return Jump
if attackPressed: return Attack // only reachable while grounded
if !movePressed: return Idle
move(dir); return Walk
state Attack:
OnEnter: play "SWORD" clip; lock movement
OnUpdate: if clipFinished: return movePressed ? Walk : IdleBecause Attack is only reachable from Walk/Idle, “attacking mid-air” is structurally impossible — no boolean can put the actor in an illegal combination.
Pitfalls
- Missing
OnExit. Forgetting to clear movement-lock or a timer on exit leaks state into the next behaviour (Claw stays frozen after Attack). Pair everyOnEntersetup withOnExitcleanup. - Boolean soup beside the FSM. Keeping
isHurtand a Hurt state lets them disagree; the state is the single source of truth. - Animation/state drift. Driving the clip outside the FSM means the sprite and logic desync; start the clip in
OnEnteronly. - Unreachable / dead-end states. A state with no outgoing edge traps the actor (a Hurt with no path back to Idle freezes it); audit the transition table for sinks.