Máquinas de estados de AnimationTree
en Godot 4

La guía completa de máquinas de estados, transiciones, blend trees, OneShot, travel() frente a condiciones — con ejemplos reales de código GDScript.

1. Introducción

AnimationTree es el sistema de Godot 4 para el blending complejo de animaciones y las transiciones de estado. Si alguna vez has intentado gestionar varias animaciones con llamadas a AnimationPlayer.play() repartidas por todo el código, sabes lo rápido que se vuelve inmanejable. AnimationTree resuelve esto con un grafo visual de estados y transiciones.

Pese a ser increíblemente potente, la documentación oficial de AnimationTree es escasa y a menudo deja a los desarrolladores a ciegas. Esta guía recorre todo – desde la configuración básica hasta los blend trees avanzados – con código GDScript real que puedes copiar directamente en tu proyecto.

Lo que aprenderás

Máquinas de estados, transiciones, blend spaces (1D y 2D), OneShot para ataques, travel() frente a condiciones y un patrón completo de controlador de personaje.

2. Requisitos previos

Antes de configurar tu AnimationTree, necesitas:

  • Un nodo AnimationPlayer con al menos las animaciones Idle, Walk, Run y Jump ya creadas
  • El AnimationPlayer debe ser un nodo hermano o hijo del nodo donde añades el AnimationTree (normalmente ambos son hijos del nodo raíz de tu personaje)
  • Godot 4.x (esta guía usa la API de Godot 4 — la API de AnimationTree cambió notablemente respecto a Godot 3)
Godot 3 frente a Godot 4

En Godot 4, AnimationTree.animation_player fue reemplazado por AnimationTree.anim_player. La ruta del parámetro de reproducción también cambió. Si estás migrando desde Godot 3, consulta la guía de migración oficial.

3. Configuración básica

Configurar un AnimationTree consta de cuatro pasos:

  1. Añadir un nodo AnimationTree — Añádelo como hijo de tu personaje (p. ej., CharacterBody2D o CharacterBody3D), junto a tu AnimationPlayer.
  2. Definir anim_player — En el Inspector, apunta la propiedad "Anim Player" a tu nodo AnimationPlayer.
  3. Definir tree_root — Haz clic en la propiedad "Tree Root" del Inspector y crea una nueva AnimationNodeStateMachine.
  4. Establecer active = true — Marca la casilla "Active" en el Inspector o hazlo desde el código.

Tu árbol de escena debería tener este aspecto:

CharacterBody2D (or CharacterBody3D)
  +-- Sprite2D (or Sprite3D)
  +-- CollisionShape2D
  +-- AnimationPlayer      <-- has Idle, Walk, Run, Jump animations
  +-- AnimationTree         <-- points to AnimationPlayer above
GDScript
# Minimal code setup (usually done via the editor instead):
@onready var anim_tree: AnimationTree = $AnimationTree

func _ready() -> void:
    # If you set these in the Inspector, you don't need this code
    anim_tree.anim_player = ^"../AnimationPlayer"
    anim_tree.tree_root = AnimationNodeStateMachine.new()
    anim_tree.active = true
Editor frente a código

En la práctica, casi siempre configuras el AnimationTree en el editor. El código anterior se muestra por completitud, pero normalmente solo necesitas anim_tree.active = true en tu script (e incluso eso puede establecerse en el editor).

4. Fundamentos de la máquina de estados

Una máquina de estados en AnimationTree se basa en un concepto sencillo: los estados representan animaciones y las transiciones definen las condiciones para cambiar entre ellos.

Añadir estados

Una vez que el tree_root de tu AnimationTree es una AnimationNodeStateMachine, haz doble clic sobre ella en el Inspector para abrir el editor de la máquina de estados:

  1. Haz clic derecho en el área del grafo y selecciona Add Animation
  2. Elige una animación de tu AnimationPlayer (Idle, Walk, Run, Jump, etc.)
  3. Repite el proceso para cada estado de animación que necesites
Estados especiales

Start es el punto de entrada — la primera transición siempre comienza aquí. End es opcional e indica que la máquina de estados ha terminado (útil para máquinas de estados anidadas).

Añadir transiciones

Para crear una transición entre dos estados:

  1. Haz clic en el nodo del estado de origen
  2. Arrastra hasta el estado de destino para crear una flecha de transición
  3. Haz clic en la flecha de transición para configurarla en el Inspector

