AnimationTree-Statemachines
in Godot 4

Der vollständige Leitfaden zu Statemachines, Übergängen, Blend Trees, OneShot, travel() vs. Bedingungen — mit echten GDScript-Codebeispielen.

1. Einführung

AnimationTree ist das System von Godot 4 für komplexes Animation-Blending und Zustandsübergänge. Wenn du jemals versucht hast, mehrere Animationen mit über den gesamten Code verstreuten AnimationPlayer.play()-Aufrufen zu verwalten, weißt du, wie schnell das unübersichtlich wird. AnimationTree löst das mit einem visuellen Graphen aus Zuständen und Übergängen.

Obwohl AnimationTree unglaublich mächtig ist, ist die offizielle Dokumentation dünn und lässt Entwickler oft im Ungewissen. Dieser Leitfaden führt dich durch alles – von der grundlegenden Einrichtung bis zu fortgeschrittenen Blend Trees – mit echtem GDScript-Code, den du direkt in dein Projekt kopieren kannst.

Was du lernen wirst

Statemachines, Übergänge, Blend Spaces (1D & 2D), OneShot für Angriffe, travel() vs. Bedingungen und ein vollständiges Character-Controller-Muster.

2. Voraussetzungen

Bevor du deinen AnimationTree einrichtest, brauchst du:

  • Einen AnimationPlayer-Node mit mindestens den bereits erstellten Animationen Idle, Walk, Run und Jump
  • Der AnimationPlayer muss ein Geschwister- oder Kind-Node des Nodes sein, an dem du den AnimationTree hinzufügst (typischerweise sind beide Kinder deines Charakter-Root-Nodes)
  • Godot 4.x (dieser Leitfaden verwendet die Godot-4-API — die AnimationTree-API hat sich gegenüber Godot 3 erheblich verändert)
Godot 3 vs. Godot 4

In Godot 4 wurde AnimationTree.animation_player durch AnimationTree.anim_player ersetzt. Auch der Parameterpfad für die Wiedergabe hat sich geändert. Wenn du von Godot 3 migrierst, sieh dir den offiziellen Migrationsleitfaden an.

3. Grundlegende Einrichtung

Die Einrichtung eines AnimationTree erfolgt in vier Schritten:

  1. Einen AnimationTree-Node hinzufügen — Füge ihn als Kind deines Charakters (z. B. CharacterBody2D oder CharacterBody3D) neben deinem AnimationPlayer hinzu.
  2. anim_player festlegen — Zeige im Inspector mit der Eigenschaft "Anim Player" auf deinen AnimationPlayer-Node.
  3. tree_root festlegen — Klicke im Inspector auf die Eigenschaft "Tree Root" und erstelle eine neue AnimationNodeStateMachine.
  4. active = true setzen — Aktiviere im Inspector das Kontrollkästchen "Active" oder setze es im Code.

Dein Szenenbaum sollte so aussehen:

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 vs. Code

In der Praxis konfigurierst du den AnimationTree fast immer im Editor. Der obige Code ist nur der Vollständigkeit halber angegeben, aber typischerweise brauchst du in deinem Skript nur anim_tree.active = true (und selbst das lässt sich im Editor setzen).

4. Statemachine-Grundlagen

Eine Statemachine im AnimationTree beruht auf einem einfachen Konzept: Zustände repräsentieren Animationen und Übergänge definieren die Bedingungen für den Wechsel zwischen ihnen.

Zustände hinzufügen

Sobald der tree_root deines AnimationTree eine AnimationNodeStateMachine ist, doppelklicke im Inspector darauf, um den Statemachine-Editor zu öffnen:

  1. Klicke mit der rechten Maustaste in den Graphen-Bereich und wähle Add Animation
  2. Wähle eine Animation aus deinem AnimationPlayer (Idle, Walk, Run, Jump usw.)
  3. Wiederhole dies für jeden Animationszustand, den du brauchst
Besondere Zustände

Start ist der Einstiegspunkt — der erste Übergang beginnt immer hier. End ist optional und signalisiert, dass die Statemachine abgeschlossen ist (nützlich für verschachtelte Statemachines).

Übergänge hinzufügen

So erstellst du einen Übergang zwischen zwei Zuständen:

  1. Klicke auf den Quell-Zustands-Node
  2. Ziehe zum Ziel-Zustand, um einen Übergangspfeil zu erstellen
  3. Klicke auf den Übergangspfeil, um ihn im Inspector zu konfigurieren

Übergangseigenschaften

