> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/core-engine-concepts/spine.md).

# Spine Animations

You can add animated interactable objects to your game using our existing library of 50k+ Spine rigs.

Spine is how animated characters and props work in All Out (chests, animals, props, VFX rigs, and the player).

To make Spine feel simpler, keep this mental model:

* **Spine rig (`.spine`)**: the “blueprint” (bones + animation names + skin names)
* **`Spine_Animator`**: a component on an entity that renders/updates a Spine rig in the world
* **`Spine_Instance`**: a standalone (non-component) instance — used for UI animations or manual lifetime control
* **Skins**: “outfits/variants” that decide which images are visible
* **Animations**: named timelines (e.g. `"idle"`, `"walk"`, `"open"`)

`Spine_Bone_Transform` describes a bone's current affine basis in skeleton-local space. A bone-local point `{x, y}` maps to `position + x_axis*x + y_axis*y`.

### When to use `Spine_Animator` vs `Spine_Instance`

* Use **`Spine_Animator`** for anything that exists in the world (props, NPCs, interactables).
* Use **`Spine_Instance`** directly when you want to draw a Spine in UI or you need manual lifetime control.

The engine updates a `Spine_Animator` automatically. A standalone instance must be updated and destroyed by your code.

{% hint style="warning" %}
If you call `Spine_Instance.create()`, you must call `instance.destroy()` when you're done (otherwise you leak).
{% endhint %}

### Spine API reference (CSL)

```go
// Component — attached to world entities
Spine_Animator :: class : Component {
    depth_offset: float;
    layer: s32;
    mask_in_shadow: bool;
    instance: Spine_Instance #read_only;

    // Visuals
    color: v4; // RGBA multiplier; {1, 1, 1, 1} leaves the appearance unchanged.
    scale: v2;
    speed_multiplier: float;
    state_machine: State_Machine #read_only;

    set_skeleton :: method(asset: Spine_Asset) -> u64;
    get_skeleton :: method(id: u64 = 0) -> Spine_Asset;
    set_animation :: method(animation: string, loop: bool, track: s64, speed: float = 1);

    // Skins
    set_skin          :: method(skin: string);
    enable_skin       :: method(skin: string);
    disable_skin      :: method(skin: string);
    disable_all_skins :: method();
    refresh_skins     :: method();
    get_skins         :: method() -> []string;
    set_to_setup_pose :: method();

    // Bone local offsets (advanced)
    get_bone_local_position :: method(bone_name: string) -> v2;
    try_get_bone_local_transform :: method(bone_name: string) -> (Spine_Bone_Transform, bool);
    set_bone_local_position :: method(bone_name: string, position: v2);

    // Optional: drive animations via a state machine
    set_state_machine       :: method(machine: State_Machine, transfer_ownership: bool);
    set_color_replace_color :: method(color: Color_Replace_Color);
    set_material            :: method(material: Material, transfer_ownership: bool);
    get_tint                :: method() -> v4;
    set_tint                :: method(tint: v4);
    set_destroy_entity_when_done_current_animation :: method(enabled: bool);
}
```

`Spine_Animator` inherits `Component.awaken()`. When your script and animator start on the same entity, call `awaken()` before accessing the animator's instance.

### Adding animated objects to your world

There are 3 ways to add animated objects to your scene, all of which will create a Spine\_Animator component.

* Drag any animated asset from the [Asset Catalog](/using-the-editor/asset-catalog.md) into your scene
* Create a new entity and add the Spine\_Animator component
  * Set the `Skeleton Data Asset` field to a .spine file in your assets
* Add a spine animator to your world using scripting (covered in the examples)

{% hint style="info" %}
If your code and the `Spine_Animator` are on the same entity and start at the same time, call `spine.awaken()` before calling any animation methods.
{% endhint %}

### Examples

#### Openable Chest

An interactable chest that plays an “open” animation and then stays open:

```go
Chest :: class : Interactable {
    opened: bool;

    ao_start :: method() {
        this.set_listener(this);
        this.set_text("Open");
        radius = 1.25;
        required_hold_time = 0.15;

        spine := entity.get_component(Spine_Animator);
        spine.awaken();
        spine.set_animation("idle", true, 0);
    }

    can_use :: method(player: Player) -> bool {
        return !opened;
    }

    on_interact :: method(player: Player) {
        opened = true;
        this.set_text("Opened");

        spine := entity.get_component(Spine_Animator);
        spine.set_animation("open", false, 0);
    }
}
```

Animation and skin names must match the names in the rig.

#### Walking Chicken

A simple “idle vs walk” loop based on movement:

