Skip to content

Animation graphs

An animation graph is a state machine over a model’s clips. It has parameters, states that play one clip or blend several, transitions with conditions and cross-fades, and timed events that play sounds and notify scripts. The same graph can also carry IK, root motion and montages; see IK & retargeting.

Graphs are stored in the project and can drive placed models, spawned characters and the third-person avatar.

  1. Open Window → Animation graphs.
  2. Type an ID in the new graph id field and click Add graph. The graph starts with one state, idle.
  3. Under Clip library, pick the model that contains the clips. Leave it empty to use the clips of whichever model the graph runs on.
  4. Add Parameters, States and Transitions.
  5. Click Apply. Revert discards your edits; Delete graph removes a graph that nothing uses.

The panel validates the graph as you edit and shows the first problem above the Apply button.

Character Where to assign the graph
A placed model Select it, add the Animation component with Add Component, then set Graph in its Animation section
The third-person avatar World & terrain → Player → Animation graph
Any character, from a script anim_graph(id, "graph_id")

Without a graph, a model’s Animation component loops its single Clip at the set Speed.

You will give the player avatar in the War City example a graph that blends idle, walk and run by speed, and switches to a falling pose in the air. War City’s avatar, Character_Soldier, has the clips Idle, Walk, Run and Jump_Idle. (In a source checkout, War City’s models are downloaded by its build.py; run it once first.)

  1. Choose File → Open example → War City, then File → Save project to keep your own copy.
  2. Open Window → Animation graphs. Type soldier in new graph id and click Add graph.
  3. Set Clip library to Character_Soldier, so the clip fields list its clips.
  4. Under States, change the first state’s Id to move and click Make blend space. Blend by is already speed. Set the two points to Idle at 0 and Walk at 2.4, then click + Blend point and add Run at 5.2. These match War City’s walk and run speeds in m/s.
  5. Click + State, set its Id to fall and its Clip to Jump_Idle.
  6. Under Transitions, click + Transition. Set it from move to fall, click + Condition and choose grounded False, with a 0.15 s fade. Add a second transition from fall to move when grounded is True, with a 0.2 s fade.
  7. Set Initial state to move and click Apply.
  8. Open Window → World & terrain, select the Player tab and set Animation graph to soldier.

speed, grounded and vertical_speed are filled in by the engine for the avatar, so the graph does not declare any parameters.

Do the same from the command line, on a copy of the example (with its assets/ folder). Save the graph as soldier.json:

{"value": {
"id": "soldier", "name": "Soldier locomotion",
"library_asset_id": 8890572774061676,
"states": [
{"id": "move", "blend_param": "speed",
"blend": [{"clip": "Idle", "at": 0}, {"clip": "Walk", "at": 2.4}, {"clip": "Run", "at": 5.2}]},
{"id": "fall", "clip": "Jump_Idle"}
],
"transitions": [
{"from": "move", "to": "fall", "conditions": [{"param": "grounded", "op": "False", "value": 0}], "duration": 0.15},
{"from": "fall", "to": "move", "conditions": [{"param": "grounded", "op": "True", "value": 0}], "duration": 0.2}
],
"initial": "move"
}}
Terminal window
cp -R examples/war-city /tmp/demo
P="--workspace /tmp/demo --project project.feather.json"
feather-cli $P command animation.clips --json '{"asset_id":8890572774061676}' # Idle, Walk, Run, Jump_Idle…
feather-cli $P command animation.upsert --json "$(cat soldier.json)" --save project.feather.json
feather-cli $P command project.player --json '{"animation_graph":"soldier"}' --save project.feather.json

Check it worked. Press Play. Standing still plays Idle; hold Ctrl to walk and the avatar blends into Walk; move normally and it reaches Run; jump and it switches to Jump_Idle until it lands. From the command line, animation.get with {"id":"soldier"} returns both states, the three blend points and the two transitions, and animation.get with no arguments lists soldier with "states": ["move", "fall"]. game.simulate does not evaluate animation, so check the blending itself in Play.

  • A state does not play its clip. Clip names are not checked when you apply, so a misspelt clip is accepted. Compare every name with animation.clips. The engine’s default names Idle_Loop, Walk_Loop and Jog_Fwd_Loop are the exception: on a model without them they play its conventionally named idle, walk and run clips; see Locomotion clips.
  • Apply reports an unknown parameter. Every condition and Blend by must name a declared parameter, or one of speed, grounded and vertical_speed.
  • A graph cannot be deleted. Something still uses it, such as Player → Animation graph. Clear that first.

