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.

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.

Ce que vous allez apprendre

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 AnimationPlayer contenant 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)
Godot 3 vs Godot 4

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 :

  1. Ajoutez un nœud AnimationTree — Ajoutez-le comme enfant de votre personnage (par exemple, CharacterBody2D ou CharacterBody3D), aux côtés de votre AnimationPlayer.
  2. Définissez anim_player — Dans l'inspecteur, faites pointer la propriété « Anim Player » vers votre nœud AnimationPlayer.
  3. Définissez tree_root — Cliquez sur la propriété « Tree Root » dans l'inspecteur et créez un nouveau AnimationNodeStateMachine.
  4. 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
Éditeur vs code

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 :

  1. Faites un clic droit dans la zone du graphe et sélectionnez Add Animation
  2. Choisissez une animation depuis votre AnimationPlayer (Idle, Walk, Run, Jump, etc.)
  3. Répétez pour chaque état d'animation dont vous avez besoin
États spéciaux

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 :

  1. Cliquez sur le nœud d'état source
  2. Faites glisser vers l'état de destination pour créer une flèche de transition
  3. 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.

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")
Chemins de paramètres

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)
Quand utiliser l'un ou l'autre ?

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)
Modes de mélange

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

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 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)
Attaques multiples

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.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
        )
Structure AnimationTree pour cet exemple

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

« L'animation ne joue pas »

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.

« travel() ne fait rien »

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.

« Le mélange ne fonctionne pas »

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().

« Warning: AnimationTree is not active »

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é.

« La transition basée sur une condition ne se déclenche pas »

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().

« L'animation joue mais le personnage ne bouge pas »

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
Obtenir Godot MCP Pro — $15