Skip to content

State Machine

States, transitions and conditions on parameters; each state can play an animation on an AnimationPlayer. Scripts get on_enter_ / on_exit_.

Type: StateMachineNode · Category: Animation & Effects · Also found as: statemachine, state_machine

A finite state machine for gameplay and animation. Its states and transitions are edited as a graph in the State Machine tab of the bottom panel (it follows the selected State Machine node) and saved in the scene as "state_machine".

  • States play an animation (on an Animation Player) or are pure gameplay states; a state can hold a child machine.
  • Transitions fire when their conditions hold: parameters (speed > 10), triggers (jump!), script functions (sees_player()), or groups of them (a | b), with a blend time.
  • Guards let a state refuse to be left (for example Leave After a minimum time) - sm_request respects them, sm_travel forces the change.
  • Scripts get on_enter_<state>, on_update_<state> and on_exit_<state> callbacks (right-click a state > Add Script Callbacks).
function Player:update(dt)
local sm = self.node:get_node("StateMachine") ---@type StateMachineNode
local vx = self.node:character_get_velocity()
sm:sm_set("speed", math.abs(vx))
if Input.is_action_pressed("jump") then sm:sm_trigger("jump") end
end
function Player:on_enter_hurt()
Audio.play_sound("audio/hurt.wav")
end

While the game runs (Simulate or Play) the graph lights the current state and lists the history of changes.

No reflected properties of its own: its settings are described above.

Plus the common properties every node has.

Call them on the node (self.node in its script, or self.node:get_node("path")). Every node also has the Node methods.

Connects a handler to one of this node’s events: state_changed (The state machine entered a state); the tree events; custom ones.

self.node:connect("state_changed", function(value, source) Log.info("state_changed") end)
  • event StateMachineNodeEvent|string
  • handler fun(value: any, source: StateMachineNode)

Returns integer id Connection id for disconnect

Sets a parameter that transition conditions test (booleans become 1 / 0).

local sm = self.node:get_node("StateMachine"); sm:sm_set("on_floor", true)
  • name StateMachineParam|string
  • value number|boolean

Gets the value of a state machine parameter.

local speed = self.node:get_node("StateMachine"):sm_get("speed")
  • name StateMachineParam|string

Returns number value

Sets a trigger: a one-shot event such as a button press. A trigger is a parameter (Inspector > Parameters, starting value 0) that a condition tests with the trigger operator (the condition row’s second dropdown). This sets it to 1; it stays set (also while a guard blocks the change) until a transition testing it fires, which resets it to 0. An arrow with no conditions fires at once, so give an arrow a trigger condition to change state only when the script says.

if Input.is_action_pressed("fire") then self.node:get_node("StateMachine"):sm_trigger("punch") end
  • name StateMachineParam|string

Switches to a state now, ignoring conditions.

local ok = self.node:get_node("StateMachine"):sm_travel("hurt", 0.1)
  • state StateMachineState|string
  • blend? number Crossfade seconds for the state’s animation

Returns boolean ok false when there is no such state

Asks to go to a state: allowed when an arrow leads there from the current state (or from Any state) and the guards agree (the current state’s leave rules - leave after, interruptible, its leave conditions, can_exit_ - and the target’s enter conditions and can_enter_). The arrow’s own conditions are not checked: the script decides when.

local ok, why = self.node:get_node("StateMachine"):sm_request("dash")
  • state StateMachineState|string
  • blend? number Crossfade seconds (default: the arrow’s)

Returns boolean ok true when it changed state, string? reason why not (e.g. “attack: leave after 0.4s (0.12s so far)”)

Gets the name of the current state.

if self.node:get_node("StateMachine"):sm_state() == "attack" then return end

Returns string state The current state (“” before it starts)

Gets the name of the state before the current one.

local prev = self.node:get_node("StateMachine"):sm_previous()

Returns string state The state before the current one

The last state changes (up to 32), oldest first.

for _, c in ipairs(self.node:get_node("Brain"):sm_history()) do Log.info(c.from, "->", c.to, c.why) end

Returns SM_Change[] changes

Gets the time in seconds spent in the current state.

if self.node:get_node("StateMachine"):sm_time() > 2 then self:give_up() end

Returns number seconds Time spent in the current state

Starts (or restarts) in a state, or in the initial state.

self.node:get_node("StateMachine"):sm_start("idle")
  • state? string

Stops checking transitions (the current state stays).

self.node:get_node("StateMachine"):sm_stop()
{
"type": "StateMachineNode",
"name": "StateMachine",
"position": [
0,
0
]
}