```go
Chicken :: class : Component {
    @ao_serialize speed: float = 1.5;
    last_pos: v2;
    was_moving: bool;

    ao_start :: method() {
        last_pos = entity.world_position;

        spine := entity.get_component(Spine_Animator);
        spine.awaken();
        spine.set_animation("idle", true, 0);
    }

    ao_update :: method(dt: float) {
        // Example motion (patrol)
        entity.add_local_position({speed * dt, 0});

        moving := length_squared(entity.world_position - last_pos) > 0.0001;
        last_pos = entity.world_position;

        if moving != was_moving {
            was_moving = moving;
            spine := entity.get_component(Spine_Animator);
            spine.set_animation(moving ? "walk" : "idle", true, 0);
        }
    }
}
```

#### Driveable Car

If you want "driveable" behavior, the simplest approach is to drive the *movement* with your own gameplay logic, and drive the *animation* based on whether the car is moving.

```go
Car :: class : Component {
    @ao_serialize agent: Movement_Agent;
    @ao_serialize target: Entity;

    ao_start :: method() {
        spine := entity.get_component(Spine_Animator);
        spine.awaken();
        spine.set_animation("idle", true, 0);
    }

    ao_update :: method(dt: float) {
        if #alive(target) {
            agent.set_path_target(target.world_position, agent.movement_speed);
        }

        moving := length_squared(agent.velocity) > 0.01;
        spine := entity.get_component(Spine_Animator);
        spine.set_animation(moving ? "drive" : "idle", true, 0);
    }
}
```

{% hint style="info" %}
If you're switching between only two looping animations, direct `set_animation` calls are usually simpler than building a state machine.
{% endhint %}

### Skins (variants/outfits)

Some spines don't start with a default skin. If your entity is "invisible", you likely need to pick a skin.

```go
spine := entity.get_component(Spine_Animator);
spine.awaken();

// Option A: set a single skin
spine.set_skin("default");
spine.refresh_skins();

// Option B: combine multiple skins
spine.disable_all_skins();
spine.enable_skin("body");
spine.enable_skin("hat");
spine.refresh_skins();
```

{% hint style="warning" %}
Always call `refresh_skins()` after changing skins.
{% endhint %}

### Animation events

Use callbacks when a Spine timeline event should trigger gameplay or audio. For a `Spine_Animator` component, register callbacks on its `instance`.

```go
Spine_Event_Listener :: class : Component {
    ao_start :: method() {
        spine := entity.get_component(Spine_Animator);
        spine.awaken();

        spine.instance.set_on_event(this, proc(userdata: Object, event: Spine_Event_Data) {
            listener := userdata.(Spine_Event_Listener);
            listener.handle_spine_event(event);
        });
    }

    handle_spine_event :: method(event: Spine_Event_Data) {
        if event.event == "footstep" {
            desc := SFX.default_sfx_desc();
            desc.set_position(entity.world_position);
            SFX.play(get_asset(SFX_Asset, "sfx/footstep.wav"), desc);
        }
    }
}
```

```go
Spine_Event_Data :: struct {
    event: string;
    int_value: s64;
    float_value: float;
    string_value: string;
}
```

The event callback runs in the shared predicted path. Call gameplay and SFX directly; do not guard it with `Game.is_server()` or `is_local()`.

## Standalone instances and `UI.spine`

The standalone API provides manual lifetime and animation updates:

```go
Spine_Instance :: class {
    create  :: proc() -> Spine_Instance;
    destroy :: method();
    update  :: method(dt: float);

    set_skeleton      :: method(asset: Spine_Asset) -> u64;
    get_skeleton      :: method(id: u64 = 0) -> Spine_Asset;
    add_skeleton      :: method(asset: Spine_Asset) -> u64;
    remove_skeleton   :: method(id: u64);
    set_main_skeleton :: method(id: u64);

    set_animation :: method(animation: string, loop: bool, track: s64, speed: float = 1);
    set_skin      :: method(skin: string);
    refresh_skins :: method();

    get_bone_local_position       :: method(bone_name: string) -> v2;
    try_get_bone_local_transform  :: method(bone_name: string) -> (Spine_Bone_Transform, bool);

    set_on_event           :: method(userdata: Object, callback: proc(userdata: Object, event: Spine_Event_Data));
    set_on_animation_start :: method(userdata: Object, callback: proc(userdata: Object, animation: string));
    set_on_animation_end   :: method(userdata: Object, callback: proc(userdata: Object, animation: string));
}
```

Keep the instance in persistent player UI state. Create it once, update and draw it from `ao_late_update`, then destroy it from `ao_end`:

