> 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/all-out-docs/docs-es/conceptos-basicos-del-motor/spine.md).

# Animaciones Spine

Spine es cómo funcionan los personajes y props animados en All Out (cofres, animales, props, rigs de VFX y el jugador).

Para que Spine se sienta más simple, mantén este modelo mental:

* **Rig de Spine (`.spine`)**: el “plano” (huesos + nombres de animaciones + nombres de skins)
* **`Spine_Animator`**: un componente en una entidad que renderiza/actualiza un rig de Spine en el mundo
* **`Spine_Instance`**: una instancia independiente (sin componente) — se usa para animaciones de UI o control manual de la vida útil
* **Skins**: “atuendos/variantes” que deciden qué imágenes son visibles
* **Animaciones**: líneas de tiempo con nombre (p. ej. `"idle"`, `"walk"`, `"open"`)

### Cuándo usarlo `Spine_Animator` vs `Spine_Instance`

* Usa **`Spine_Animator`** para cualquier cosa que exista en el mundo (props, NPCs, interactuables).
* Usa **`Spine_Instance`** directamente cuando quieras dibujar un Spine en UI o necesites control manual de la vida útil.

El motor actualiza un `Spine_Animator` automáticamente. Una instancia independiente debe ser actualizada y destruida por tu código.

{% hint style="warning" %}
Si llamas a `Spine_Instance.create()`, debes llamar a `instance.destroy()` cuando hayas terminado (si no, provocarás fugas).
{% endhint %}

### Referencia de la API de Spine (CSL)

```go
// Componente — adjunto a entidades del mundo
Spine_Animator :: class : Component {
    depth_offset: float;
    layer: s32;
    mask_in_shadow: bool;
    instance: Spine_Instance #read_only;

    // Visuales
    color_multiplier: v4;
    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();

    // Desplazamientos locales de huesos (avanzado)
    get_bone_local_position :: method(bone_name: string) -> v2;
    set_bone_local_position :: method(bone_name: string, position: v2);

    // Opcional: dirigir las animaciones mediante una máquina de estados
    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` hereda `Component.awaken()`. Cuando tu script y el animador empiezan en la misma entidad, llama a `awaken()` antes de acceder a la instancia del animador.

### Añadir objetos animados a tu mundo

Hay 3 formas de añadir objetos animados a tu escena, todas crearán un componente Spine\_Animator.

* Arrastra cualquier asset animado desde el [Catálogo de recursos](/all-out-docs/docs-es/uso-del-editor/asset-catalog.md) a tu escena
* Crea una nueva entidad y añade el componente Spine\_Animator
  * Establece el `Skeleton Data Asset` campo en un archivo .spine de tus assets
* Añade un spine animator a tu mundo usando scripting (cubierto en los ejemplos)

{% hint style="info" %}
Si tu código y el `Spine_Animator` están en la misma entidad y empiezan al mismo tiempo, llama a `spine.awaken()` antes de llamar a cualquier método de animación.
{% endhint %}

### Ejemplos

#### Cofre abrible

