# Animation graphs

> Drive a character's clips with state machines, blend spaces, parameters and timed events.

Source: https://feather-engine.com/docs/animation/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](/docs/animation/ik-and-retargeting/).

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

## Create a graph

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.

## Use a graph

| 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**.

## Example: an idle, walk and run character

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`:

```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"
}}
```

```bash
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.

### Common problems

- **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](/docs/gameplay/player-and-traversal/#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

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 |

## States

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.

## Transitions

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

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.

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

## Script API

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](/docs/animation/ik-and-retargeting/).

```rust
fn update(dt) {
    if key_down("E") {
        anim_trigger("player", "wave");
    }
}
```

## Limits

| 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 |

## Automate it

| 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:

```bash
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](/docs/agents/overview/) for connecting a live session.