Propiedades de la transición

Propiedad Descripción
advance_mode Auto — se dispara cuando su condición es verdadera. Enabled — siempre disponible para travel(). Disabled — bloqueada.
advance_condition Nombre de un parámetro booleano (p. ej., is_moving). Cuando es verdadero, la transición se dispara automáticamente.
xfade_time Duración del crossfade en segundos. Blending suave entre animaciones. Habitual: 0,1 – 0,3 s.
switch_mode Immediate — cambiar de inmediato. Sync — igualar la posición de reproducción. AtEnd — esperar a que termine la animación actual.

Una configuración típica para un personaje de plataformas:

Start --> Idle
Idle  --> Walk   (condition: is_moving)
Walk  --> Idle   (condition: is_idle)
Idle  --> Jump   (condition: is_jumping)
Walk  --> Jump   (condition: is_jumping)
Jump  --> Fall   (condition: is_falling)
Fall  --> Idle   (condition: is_on_floor, switch_mode: Immediate)

5. Controlar la máquina de estados desde el código

Hay dos formas principales de gobernar la máquina de estados: establecer parámetros de condición (transiciones automáticas) y llamar a travel() (transiciones manuales). Puedes combinar ambos enfoques.

GDScript
extends CharacterBody2D

@onready var anim_tree: AnimationTree = $AnimationTree
@onready var state_machine: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback")

func _physics_process(delta: float) -> void:
    # ... movement logic here ...
    move_and_slide()
    _update_animation_parameters()

func _update_animation_parameters() -> void:
    # Approach 1: Set condition parameters — transitions fire automatically
    anim_tree.set("parameters/conditions/is_moving", velocity.length() > 10.0)
    anim_tree.set("parameters/conditions/is_idle", velocity.length() <= 10.0)
    anim_tree.set("parameters/conditions/is_on_floor", is_on_floor())
    anim_tree.set("parameters/conditions/is_jumping", velocity.y < 0 and not is_on_floor())
    anim_tree.set("parameters/conditions/is_falling", velocity.y > 0 and not is_on_floor())

    # Approach 2: Use travel() for direct control
    # if velocity.length() > 10.0:
    #     state_machine.travel("Walk")
    # else:
    #     state_machine.travel("Idle")
Rutas de parámetros

Los parámetros siguen el patrón parameters/conditions/<condition_name> para las condiciones definidas en las transiciones. El nombre de la condición debe coincidir con la advance_condition que estableciste en la transición dentro del editor.

6. travel() frente a condiciones

Basado en condiciones (recomendado para locomoción)

Establece parámetros booleanos en cada frame y deja que las transiciones se disparen automáticamente. Esto es más declarativo y mantiene tu código limpio. La máquina de estados se encarga por ti de la lógica de transición, los crossfades y los casos límite.

# Declarative: just describe the current state of the world
anim_tree.set("parameters/conditions/is_moving", velocity.length() > 10.0)
anim_tree.set("parameters/conditions/is_on_floor", is_on_floor())

travel() (recomendado para acciones puntuales)

travel() solicita una transición de estado. Respeta las reglas de transición — si no existe una ruta válida desde el estado actual hasta el destino, la llamada se ignora. Por eso es seguro llamarlo repetidamente. Úsalo para disparadores puntuales como ataques, emotes o animaciones de cinemáticas.

# travel() — requests a transition (respects transition rules)
state_machine.travel("Jump")

# Get current state name
var current: StringName = state_machine.get_current_node()
print(current)  # "Idle", "Walk", etc.

# Check if travel is possible
var is_playing: bool = state_machine.is_playing()
print(is_playing)
¿Cuándo usar cada uno?

Condiciones para estados continuos (Idle, Walk, Run, Fall). travel() para estados desencadenados por eventos (ataque, esquiva, interactuar). Muchos proyectos usan ambos: condiciones para la locomoción, travel() para las acciones de combate.

7. Blend Trees

Los blend trees te permiten interpolar suavemente entre varias animaciones a partir de un valor continuo, en lugar de cambiar de golpe entre estados discretos. Es ideal para el blending de velocidad al caminar/correr y para el movimiento direccional.

BlendSpace1D

Un blend 1D entre dos o más animaciones a lo largo de un único eje. Uso habitual: mezclar Walk y Run según la velocidad de movimiento.