Un cofre interactuable que reproduce una animación de “open” y luego permanece abierto:

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

    ao_start :: method() {
        this.set_listener(this);
        this.set_text("Abrir");
        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("Abierto");

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

Los nombres de las animaciones y skins deben coincidir con los nombres del rig.

#### Pollo caminante

Un simple bucle de "idle vs walk" basado en el movimiento:

```go
Chicken :: class : Component {
    speed: float = 1.5 @ao_serialize;
    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) {
        // Movimiento de ejemplo (patrulla)
        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);
        }
    }
}
```

#### Coche conducible

Si quieres comportamiento "conducible", el enfoque más simple es controlar el *movimiento* con tu propia lógica de gameplay, y controlar la *animación* según si el coche se está moviendo.

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

    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" %}
Si estás alternando solo entre dos animaciones en bucle, las llamadas directas a `set_animation` suelen ser más simples que construir una máquina de estados.
{% endhint %}

### Skins (variantes/atuendos)

Algunos spines no empiezan con una skin predeterminada. Si tu entidad es "invisible", probablemente necesites elegir una skin.

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

// Opción A: establecer una sola skin
spine.set_skin("default");
spine.refresh_skins();

// Opción B: combinar múltiples skins
spine.disable_all_skins();
spine.enable_skin("body");
spine.enable_skin("hat");
spine.refresh_skins();
```

{% hint style="warning" %}
Llama siempre a `refresh_skins()` después de cambiar las skins.
{% endhint %}

### Eventos de animación

Usa callbacks cuando un evento de línea de tiempo de Spine deba disparar gameplay o audio. Para un `Spine_Animator` componente, registra callbacks en su `instancia`.

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

El callback del evento se ejecuta en la ruta predicha compartida. Llama directamente a la lógica de gameplay y a los SFX; no lo protejas con `Game.is_server()` o `is_local()`.

## Instancias independientes y `UI.spine`

La API independiente proporciona vida útil y actualizaciones de animación manuales:

```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();

    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));
}
```

Mantén la instancia en el estado persistente de la UI del jugador. Créala una vez, actualízala y dibújala desde `ao_late_update`, luego destrúyela desde `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;
        }
    }
}
```

No crees la instancia dentro de `ao_late_update`; eso reiniciaría su animación en cada frame y provocaría una fuga de la instancia anterior.

### Máquinas de estados (opcional, para lógica de animación compleja)

Puedes mantener la lógica de animación simple llamando a `set_animation` directamente. Si tienes muchos estados (idle/run/attack/hit/death), una `State_Machine` ayuda a definir las transiciones una vez y luego solo establecer variables/disparadores.

La idea clave: **los nombres de los estados deben coincidir con los nombres de las animaciones** en tu archivo Spine.

#### Qué hace una máquina de estados

Piensa en una máquina de estados como un pequeño “controlador de animaciones”:

* Defines **estados** (cada nombre de estado debe coincidir con un nombre de animación de Spine)
* Defines **variables** (`bool`, `disparador`, `int`, `float`)
* Defines **transiciones** entre estados según esas variables
* En tiempo de ejecución, solo actualizas variables (la máquina de estados elige la animación)

#### Ejemplo mínimo: idle/walk + disparador de ataque

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

    ao_start :: method() {
        // 1) Crea la máquina de estados + variables
        state_machine = State_Machine.create();
        moving_var := state_machine.create_variable("is_moving", .BOOL);
        attack_var := state_machine.create_variable("attack", .TRIGGER);

        // 2) Crea una capa (se mapea a un track de Spine, normalmente 0)
        layer := state_machine.create_layer("main", 0);

        // 3) Crea estados (los nombres deben coincidir con las animaciones del rig de Spine)
        idle := layer.create_state("idle", true);
        walk := layer.create_state("walk", true);
        attack := layer.create_state("attack", false);
        layer.set_initial_state(idle);

        // 4) Transiciones de movimiento
        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) Ataque desde cualquier estado, luego volver a idle cuando termine
        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) Adjunta al animador
        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" %}
Si pasas `transfer_ownership = true` a `set_state_machine`, el animador destruirá la máquina de estados por ti. De lo contrario, debes destruirla tú mismo.
{% endhint %}

### Solución de problemas

P: Arrastré un asset de spine a mi escena y no veo ningún objeto, solo una entidad vacía

R: Asegúrate de hacer clic en "Add Skin" y seleccionar una variante del spine para aplicar. Algunos spines no empiezan con una skin predeterminada.

P: Mi script falla cuando llamo a métodos de animación en mi Spine\_Animator

R: Si tu script y el `Spine_Animator` empiezan al mismo tiempo, llama a `spine.awaken()` antes de llamar a cualquier método de animación.

P: Cambié las skins pero no pasó nada

R: Después de cualquier cambio de skin (`set_skin`, `enable_skin`, `disable_skin`, `disable_all_skins`), debes llamar a `refresh_skins()`.

### Skins/animaciones personalizadas del jugador

Si quieres añadir animaciones personalizadas a tu jugador, puedes hacerlo fusionando el rig base de Spine de All Out con un rig personalizado que contenga más animaciones. Podemos proporcionar muchas de estas; solo [Soporte para desarrolladores](/all-out-docs/docs-es/dandolo-todo/developer-support.md).

### El formato del rig de Spine

Los rigs de Spine incluyen:

* Un archivo skeleton (`.spine`) con los huesos, skins, animaciones y estructura.
* Un archivo atlas (`.atlas`) que asigna los attachments a imágenes.
* Cada página de imagen nombrada por el atlas. Una exportación de una sola página normalmente tiene una `.png`.

### Creando tus propias animaciones

{% hint style="warning" %}
Actualmente, para crear tus propias animaciones o rigs, necesitarás tener una copia de Esoteric Spine. Sin embargo, proporcionamos una enorme biblioteca de rigs y animaciones de Spine existentes como parte de la [Catálogo de recursos](/all-out-docs/docs-es/uso-del-editor/asset-catalog.md) para que los uses, ¡y recomendamos empezar por ahí!
{% endhint %}

#### Formato de exportación

Para usar un rig de Spine personalizado en All Out, exporta el paquete estándar de Spine en tu `res` carpeta:

* `something.spine`
* `something.atlas`
* Cada archivo de imagen nombrado por `something.atlas`

Si no estás seguro sobre la configuración de exportación, empieza con un asset de Spine existente de All Out desde el Catálogo de Assets y replica su estructura o ¡contáctanos!
