StateTree Authoring
Author a StateTree from scratch with MifBridge: create the asset, add states, tasks, conditions, transitions, parameters and property bindings, compile, read it back, record what it did in play, and set breakpoints. Read this if you build AI or gameplay logic with StateTrees.
StateTree authoring works on UE 5.3 to 5.8. Where a feature needs a newer engine, the call is refused by version and says so.
How a StateTree is built
A StateTree asset holds two layers:
- Editor data, which is what you author: a schema, root parameters, evaluators, global tasks, and a tree of states. Each state has tasks, enter conditions, considerations (5.5+) and transitions. Cooked assets have no editor data.
- Compiled data, which is what a game runs. It is built from the editor data by a compile.
Editing the editor data changes nothing a game sees until the tree is compiled. Every write below ends in a compile and reports it.
Every task, condition, consideration and evaluator is a node: a node struct plus its instance data. The instance data holds the settings the Details panel shows under the node (a Delay task's Duration). That is where properties in the calls below are looked up first.
The endpoints
| Endpoint | Does |
|---|---|
create_state_tree |
Makes a new asset with a schema, a root state and a first compile |
edit_state_tree |
Adds, removes, moves and changes states, nodes, transitions, parameters and bindings, then compiles |
compile_state_tree |
Compiles, returning the compiler's errors and warnings with the state each names |
list_state_tree_node_types |
Lists the node types a schema allows: the list every type refusal quotes |
describe_state_tree |
Reads the compiled states, and with editor the authored tree in edit_state_tree's own grammar, with every id |
list_state_trees |
Lists a project's StateTree assets from the asset registry, loading nothing |
state_tree_runtime |
Drives a StateTree running in play: status, send an event, start, stop, restart, request a transition |
trace_state_tree |
Reads a recorded play session: states entered, transitions taken |
watch_state_tree |
Pauses play when an actor's StateTree enters a named state |
Nothing here saves. save_package writes the asset.
Step 1: a schema must exist
A StateTree cannot exist without a schema, which says what the tree runs against (an actor's StateTree component, a Mass entity, a camera director). create_state_tree takes path and schema, and schema is required. A missing or wrong one is refused with every schema this editor has loaded.
The general-purpose schemas, StateTreeComponentSchema and from 5.4 StateTreeAIComponentSchema, come from the GameplayStateTree plugin, which is off by default on every engine. From 5.5 the camera director schema is available because its plugin is on by default. MifBridge does not enable plugins in your project for you. When no schema is loaded, the refusal names the fix:
set_plugin_enabled {name: "GameplayStateTree", enabled: true}
then restart the editor.
| Schema | Plugin | Engines |
|---|---|---|
StateTreeComponentSchema, an actor's StateTree component |
GameplayStateTree | 5.3 to 5.8 |
StateTreeAIComponentSchema |
GameplayStateTree | 5.4 to 5.8 |
MassStateTreeSchema |
MassAI (experimental) | 5.3 to 5.8 |
GameplayInteractionStateTreeSchema |
GameplayInteractions (experimental) | 5.3 to 5.8 |
CameraDirectorStateTreeSchema |
GameplayCameras (experimental) | 5.5 to 5.8 |
create_state_tree {path: "/Game/AI/ST_Guard", schema: "StateTreeComponentSchema"}
The reply carries the tree, the schema, the root state's id and name, and the compile result. The path is checked the way create_asset checks one. The asset is made without any dialog.
Next, call list_state_tree_node_types with the tree to see which task and condition types its schema takes.
Step 2: edit the tree
edit_state_tree takes one or more lists in a single call. The whole call is checked, applied in one undo step, and undone whole if any step is refused.
edit_state_tree {
tree: "/Game/AI/ST_Guard",
addParameters: [{name: "Speed", type: "Float", value: 300}],
addStates: [{name: "Patrol"}, {name: "Chase"}],
addTasks: [{state: "Patrol", type: "StateTreeDelayTask",
properties: {Duration: 2.0}}],
addTransitions:[{state: "Patrol", trigger: "OnStateCompleted", to: "Chase"}]
}
The lists
| List | Entry |
|---|---|
remove |
an id or a parameter name: a state (with its whole subtree), a node, a transition or a root parameter |
addParameters |
{name, type, value?, enum?, struct?, class?, container?} |
addStates |
{name, parent? or topLevel?, type?, selectionBehavior?, tag?, enabled?, linkedSubtree?, linkedAsset?, parameterOverrides?, enterEvent?, parameters?} |
addEvaluators, addGlobalTasks |
{type, name?, properties?} |
addTasks |
{state, type, name?, properties?} |
addEnterConditions, addConsiderations (5.5+) |
{state, type, name?, operand?, indent?, properties?} |
addTransitions |
{state, trigger, to? or type?, priority?, event?, payload?, delay?, enabled?, conditions?} |
addTransitionConditions |
{transition, type, name?, operand?, indent?, properties?} for a transition the tree already has |
setProperties |
{node, properties} for a node the tree has |
moveStates |
{state, parent or topLevel, index?} moves a state with its subtree |
setStates |
changes a Linked state's target, a LinkedAsset state's tree (5.4+) or a state's theme color (5.4+) |
addColors |
{name, color} adds theme colors (5.4+) |
addBindings, removeBindings |
see Property bindings below |
schema |
replaces the tree's schema, alone in the call |
compile |
set false to skip the compile |
At least one list is required. Whatever order the lists come in, they are applied as: removals and removeBindings, parameters, states, evaluators and global tasks, the states' nodes, transitions, properties, addBindings, then the compile.
Naming things
- A state is named by its id, or by its name when no other state has that name. That includes states an earlier
addStatesentry in the same call creates, so a transition can name a state the call makes. - A name two states share is refused with both ids.
- Nodes and transitions are named by id.
describe_state_treewitheditorgives every id. - A state with no
parentgoes under the tree's first top-level state, as the editor's Add State does.topLevelset to true adds a new top-level state instead (a subtree root, forlinkedSubtree).
Types
A node's type is its struct's name, for example StateTreeDelayTask. The F prefix and a full /Script/Module.Struct path are accepted, and so is a Blueprint class deriving from the StateTree Blueprint task, condition, consideration or evaluator base. The type must be one the tree's schema allows. This is the same filter the editor's node picker applies. Anything else is refused with the full list list_state_tree_node_types would give for that kind and schema.
Properties
properties is {path: value} and uses set_property's value grammar, with dot paths and accessors. A path is looked up on the node's instance data first, then on the node struct. Every property is applied to a throwaway copy of the node before the tree is touched, so a bad path or value refuses the whole call and writes nothing.
Transitions
triggeris OnStateCompleted, OnStateSucceeded, OnStateFailed, OnTick or OnEvent.tonames a target state.typegives the other link types instead: Succeeded, Failed, NextState, and from 5.4 NextSelectableState.eventis a gameplay tag that must be registered (list_gameplay_tags,add_gameplay_tag).delayis{duration, randomVariance}in seconds.- Conditions on a transition take
operand(And, Or) andindent(0 to 4) to group them.
Parameters
A root parameter is {name, type, value?}. type is one of Bool, Byte, Int32, Int64, Float, Double, Name, String, Text, Enum, Struct, Object, SoftObject, Class, SoftClass. enum, struct or class names the value type where the type needs one, and container is array, or set from 5.5. A map container is refused because it needs a key type. value takes whatever set_property takes. A name the tree already has is refused.
What the schema allows
The editor hides or refuses what a schema says no to, and the endpoint refuses the same things, naming the schema, before writing:
| Refused | Engines |
|---|---|
| a node type the schema does not take | 5.3+ |
| enter conditions, when the schema disallows them | 5.3+ |
| evaluators, when the schema disallows them | 5.3+ |
| a second task on a state, when the schema allows one task (the state holds a single task instead) | 5.3+ |
| considerations | 5.5+ |
| root parameters, when the schema disallows them | 5.6+ |
a selectionBehavior, when the schema disallows it |
5.6+ |
a state type the schema disallows |
5.7+ |
What is available on which engine
| 5.3 | 5.4 | 5.5 | 5.6 | 5.7 | 5.8 | |
|---|---|---|---|---|---|---|
| considerations | no | no | yes | yes | yes | yes |
a state's tag and weight |
no | no | yes | yes | yes | yes |
| LinkedAsset states | no | yes | yes | yes | yes | yes |
parameterOverrides |
no | yes | yes | yes | yes | yes |
event payloads, enterEvent |
no | no | yes | yes | yes | yes |
| property function sources | no | no | yes | yes | yes | yes |
| property reference targets | no | no | yes | yes | yes | yes |
set containers on parameters |
no | no | yes | yes | yes | yes |
Enums are read and written by name, never by number, because values move between engines. A refusal that lists enum values quotes the running engine's own list.
After an edit
An open StateTree editor redraws on its own. The handler then compiles unless compile is false, and reads back what it added. The reply has added (every addition with its id and the entry it came from), removed, compiled (ok, errors, warnings) and verified.
Whole or nothing
Everything that can be checked is checked before any write: ids and names, each type against the schema, each property on a throwaway node, parameter types and values, gameplay tags, and the schema's rules. A refusal at that stage says nothing was changed. Then the writes run in one transaction. If a step still fails, the handler undoes the transaction, reads the tree again, and says whether it matches the tree from before the call.
One undo_transactions takes back a whole successful edit.
Removing things
Deleting a state in the editor leaves dangling references behind, and each is a compile error:
- A transition, or a Linked state's link, to a removed state:
Failed to resolve transition to state '%s'.A transition disabled withenabledfalse does not escape this. - A binding whose source was a removed node:
Failed to find binding source property '%s' for target %s.
edit_state_tree refuses a removal that would leave either behind, naming each transition, link and binding, unless the thing pointing at the removed item goes in the same call (the transition by its id, or the binding by removeBindings, or the binding rebound from something else). Removing a state removes its whole subtree, as the editor does.
Compiling
compile_state_tree runs the editor's own compile: validate, compile, record. The reply has ok, errors and warnings (each with the state or node it names), compiledStateCount and compiledCurrent, which says whether the compiled data was made from the editor data as it stands.
A tree that does not compile keeps its editor data, but its compiled data is reset, so a game runs nothing from it. On UE 5.3 and 5.4 the engine writes the compiler's messages only when a compile fails, so a successful compile there reports no warnings, and the reply says so.
Property bindings
A binding makes the compiled tree copy one property into another before a node runs: a task's input from a tree parameter, a condition's Left from a task's output, an actor reference from the schema's context. This matters because a node with an unbound Input property does not compile, and that is most of the engine's conditions: every Compare condition's Left is an Input. Of the stock conditions only the random condition compiles with nothing bound.
addBindings: [{node, property, source}]
removeBindings: [{node, property}]
nodeis the target node: its id, or the entry of a node the same call adds, such asaddTasks[0]oraddTransitions[1].conditions[0].propertyis a path in that node's instance data:Left,Offset.X,Values[0].sourceis one of:parameters.<name>[.<member>], a tree parameter, including one the same call addscontext.<name>[.<member>], the schema's context such ascontext.Actor; the name alone binds the whole object<node>.<property>, a property of a task, evaluator or global task, by id or by this call's entry. A condition or consideration is never a source.function.<Type>[.<output>](5.5+), a property functionevent.<state | transition id | addTransitions[i]>[.<path>](5.5+), an event's payloadstate.<state>.<parameter>, a Subtree or linked state's parameter
A property that already has a binding is rebound, and the reply row says replaced.
What is checked
The same rules the editor's binding widget applies:
- The property resolves in the target's instance data, with array indexes checked against the array.
- It is a property the widget would offer: an Input or Context property whole, a Parameter at any depth, never an Output.
- The source resolves. An unknown context name is refused with the schema's real context names.
- The types are compatible by the engine's own test. Promotions are allowed: int into float, float into double and back. An enum compare's input takes an enum.
- One entry per target property in a call.
- The source must be one the node can see: root parameters, the context, evaluators and global tasks, and the tasks of the states above it that run before it. An enter condition does not see its own state's tasks, a transition condition does. A source that is not listed undoes the call, and the refusal names what the target can bind from.
Property functions (5.5+)
source: "function.<Type>[.<output>]" computes a value with a small node that lives inside the binding, such as Add, Multiply or Invert. <Type> must be a function the schema offers; an unknown one is refused with the list. <output> names an Output property, or defaults to the single output. functionValues: {input: value} gives constant inputs and functionBindings: {input: source} binds inputs, one level deep. An input takes a value or a binding, not both. Removing the binding removes the function.
Property references (5.5+)
A property of the reference type is bound whole and refers to its source rather than copying it. Run Env Query's Result is one. The accepted sources are a tree or state parameter, an event's payload, or a task, evaluator or global task's Output; never the context, a condition or a function. The row says reference: true.
Linked states and their parameters
A Linked state runs a Subtree state of the same tree, and a LinkedAsset state (5.4+) runs another StateTree asset. Each takes its parameters from what it runs.
addStates: [{name: "Run", type: "Linked", linkedSubtree: "Walk",
parameterOverrides: {Speed: 600}}]
parameterOverrides does what the Details panel's override checkbox does. A name the subtree lacks is refused with its parameter list, and an override on any other state type is refused.
A Subtree state's own parameters are authored with parameters on addStates, in addParameters grammar. Only a Subtree state takes parameters. Bindings into a linked state's parameters use node: "state.<state>". Bindings into a Subtree state's parameters are refused, because its Linked state provides them.
A component can also run a different subtree in a LinkedAsset state by gameplay tag, through the LinkedStateTreeOverrides property on the StateTree component (5.5+). Set it with set_property on the component template.
Events (5.5+)
enterEvent: {tag?, payload?} on a state sets the event it needs to be entered. An OnEvent transition's event takes payload too. Both can be binding sources through event.<id>.<path>, which reaches Tag, Payload.<member> and Origin. Removing a state or transition is refused while a binding reads its event.
Not authored here
Output bindings (5.7+), task-completion bindings (5.8), delegate dispatchers and listeners, and struct references. A target of one of those types is refused.
Reading bindings back
describe_state_tree with editor gives each node:
bindings, each withproperty,source(inaddBindings' words),sourceKindandsourceNamerequiredInputs, the Input properties the compile needs boundoutputs, what a later node can bind from
Bindings into something that is not a node (a state's parameters) are listed once as otherBindings. bindableSources set to true adds, for every evaluator, global task, task, enter condition and transition condition, the sources its inputs can bind to, in addBindings' grammar.
Reading a tree
describe_state_tree returns the compiled view (states, stateCount, schema) and, by default, editor: the authored tree with parameters, evaluators, global tasks and the states nested as authored. Each state has its id, objectPath (set_property takes it for a state's own fields), type, selection behavior, enabled flag, tag and weight (5.5+), tasks, enter conditions, considerations, transitions and children. Each node has its id, kind, type, name, properties (instance data) and nodeProperties (the node struct). The editor block uses the grammar edit_state_tree takes, so what one call reads the other can be given. editor: false leaves the block out.
A tree edited and never recompiled reads as zero compiled states even though it has authored ones, and the response says so. A cooked StateTree has no editor data: editor.available is false.
Watching a tree run
Active states and events in play
state_tree_runtime drives a tree running in play, on an actor's State Tree component or an AI pawn's controller's. status lists each component with running, paused, runStatus and activeStates. sendEvent sends a registered gameplay tag; the tree reads it on its next tick, so call status after a frame. start, stop and restart call the component's own logic calls. requestTransition forces the running tree into a state by name at a priority (Low, Normal, Medium, High, Critical; default Normal). An actor with more than one component needs component for a change. Changes need the play world.
Recording a session
To see what a tree did:
trace_startwithchannels: "StateTreeDebugChannel,FrameChannel"before play starts. An instance that started before the recording is not kept.- Play, then stop play.
trace_stop, which answers the.utracepath.trace_state_tree {path}.
Options on trace_state_tree: tree (only instances running this StateTree), instance (part of an owning actor's name), start and end (world seconds) and limit (events listed per instance, default 400). The reply lists instances, each with statesEntered in order, transitions, and events with world time, kind and value in the StateTree debugger's own words. inTrace lists every instance the trace holds. No window and no other process is involved.
Limits:
- Read the trace in the editor that recorded it, before the tree is recompiled. The analysis keeps an instance only while its compiled data matches the recording.
- An event sent to a tree is not a traced record; it shows as the transition it triggered.
- A file trace starts with the engine's in-memory tail, so it can hold events from just before
trace_start. - An empty answer is a failure that lists what the trace does hold.
Breakpoints
watch_state_tree pauses play when an actor's StateTree enters a named state.
| Action | Does |
|---|---|
set with actor and state |
Watches an actor (name or label, placed or spawned) and pauses play the first frame its State Tree components have that state active |
status |
Lists watches and hits with world time and active states, and whether play is paused |
resume |
Continues play and catches the next entry |
clear |
Removes one watch by id, or all |
active with actor |
Reads the states the actor's State Trees are in now, with no watch needed |
It reads the component's execution state every frame, so it needs no open StateTree editor and no trace. A state entered and left inside one frame is never active at a frame's end and is not caught. Task and transition breakpoints are not available. Watches live in the editor session.