En el editor, crea un nodo BlendSpace1D dentro de tu máquina de estados (o como tree root independiente). Añade puntos de animación:

# BlendSpace1D setup (in editor):
# Point 0.0 = Walk animation
# Point 1.0 = Run animation

# Control from code:
var speed_factor: float = clamp(velocity.length() / max_speed, 0.0, 1.0)
anim_tree.set("parameters/WalkRun/blend_position", speed_factor)

BlendSpace2D

Un blend 2D con dos ejes. Perfecto para movimiento en 8 direcciones o para juegos cenitales en los que el personaje puede moverse en cualquier dirección.

# BlendSpace2D setup (in editor):
# Place animations at positions:
#   Idle at (0, 0)
#   WalkRight at (1, 0), WalkLeft at (-1, 0)
#   WalkUp at (0, -1), WalkDown at (0, 1)
#   Diagonals at corners

# Control from code:
var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
anim_tree.set("parameters/Movement/blend_position", input_dir)
Modos de blend

BlendSpace2D admite varios modos de blend: la triangulación predeterminada funciona bien en la mayoría de los casos. También puedes elegir el modo discreto (se ajusta al punto más cercano) si quieres animación de pixel-art sin interpolación.

8. Tipos de nodos comunes

AnimationTree admite varios tipos de nodos que pueden combinarse para crear comportamientos de animación complejos:

Tipo de nodo Caso de uso
AnimationNodeStateMachine Máquina de estados con transiciones. El nodo raíz más común. Los estados pueden ser animaciones o máquinas de estados anidadas.
AnimationNodeBlendSpace1D Blend 1D a lo largo de un eje. Velocidad de caminar/correr, ángulo de apuntado, etc.
AnimationNodeBlendSpace2D Blend 2D con dos ejes. Movimiento direccional, blending de desplazamiento lateral.
AnimationNodeBlendTree Un grafo de operaciones de blend. Combina varios nodos de blend con lógica propia.
AnimationNodeAdd2 Blending aditivo. Superpone una animación sobre otra (p. ej., un offset de apuntado sobre el caminar).
AnimationNodeTimeScale Control de velocidad. Hace que una animación se reproduzca más rápido o más lento en tiempo de ejecución.
AnimationNodeOneShot Superposición de animación puntual. Perfecto para ataques, emotes y reacciones a impactos.
AnimationNodeTransition Cambia entre varias entradas con crossfades. Alternativa a las máquinas de estados para configuraciones más sencillas.

9. Patrón OneShot (ataques, emotes)

El nodo OneShot es uno de los patrones más útiles de AnimationTree. Reproduce una animación puntual sobre tu animación base (como reproducir un golpe de ataque mientras caminas) y después vuelve automáticamente a la animación base.

Configuración en el BlendTree

Para usar OneShot, la raíz de tu AnimationTree (o un estado dentro de ella) debe ser un BlendTree:

# BlendTree graph setup:
#
#   [StateMachine] ---> [OneShot "AttackOneShot"] ---> [Output]
#   (base locomotion)      ^
#                          |
#                   [Animation "Attack"]
#                   (shot input)
#
# The StateMachine provides the base (idle/walk/run).
# The Attack animation is connected to the OneShot's "shot" input.

Disparar desde el código

GDScript
extends CharacterBody2D

@onready var anim_tree: AnimationTree = $AnimationTree

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("attack"):
        _play_attack()

func _play_attack() -> void:
    # Fire the one-shot animation
    anim_tree.set(
        "parameters/AttackOneShot/request",
        AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
    )

func _process(delta: float) -> void:
    # Check if the one-shot is currently active
    var is_attacking: bool = anim_tree.get("parameters/AttackOneShot/active")
    if is_attacking:
        # Optionally disable movement during attack
        pass

Constantes de solicitud de OneShot

Constante Efecto
ONE_SHOT_REQUEST_FIRE Inicia la reproducción de la animación OneShot
ONE_SHOT_REQUEST_ABORT Cancela el OneShot y vuelve de inmediato a la base
ONE_SHOT_REQUEST_FADE_OUT Atenúa el OneShot (usa la propiedad fadeout_time)
Ataques múltiples

Para sistemas de combos, usa varios nodos OneShot en secuencia o una máquina de estados anidada en la entrada "shot" del OneShot con los estados Attack1 → Attack2 → Attack3.

