Machines à états AnimationTree
dans Godot 4
Le guide complet des machines à états, transitions, blend trees, OneShot, travel() vs conditions — avec de vrais exemples de code GDScript.
Table des matières
- Introduction
- Prérequis
- Configuration de base
- Les bases des machines à états
- Contrôler la machine à états depuis le code
- travel() vs conditions
- Blend trees
- Types de nœuds courants
- Motif OneShot (attaques, emotes)
- Exemple pratique : contrôleur de personnage complet
- Dépannage
- Automatiser avec Godot MCP Pro
1. Introduction
AnimationTree est le système de Godot 4 pour le mélange d'animations complexes et les transitions d'états. Si vous avez déjà essayé de gérer plusieurs animations avec des appels AnimationPlayer.play() disséminés dans votre code, vous savez à quel point cela devient vite ingérable. AnimationTree résout ce problème avec un graphe visuel d'états et de transitions.
Bien qu'il soit incroyablement puissant, la documentation officielle d'AnimationTree est succincte et laisse souvent les développeurs dans le flou. Ce guide couvre tout, de la configuration de base aux blend trees avancés, avec du vrai code GDScript que vous pouvez copier directement dans votre projet.
Machines à états, transitions, blend spaces (1D et 2D), OneShot pour les attaques, travel() vs conditions, et un motif complet de contrôleur de personnage.
2. Prérequis
Avant de configurer votre AnimationTree, vous avez besoin :
- D'un nœud
AnimationPlayercontenant au minimum les animations Idle, Walk, Run et Jump déjà créées - L'AnimationPlayer doit être un frère ou un enfant du nœud auquel vous ajoutez l'AnimationTree (généralement, les deux sont des enfants du nœud racine de votre personnage)
- De Godot 4.x (ce guide utilise l'API de Godot 4 — l'API AnimationTree a considérablement changé depuis Godot 3)
Dans Godot 4, AnimationTree.animation_player a été remplacé par AnimationTree.anim_player. Le chemin du paramètre de lecture a également changé. Si vous migrez depuis Godot 3, consultez le guide de migration officiel.
3. Configuration de base
La configuration d'un AnimationTree se fait en quatre étapes :
-
Ajoutez un nœud AnimationTree
— Ajoutez-le comme enfant de votre personnage (par exemple,
CharacterBody2DouCharacterBody3D), aux côtés de votre AnimationPlayer. -
Définissez
anim_player— Dans l'inspecteur, faites pointer la propriété « Anim Player » vers votre nœud AnimationPlayer. -
Définissez
tree_root— Cliquez sur la propriété « Tree Root » dans l'inspecteur et créez un nouveauAnimationNodeStateMachine. -
Définissez
active = true— Cochez la case « Active » dans l'inspecteur, ou définissez-la dans le code.
Votre arbre de scène devrait ressembler à ceci :
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 pratique, vous configurez presque toujours l'AnimationTree dans l'éditeur. Le code ci-dessus est présenté par souci d'exhaustivité, mais vous n'avez généralement besoin que de anim_tree.active = true dans votre script (et même cela peut être défini dans l'éditeur).
4. Les bases des machines à états
Une machine à états dans AnimationTree fonctionne selon un concept simple : les états représentent des animations, et les transitions définissent les conditions de passage de l'un à l'autre.
Ajouter des états
Une fois que le tree_root de votre AnimationTree est un AnimationNodeStateMachine, double-cliquez dessus dans l'inspecteur pour ouvrir l'éditeur de machine à états :
- Faites un clic droit dans la zone du graphe et sélectionnez Add Animation
- Choisissez une animation depuis votre AnimationPlayer (Idle, Walk, Run, Jump, etc.)
- Répétez pour chaque état d'animation dont vous avez besoin
Start est le point d'entrée — la première transition commence toujours ici. End est optionnel et signale que la machine à états a terminé (utile pour les machines à états imbriquées).
Ajouter des transitions
Pour créer une transition entre deux états :
- Cliquez sur le nœud d'état source
- Faites glisser vers l'état de destination pour créer une flèche de transition
- Cliquez sur la flèche de transition pour la configurer dans l'inspecteur
Propriétés de transition
| Propriété | Description |
|---|---|
advance_mode |
Auto — se déclenche lorsque sa condition est vraie. Enabled — toujours disponible pour travel(). Disabled — bloquée. |
advance_condition |
Nom d'un paramètre booléen (par exemple, is_moving). Lorsqu'il est vrai, la transition se déclenche automatiquement. |
xfade_time |
Durée du fondu enchaîné en secondes. Mélange fluide entre les animations. Typique : 0,1 – 0,3 s. |
switch_mode |
Immediate — bascule immédiatement. Sync — aligne la position de lecture. AtEnd — attend la fin de l'animation en cours. |
Une configuration typique pour un personnage de jeu de plateforme :
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. Contrôler la machine à états depuis le code
Il existe deux principales façons de piloter la machine à états : définir des paramètres de condition (transitions automatiques) et appeler travel() (transitions manuelles). Vous pouvez combiner les deux approches.
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")
Les paramètres suivent le motif parameters/conditions/<condition_name> pour les conditions définies sur les transitions. Le nom de la condition doit correspondre à l'advance_condition que vous avez défini sur la transition dans l'éditeur.
6. travel() vs conditions
Basé sur les conditions (recommandé pour la locomotion)
Définissez des paramètres booléens à chaque frame et laissez les transitions se déclencher automatiquement. C'est plus déclaratif et cela garde votre code propre. La machine à états gère pour vous la logique de transition, les fondus enchaînés et les cas limites.
# 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() (recommandé pour les actions ponctuelles)
travel() demande une transition d'état. Il respecte les règles de transition — s'il n'existe aucun chemin valide entre l'état actuel et la cible, l'appel est ignoré. On peut donc l'appeler à répétition sans risque. Utilisez-le pour des déclencheurs ponctuels comme les attaques, les emotes ou les animations de cinématique.
# 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)
Les conditions pour les états continus (idle, walk, run, fall). travel() pour les états déclenchés par un événement (attaque, esquive, interaction). De nombreux projets utilisent les deux : les conditions pour la locomotion, travel() pour les actions de combat.
7. Blend trees
Les blend trees vous permettent d'interpoler en douceur entre plusieurs animations en fonction d'une valeur continue, plutôt que de basculer brutalement entre des états discrets. C'est parfait pour le mélange des vitesses de marche/course et le mouvement directionnel.
BlendSpace1D
Un mélange 1D entre deux animations ou plus le long d'un seul axe. Usage courant : mélanger Walk et Run en fonction de la vitesse de déplacement.
Dans l'éditeur, créez un nœud BlendSpace1D à l'intérieur de votre machine à états (ou comme tree root autonome). Ajoutez des points d'animation :
# 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 mélange 2D utilisant deux axes. Parfait pour le mouvement à 8 directions ou les jeux en vue de dessus où le personnage peut se déplacer dans n'importe quelle direction.
# 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 prend en charge plusieurs modes de mélange : la triangulation par défaut fonctionne bien dans la plupart des cas. Vous pouvez aussi choisir le mode discret (accroche au point le plus proche) si vous voulez une animation de style pixel-art sans interpolation.
8. Types de nœuds courants
AnimationTree prend en charge plusieurs types de nœuds qui peuvent être combinés pour créer des comportements d'animation complexes :
| Type de nœud | Cas d'usage |
|---|---|
AnimationNodeStateMachine |
Machine à états avec transitions. Le nœud racine le plus courant. Les états peuvent être des animations ou des machines à états imbriquées. |
AnimationNodeBlendSpace1D |
Mélange 1D le long d'un axe. Vitesse de marche/course, angle de visée, etc. |
AnimationNodeBlendSpace2D |
Mélange 2D utilisant deux axes. Mouvement directionnel, mélange de déplacement latéral. |
AnimationNodeBlendTree |
Un graphe d'opérations de mélange. Combinez plusieurs nœuds de mélange avec une logique personnalisée. |
AnimationNodeAdd2 |
Mélange additif. Superposez une animation par-dessus une autre (par exemple, un décalage de visée sur la marche). |
AnimationNodeTimeScale |
Contrôle de la vitesse. Fait jouer une animation plus vite ou plus lentement à l'exécution. |
AnimationNodeOneShot |
Superposition d'animation ponctuelle. Parfait pour les attaques, emotes, réactions aux coups. |
AnimationNodeTransition |
Bascule entre plusieurs entrées avec des fondus enchaînés. Alternative aux machines à états pour des configurations plus simples. |
9. Motif OneShot (attaques, emotes)
Le nœud OneShot est l'un des motifs les plus utiles d'AnimationTree. Il joue une animation ponctuelle par-dessus votre animation de base (comme jouer un coup d'attaque tout en marchant), puis revient automatiquement à l'animation de base.
Configuration dans un BlendTree
Pour utiliser OneShot, la racine de votre AnimationTree (ou un état à l'intérieur) doit être 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.
Déclenchement depuis le code
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 requête OneShot
| Constante | Effet |
|---|---|
ONE_SHOT_REQUEST_FIRE |
Démarre la lecture de l'animation ponctuelle |
ONE_SHOT_REQUEST_ABORT |
Annule l'animation ponctuelle et revient immédiatement à la base |
ONE_SHOT_REQUEST_FADE_OUT |
Fait disparaître l'animation ponctuelle en fondu (utilise la propriété fadeout_time) |
Pour les systèmes de combo, utilisez plusieurs nœuds OneShot en séquence ou une machine à états imbriquée dans l'entrée shot du OneShot avec les états Attack1 → Attack2 → Attack3.
10. Exemple pratique : contrôleur de personnage complet
Voici un script complet de personnage de jeu de plateforme 2D qui combine la locomotion par machine à états avec une attaque OneShot. C'est un motif prêt pour la production que vous pouvez adapter à votre propre projet.
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
)
Racine = BlendTree. À l'intérieur : un nœud StateMachine (avec les états Idle/Walk/Run/Jump/Fall) connecté à un nœud OneShot (« AttackOneShot »), qui se connecte à l'Output. L'animation d'attaque se connecte à l'entrée « shot » du OneShot.
11. Dépannage
Vérifiez que active = true sur l'AnimationTree et que la propriété anim_player pointe vers un AnimationPlayer valide. Vérifiez également que l'AnimationPlayer possède réellement des animations portant les noms que vous référencez.
Assurez-vous qu'un chemin de transition valide existe entre l'état actuel et l'état cible. travel() échoue silencieusement si aucun chemin n'existe. Utilisez state_machine.get_current_node() pour déboguer l'état dans lequel vous vous trouvez réellement.
Vérifiez que votre valeur blend_position se situe dans la plage des points de votre blend space. Si vos points sont à 0.0 et 1.0, une valeur de 5.0 ne fonctionnera pas comme prévu. Utilisez clamp().
Définissez active = true soit dans l'éditeur (case à cocher de l'inspecteur), soit dans votre fonction _ready(). L'AnimationTree ne fait rien tant qu'il n'est pas activé.
Vérifiez bien que : (1) l'advance_mode de la transition est réglé sur Auto, (2) le nom de l'advance_condition correspond exactement à celui défini dans le code (sensible à la casse), et (3) vous définissez le paramètre à chaque frame dans _physics_process().
AnimationTree ne gère que la lecture des animations. La logique de déplacement (velocity, move_and_slide()) est séparée et doit être implémentée dans le _physics_process() de votre script.
Vous voulez que l'IA construise votre AnimationTree ?
Godot MCP Pro peut créer des machines à états, ajouter des états et des transitions, configurer des blend trees et définir des paramètres — le tout à partir d'un seul prompt. Dites à votre assistant IA le comportement d'animation que vous voulez, et il construit l'AnimationTree complet pour vous.
- create_animation_tree
- add_state_machine_state
- add_state_machine_transition
- set_blend_tree_node
- set_tree_parameter
- get_animation_tree_structure