Skip to content

Tutorial: Building a State Machine

StateMachine lets you describe behaviour as a set of named states connected by condition-driven transitions. No Update polling, no manual state tracking — each state defines its own running process and the transitions out of it. Built on the same Key + Process primitives you already use elsewhere.

By the end of this tutorial you'll have a door that opens on player approach, stays open for two seconds after the player walks away, then closes.

Prerequisites: Basic familiarity with the Process system, Conditions, and Events.


Mental Model

A StateMachine owns a list of State Nodes. Each node is:

  • A Key that identifies the state.
  • A State Process that runs while the state is active.
  • A list of Transitions that move the machine to another Key when their condition fires.
stateDiagram-v2
    [*] --> Closed
    Closed --> Opening : PlayerNear
    Opening --> Open : All Processes Done
    Open --> Closing : PlayerFar (after 2s)
    Closing --> Closed : All Processes Done

Transitions are subscription-based: when a state is entered, its transition listeners are set up. When the state exits, they're torn down. No polling.


1. Create the Keys

Each state is identified by a Key (a ScriptableObject). Create:

  • Create → Jungle → Data → Key named Closed.
  • Repeat for Opening, Open, Closing.

Keys are refactor-safe — renaming the asset doesn't break references.


2. Add the StateMachine

On the Door GameObject add a StateMachine component.

Add Component → Jungle → Processes → State Machine

Configure:

  • startingState — drag Closed Key in. (Falls back to the first state if left empty.)
  • states — empty for now; we'll add four.

3. Define Each State

Click + on states four times. For each entry:

Field What it is
State Value Pairs a Key with the State Process to run while the state is active.
Transitions Each transition has a condition and a target Key.

Closed

  • Key: Closed
  • State Process: leave empty or set to a NoOp — the door just sits. Optionally, an animation that locks the door in the closed pose.
  • Transitions:
  • On ConditionConditionAdapter wrapping a "Player Within Range" condition. Target Key: Opening.

Opening

  • Key: Opening
  • State Process: a TransformCommand or AnimationCommand that opens the door (animates rotation / position / animator state).
  • Transitions:
  • All Processes Done — fires when the state's process finishes naturally. Target Key: Open.

Open

  • Key: Open
  • State Process: hold pose. Optionally a WaitForDuration(2 seconds) chained with a check that the player has left.
  • Transitions:
  • On ConditionPlayerWithinRange == false. Target Key: Closing.

Closing

  • Key: Closing
  • State Process: animate door closing.
  • Transitions:
  • All Processes DoneTarget Key: Closed.

Press Play. Walk near the door — it opens. Walk away — after the open animation finishes and the proximity condition flips, it closes.


4. Transition Condition Types

The picker on each transition's Condition field shows several built-ins:

Condition Fires When
On Event (EventTransitionCondition) A configured IEvent raises. Subscribes on enter, unsubscribes on exit. Use for EventFromAsset, SignalEventFromHub, input events, anything.
On Condition (ConditionAdapter) A regular Jungle Condition becomes valid. Polls each frame while the state is active.
All Processes Done The state's State Process completed naturally. Subscribes to the state's completion event — no polling.
Wait One Frame One frame after the state was entered. Useful for chaining states without sitting on an "empty" state.
Composite Combines child conditions with All (every child must fire) or Any (first to fire wins).

Mix freely on a single state — the first transition whose condition fires wins.


5. The Execution Frame

A StateMachine is a ControllableComponent. Its Begin/End is driven by an IExecutionFrame — by default the GameObject's enable cycle. On Begin the machine enters its startingState; on End it exits the current state.

Common alternative frames:

  • Event Frame — begin on one IEvent, end on another. Useful for "this state machine only runs during the boss fight".
  • Targeted Cycle Frame — bind to another component's cycle (an Attachable's Attached cycle, an Agent's Holding cycle).
  • Mono Lifecycle Frame — explicit Start / OnEnable / etc. choice.

6. Querying the Machine

From scripts or external Conditions:

API Returns
CurrentState The currently running StateProcess.
CurrentStateKey The Key of the current state.
HasState(Key) Whether a node with the given Key exists.
GetState(Key) The StateProcess for the given Key.
TransitionTo(Key, callback) Force a transition to the given Key. Callback fires after the transition completes.

The event OnStateChanged(Key previous, Key new) fires on every transition — wire it to UI, audio, debug.


7. Reactive Wiring on the Side

State Machines often pair with Reactors for per-state side effects without bloating the state nodes:

  • A Reactor with an Event target on OnStateChanged (via a small adapter) — runs different bodies depending on which state was entered.
  • A Reactor with a Signal target that fires RaiseSignalOnOwnerTransmitter(SomeSignal) from inside a state's process — broadcasts that the state "did something" without coupling the state machine to the listener.

8. State Machine vs Reactor

Both run logic based on conditions. When to use which?

Pick a... When
StateMachine Behaviour is naturally a mode/phase: only one running process at a time, transitions between modes are the central design.
Reactor Multiple independent responses to triggers — they aren't mutually exclusive.

A boss with three phases (idle → enraged → dying) is a StateMachine. A UI panel that animates two unrelated effects on different events is a Reactor.

You'll often want both — a StateMachine for the boss's phase, a Reactor on the same GameObject for the cross-cutting effects.


Next Steps