10. Ejemplo práctico: controlador de personaje completo

Aquí tienes un script completo de personaje de plataformas 2D que combina la locomoción con máquina de estados con un ataque OneShot. Es un patrón listo para producción que puedes adaptar a tu propio proyecto.

GDScript — player.gd
extends CharacterBody2D

const SPEED := 200.0
const JUMP_VELOCITY := -350.0
const SPRINT_MULTIPLIER := 1.6

@onready var anim_tree: AnimationTree = $AnimationTree
@onready var playback: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback")
@onready var sprite: Sprite2D = $Sprite2D

var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")

func _ready() -> void:
    anim_tree.active = true

func _physics_process(delta: float) -> void:
    _apply_gravity(delta)
    _handle_jump()
    _handle_movement()
    move_and_slide()
    _update_animation()

func _apply_gravity(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravity * delta

func _handle_jump() -> void:
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY

func _handle_movement() -> void:
    var direction := Input.get_axis("move_left", "move_right")
    var is_sprinting := Input.is_action_pressed("sprint")
    var current_speed := SPEED * (SPRINT_MULTIPLIER if is_sprinting else 1.0)

    if direction != 0.0:
        velocity.x = direction * current_speed
        sprite.flip_h = direction < 0.0
    else:
        velocity.x = move_toward(velocity.x, 0.0, SPEED)

func _update_animation() -> void:
    # Skip animation updates during attack
    var is_attacking: bool = anim_tree.get("parameters/AttackOneShot/active")
    if is_attacking:
        return

    if not is_on_floor():
        if velocity.y < 0:
            playback.travel("Jump")
        else:
            playback.travel("Fall")
    elif abs(velocity.x) > 10.0:
        if Input.is_action_pressed("sprint"):
            playback.travel("Run")
        else:
            playback.travel("Walk")
    else:
        playback.travel("Idle")

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("attack") and is_on_floor():
        anim_tree.set(
            "parameters/AttackOneShot/request",
            AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
        )
Estructura del AnimationTree para este ejemplo

Root = BlendTree. Dentro: un nodo StateMachine (con los estados Idle/Walk/Run/Jump/Fall) conectado a un nodo OneShot ("AttackOneShot"), que a su vez se conecta al Output. La animación de ataque se conecta a la entrada "shot" del OneShot.

11. Resolución de problemas

"La animación no se reproduce"

Comprueba que active = true esté establecido en el AnimationTree y que la propiedad anim_player apunte a un AnimationPlayer válido. Verifica también que el AnimationPlayer contenga realmente animaciones con los nombres que estás referenciando.

"travel() no hace nada"

Asegúrate de que exista una ruta de transición válida entre el estado actual y el estado destino. travel() falla silenciosamente si no existe una ruta. Usa state_machine.get_current_node() para depurar en qué estado te encuentras realmente.

"El blend no funciona"

Comprueba que tu valor de blend_position esté dentro del rango de los puntos de tu blend space. Si tus puntos están en 0.0 y 1.0, un valor de 5.0 no funcionará como esperas. Usa clamp().

"Warning: AnimationTree is not active"

Establece active = true en el editor (casilla del Inspector) o en tu función _ready(). El AnimationTree no hace nada hasta que se activa.

"La transición basada en condiciones no se dispara"

Verifica con cuidado que: (1) el advance_mode de la transición esté en Auto, (2) el nombre de la advance_condition coincida exactamente con lo que estableciste en el código (distingue mayúsculas y minúsculas) y (3) estés estableciendo el parámetro en cada frame dentro de _physics_process().

"La animación se reproduce, pero el personaje no se mueve"

El AnimationTree se ocupa exclusivamente de la reproducción de animaciones. La lógica de movimiento (velocity, move_and_slide()) es independiente y debe implementarse en el _physics_process() de tu script.

¿Quieres que la IA construya tu AnimationTree?

Godot MCP Pro puede crear máquinas de estados, añadir estados y transiciones, configurar blend trees y establecer parámetros — todo a partir de un único prompt. Dile a tu asistente de IA qué comportamiento de animación quieres y él construirá el AnimationTree completo por ti.

  • create_animation_tree
  • add_state_machine_state
  • add_state_machine_transition
  • set_blend_tree_node
  • set_tree_parameter
  • get_animation_tree_structure
Consigue Godot MCP Pro — $15