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.
Índice de contenidos
- Introducción
- Requisitos previos
- Configuración básica
- Fundamentos de la máquina de estados
- Controlar la máquina de estados desde el código
- travel() frente a condiciones
- Blend Trees
- Tipos de nodos comunes
- Patrón OneShot (ataques, emotes)
- Ejemplo práctico: controlador de personaje completo
- Resolución de problemas
- Automatizar con Godot MCP Pro
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.
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
AnimationPlayercon 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)
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:
-
Añadir un nodo AnimationTree
— Añádelo como hijo de tu personaje (p. ej.,
CharacterBody2DoCharacterBody3D), junto a tu AnimationPlayer. -
Definir
anim_player— En el Inspector, apunta la propiedad "Anim Player" a tu nodo AnimationPlayer. -
Definir
tree_root— Haz clic en la propiedad "Tree Root" del Inspector y crea una nuevaAnimationNodeStateMachine. -
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
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:
- Haz clic derecho en el área del grafo y selecciona Add Animation
- Elige una animación de tu AnimationPlayer (Idle, Walk, Run, Jump, etc.)
- Repite el proceso para cada estado de animación que necesites
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:
- Haz clic en el nodo del estado de origen
- Arrastra hasta el estado de destino para crear una flecha de transición
- 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.
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")
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)
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)
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
GDScriptextends 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) |
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.gdextends 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
)
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
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.
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.
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().
Establece active = true en el editor (casilla del Inspector) o en tu función _ready(). El AnimationTree no hace nada hasta que se activa.
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().
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