Eigenschaft Beschreibung
advance_mode Auto — feuert, wenn die Bedingung wahr ist. Enabled — immer für travel() verfügbar. Disabled — blockiert.
advance_condition Name eines Boolean-Parameters (z. B. is_moving). Wenn wahr, feuert der Übergang automatisch.
xfade_time Crossfade-Dauer in Sekunden. Sanftes Blending zwischen Animationen. Typisch: 0,1 – 0,3 s.
switch_mode Immediate — sofort wechseln. Sync — Wiedergabeposition angleichen. AtEnd — warten, bis die aktuelle Animation endet.

Eine typische Einrichtung für einen Plattformer-Charakter:

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. Die Statemachine aus Code steuern

Es gibt zwei Hauptwege, die Statemachine anzusteuern: das Setzen von Bedingungsparametern (automatische Übergänge) und der Aufruf von travel() (manuelle Übergänge). Du kannst beide Ansätze kombinieren.

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")
Parameterpfade

Parameter folgen dem Muster parameters/conditions/<condition_name> für Bedingungen, die auf Übergängen gesetzt sind. Der Bedingungsname muss mit der advance_condition übereinstimmen, die du im Editor am Übergang festgelegt hast.

6. travel() vs. Bedingungen

Bedingungsbasiert (empfohlen für Fortbewegung)

Setze jeden Frame Boolean-Parameter und lass die Übergänge automatisch feuern. Das ist deklarativer und hält deinen Code sauber. Die Statemachine kümmert sich um Übergangslogik, Crossfades und Randfälle für dich.

# 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() (empfohlen für einmalige Aktionen)

travel() fordert einen Zustandsübergang an. Es respektiert die Übergangsregeln — wenn vom aktuellen Zustand kein gültiger Pfad zum Ziel existiert, wird der Aufruf ignoriert. Dadurch ist es sicher, es wiederholt aufzurufen. Verwende es für einmalige Auslöser wie Angriffe, Emotes oder Cutscene-Animationen.

# 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)
Wann welchen Ansatz?

Bedingungen für kontinuierliche Zustände (Idle, Walk, Run, Fall). travel() für ereignisgesteuerte Zustände (Angriff, Ausweichen, Interagieren). Viele Projekte nutzen beides: Bedingungen für die Fortbewegung, travel() für Kampfaktionen.

7. Blend Trees

Blend Trees ermöglichen es dir, anhand eines kontinuierlichen Werts sanft zwischen mehreren Animationen zu interpolieren, statt hart zwischen diskreten Zuständen umzuschalten. Das ist ideal für das Blending von Geh-/Laufgeschwindigkeit und für gerichtete Bewegung.

BlendSpace1D

Ein 1D-Blend zwischen zwei oder mehr Animationen entlang einer einzigen Achse. Häufige Verwendung: das Blending von Walk und Run basierend auf der Bewegungsgeschwindigkeit.

Erstelle im Editor einen BlendSpace1D-Node innerhalb deiner Statemachine (oder als eigenständigen Tree Root). Füge Animationspunkte hinzu:

# 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

Ein 2D-Blend mit zwei Achsen. Perfekt für 8-Richtungs-Bewegung oder Top-Down-Spiele, in denen sich der Charakter in jede Richtung bewegen kann.

# 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)
Blend-Modi

BlendSpace2D unterstützt mehrere Blend-Modi: Die Standard-Triangulation funktioniert in den meisten Fällen gut. Du kannst auch den diskreten Modus wählen (schnappt zum nächstgelegenen Punkt), wenn du Pixel-Art-Animation ohne Interpolation möchtest.

8. Gängige Node-Typen

AnimationTree unterstützt mehrere Node-Typen, die kombiniert werden können, um komplexes Animationsverhalten zu erzeugen:

Node-Typ Anwendungsfall
AnimationNodeStateMachine Statemachine mit Übergängen. Der häufigste Root-Node. Zustände können Animationen oder verschachtelte Statemachines sein.
AnimationNodeBlendSpace1D 1D-Blend entlang einer Achse. Geh-/Laufgeschwindigkeit, Zielwinkel usw.
AnimationNodeBlendSpace2D 2D-Blend mit zwei Achsen. Gerichtete Bewegung, Strafe-Blending.
AnimationNodeBlendTree Ein Graph aus Blend-Operationen. Kombiniere mehrere Blend-Nodes mit eigener Logik.
AnimationNodeAdd2 Additives Blending. Lege eine Animation über eine andere (z. B. einen Ziel-Offset über das Gehen).
AnimationNodeTimeScale Geschwindigkeitssteuerung. Lässt eine Animation zur Laufzeit schneller oder langsamer abspielen.
AnimationNodeOneShot Einmalige Animations-Überlagerung. Perfekt für Angriffe, Emotes, Trefferreaktionen.
AnimationNodeTransition Wechsel zwischen mehreren Eingängen mit Crossfades. Alternative zu Statemachines für einfachere Setups.