```go
My_Player :: class : Player_Base {
    menu_spine: Spine_Instance;

    ao_start :: method() {
        menu_spine = Spine_Instance.create();
        menu_spine.set_skeleton(get_asset(Spine_Asset, "characters/shopkeeper.spine"));
        menu_spine.set_skin("default");
        menu_spine.refresh_skins();
        menu_spine.set_animation("idle", true, 0);
    }

    ao_late_update :: method(dt: float) {
        if !is_local_or_server() return;

        menu_spine.update(dt);

        UI.push_screen_draw_context();
        defer UI.pop_draw_context();
        UI.spine(UI.get_screen_rect().center(), menu_spine, {0.75, 0.75});
    }

    ao_end :: method() {
        if menu_spine != null {
            menu_spine.destroy();
            menu_spine = null;
        }
    }
}
```

Do not create the instance inside `ao_late_update`; that would restart its animation every frame and leak the previous instance.

### State machines (optional, for complex animation logic)

You can keep animation logic simple by calling `set_animation` directly. If you have many states (idle/run/attack/hit/death), a `State_Machine` helps you define transitions once and then just set variables/triggers.

The key idea: **state names must match animation names** in your Spine file.

#### What a state machine does

Think of a state machine as a tiny “animation controller”:

* You define **states** (each state name should match a Spine animation name)
* You define **variables** (`bool`, `trigger`, `int`, `float`)
* You define **transitions** between states based on those variables
* At runtime, you only update variables (the state machine chooses the animation)

#### Minimal example: idle/walk + attack trigger

```go
NPC_Anim :: class : Component {
    state_machine: State_Machine;
    is_moving: bool;

    ao_start :: method() {
        // 1) Create the state machine + variables
        state_machine = State_Machine.create();
        moving_var := state_machine.create_variable("is_moving", .BOOL);
        attack_var := state_machine.create_variable("attack", .TRIGGER);

        // 2) Create a layer (maps to a Spine track, usually 0)
        layer := state_machine.create_layer("main", 0);

        // 3) Create states (names must match animations in the Spine rig)
        idle := layer.create_state("idle", true);
        walk := layer.create_state("walk", true);
        attack := layer.create_state("attack", false);
        layer.set_initial_state(idle);

        // 4) Movement transitions
        idle_to_walk := layer.create_transition(idle, walk, false);
        idle_to_walk.create_bool_condition(moving_var, true);

        walk_to_idle := layer.create_transition(walk, idle, false);
        walk_to_idle.create_bool_condition(moving_var, false);

        // 5) Attack from any state, then return to idle when complete
        to_attack := layer.create_global_transition(attack, true);
        to_attack.create_trigger_condition(attack_var);

        attack_to_idle := layer.create_transition(attack, idle, true); // require_state_complete = true

        // 6) Attach to the animator
        spine := entity.get_component(Spine_Animator);
        spine.awaken();
        spine.set_state_machine(state_machine, true); // transfer_ownership = true
    }

    ao_update :: method(dt: float) {
        state_machine.set_bool("is_moving", is_moving);
    }

    do_attack :: method() {
        state_machine.set_trigger("attack");
    }
}
```

{% hint style="info" %}
If you pass `transfer_ownership = true` to `set_state_machine`, the animator will destroy the state machine for you. Otherwise, you must destroy it yourself.
{% endhint %}

### Troubleshooting

Q: I dragged a spine asset into my scene and I don't see any object, it's just an empty entity

A: Make sure to click "Add Skin" and select a variant of the spine to apply! Some spines don't start with a default skin.

Q: My script crashes when I call animation methods on my Spine\_Animator

A: If your script and the `Spine_Animator` start at the same time, call `spine.awaken()` before calling any animation methods.

Q: I changed skins but nothing happened

A: After any skin change (`set_skin`, `enable_skin`, `disable_skin`, `disable_all_skins`), you must call `refresh_skins()`.

### Custom Player Skins/Animations

If you want to add custom animations to your player, you can do so by merging the base All Out player spine rig together with a custom rig containing more animations. We can provide many of these; just [Developer Support](/going-all-out/developer-support.md).

### The Spine Rig Format

Spine rigs include:

* A skeleton file (`.spine`) with the bones, skins, animations, and structure.
* An atlas file (`.atlas`) that maps attachments to images.
* Every image page named by the atlas. A one-page export usually has one `.png`.

### Creating your own animations

{% hint style="warning" %}
Currently to make your own animations or rigs, you'll need to own a copy of Esoteric Spine. However, we provide a massive library of existing spine rigs and animations as part of the [Asset Catalog](/using-the-editor/asset-catalog.md) for you to use and we recommend starting there!
{% endhint %}

#### Export Format

To use a custom Spine rig in All Out, export the standard Spine bundle into your `res` folder:

* `something.spine`
* `something.atlas`
* Every image file named by `something.atlas`

If you're unsure about export settings, start from an existing All Out Spine asset from the Asset Catalog and mirror its structure or reach out to us!