Parameters are the inputs that transitions test and blend spaces read.

Kind Value Notes
Float A number Set with anim_set(id, name, number)
Bool 0 or 1 Set with anim_set(id, name, true)
Trigger Set or not set Fired with anim_trigger(id, name); consumed by the transition that tests it

Each parameter has a starting value. The engine fills in three parameters for the third-person avatar every frame, so you can use them without declaring them:

Parameter Value
speed Movement speed
grounded 1 on the ground, 0 in the air
vertical_speed Vertical velocity

A state plays a single clip, or a 1D blend space that mixes clips along a parameter.

Setting Description Default
Clip The clip to play —
Blend points Clips placed at values along the Blend by parameter. Click Make blend space to convert a state, Single clip instead to revert None
Speed Playback rate, 0 to 10 1
Speed parameter Multiplies the speed by a parameter’s value None
Loop Whether the clip loops On
Events Timed events in the clip; see below None

Initial state sets the state the graph starts in. When it is empty, the first state is used.

A transition moves from one state, or from any state (*), to another.

Setting Description Default
From A state ID, or * for any state *
To The target state —
Conditions All must hold. Each compares a parameter with Greater, Less, GreaterOrEqual, LessOrEqual, Equal or NotEqual, or tests True / False None
Duration Cross-fade time in seconds, 0 to 10 0.2
Exit time Waits until the source clip reaches this normalized time, 0 to 1. Empty means any time Empty

Transitions are checked in order each frame; the first one whose conditions hold fires.

Events fire at a normalized point of a state’s clip, from 0 to 1. Each event can play a project sound at the character, at a set volume, and calls the on_anim_event(id, name) hook in world scripts. Use them for footsteps, weapon swings and hit frames.

// World script.
fn on_anim_event(id, name) {
if name == "footstep" {
print("Step from " + id);
}
}

The id argument is the character’s object ID. Pass "player" for the third-person avatar.

Function Description
anim_graph(id, graph) Runs a graph on a character; an empty name removes it
anim_set(id, name, value) Sets a Float or Bool parameter
anim_trigger(id, name) Fires a Trigger parameter
anim_state(id) Returns the current state ID
play_montage(id, clip), play_montage(id, clip, options) Plays a one-shot over the graph
stop_montage(id), stop_montage(id, blend) Stops the playing montage
montage_playing(id) Returns the playing montage, or an empty string

Montages, look-at and IK weights are covered in IK & retargeting.

fn update(dt) {
if key_down("E") {
anim_trigger("player", "wave");
}
}
Limit Value
Graphs per project 128
Graph, state, parameter and montage IDs 1 to 64 letters, digits, - or _
Blend points per state 16
Events per state 32
Event names 64 characters
Montages per graph 64
Command Purpose
animation.get Lists graphs, or returns one in full with id
animation.upsert Creates or patches a graph (JSON merge; arrays replace). rename_from renames it and updates its users
animation.remove Deletes a graph that nothing uses
animation.clips Lists a model’s clips and their lengths

Create a locomotion graph that blends idle, walk and run by speed and jumps on a trigger:

Terminal window
feather-cli --connect .feather-session.json command animation.upsert --json '{
"value": {
"id": "hero",
"name": "Hero",
"parameters": [{"name": "jump", "kind": "Trigger", "default": 0}],
"states": [
{"id": "move", "blend_param": "speed",
"blend": [{"clip": "Idle", "at": 0}, {"clip": "Walk", "at": 2}, {"clip": "Run", "at": 6}]},
{"id": "jump", "clip": "Jump", "looping": false}
],
"transitions": [
{"from": "move", "to": "jump", "conditions": [{"param": "jump", "op": "True", "value": 0}], "duration": 0.1},
{"from": "jump", "to": "move", "exit_time": 0.9, "duration": 0.2}
],
"initial": "move"
}
}'

Clip names must match the model’s clips; list them with animation.clips. See Agents & automation for connecting a live session.