9. OneShot-Muster (Angriffe, Emotes)

Der OneShot-Node ist eines der nützlichsten Muster im AnimationTree. Er spielt eine einmalige Animation über deiner Basisanimation ab (etwa das Abspielen eines Angriffsschwungs während des Gehens) und kehrt anschließend automatisch zur Basisanimation zurück.

Einrichtung im BlendTree

Um OneShot zu verwenden, muss der Root deines AnimationTree (oder ein Zustand darin) ein BlendTree sein:

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

Auslösen aus 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

OneShot-Request-Konstanten

Konstante Wirkung
ONE_SHOT_REQUEST_FIRE Startet die Wiedergabe der OneShot-Animation
ONE_SHOT_REQUEST_ABORT Bricht den OneShot ab und kehrt sofort zur Basis zurück
ONE_SHOT_REQUEST_FADE_OUT Blendet den OneShot aus (verwendet die Eigenschaft fadeout_time)
Mehrere Angriffe

Für Kombosysteme verwende mehrere OneShot-Nodes in Folge oder eine verschachtelte Statemachine im "shot"-Eingang des OneShot mit den Zuständen Attack1 → Attack2 → Attack3.

10. Praxisbeispiel: Vollständiger Character Controller

Hier ist ein vollständiges 2D-Plattformer-Charakterskript, das Statemachine-Fortbewegung mit einem OneShot-Angriff kombiniert. Das ist ein produktionsreifes Muster, das du an dein eigenes Projekt anpassen kannst.

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
        )
AnimationTree-Struktur für dieses Beispiel

Root = BlendTree. Darin: ein StateMachine-Node (mit den Zuständen Idle/Walk/Run/Jump/Fall), verbunden mit einem OneShot-Node ("AttackOneShot"), der wiederum mit dem Output verbunden ist. Die Angriffsanimation ist mit dem "shot"-Eingang des OneShot verbunden.

11. Fehlerbehebung

"Animation wird nicht abgespielt"

Prüfe, dass am AnimationTree active = true gesetzt ist und die Eigenschaft anim_player auf einen gültigen AnimationPlayer zeigt. Vergewissere dich außerdem, dass der AnimationPlayer tatsächlich Animationen mit den Namen enthält, die du referenzierst.

"travel() tut nichts"

Stelle sicher, dass ein gültiger Übergangspfad zwischen dem aktuellen Zustand und dem Zielzustand existiert. travel() schlägt still fehl, wenn kein Pfad existiert. Verwende state_machine.get_current_node(), um zu debuggen, in welchem Zustand du dich tatsächlich befindest.

"Blend funktioniert nicht"

Prüfe, dass dein blend_position-Wert im Bereich deiner Blend-Space-Punkte liegt. Wenn deine Punkte bei 0.0 und 1.0 liegen, funktioniert ein Wert von 5.0 nicht wie erwartet. Verwende clamp().

"Warning: AnimationTree is not active"

Setze active = true entweder im Editor (Kontrollkästchen im Inspector) oder in deiner _ready()-Funktion. Der AnimationTree tut nichts, bis er aktiviert ist.

"Bedingungsbasierter Übergang feuert nicht"

Prüfe genau, dass: (1) der advance_mode des Übergangs auf Auto gesetzt ist, (2) der Name der advance_condition exakt dem entspricht, was du im Code gesetzt hast (Groß-/Kleinschreibung beachten), und (3) du den Parameter in _physics_process() jeden Frame setzt.

"Animation läuft, aber der Charakter bewegt sich nicht"

Der AnimationTree kümmert sich ausschließlich um die Animationswiedergabe. Die Bewegungslogik (velocity, move_and_slide()) ist davon getrennt und muss in der _physics_process() deines Skripts implementiert werden.

Willst du deinen AnimationTree von der KI bauen lassen?

Godot MCP Pro kann Statemachines erstellen, Zustände und Übergänge hinzufügen, Blend Trees konfigurieren und Parameter setzen — alles aus einem einzigen Prompt. Sag deinem KI-Assistenten, welches Animationsverhalten du willst, und er baut den kompletten AnimationTree für dich.

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