Godot 4 Animation Tutorial — AnimationPlayer, AnimationTree
& Blending
Animation is what separates a playable prototype from a game that feels good. Godot 4 ships with a complete animation toolkit — AnimationPlayer for keyframe editing, AnimationTree for state-machine blending, and first-class GDScript hooks so you can drive everything from code.
In this Godot 4 animation tutorial, you'll learn how to animate sprites, properties, and whole characters. We'll cover AnimationPlayer basics, keyframe workflows, AnimationTree state machines, blend spaces for smooth movement, and code-driven animation control. By the end you'll have 6+ copy-paste GDScript examples covering character movement blending, attack combos, and polished UI animations.
Whether you're rigging a 2D platformer or smoothing out a top-down RPG, this guide has you covered. Let's dive in.
AnimationPlayer Basics — Your Starting Point
Every animation in Godot starts with AnimationPlayer. Add one as a child of any node and it can animate any property on any sibling or descendant — position, scale, color, modulate, custom shader params, even exported variables.
The workflow is dead simple: create an Animation resource inside AnimationPlayer, add property tracks, set keyframes on the timeline, and press play. Godot interpolates the values between keys automatically.
Here's what makes Godot AnimationPlayer so powerful:
- Animate ANY property on ANY node — position, rotation, modulate, shader uniforms, you name it
- Method Call tracks — trigger functions at specific frames (perfect for SFX and particles)
- Bezier tracks — fine-tune easing curves for cinematic motion
- Audio tracks — sync sound effects directly to animation timelines
- Multiple animations per player — switch between idle, walk, attack instantly
If you're coming from Unity, think of AnimationPlayer as the Animator + Animation Window combined into one node. If you used Godot 3, the API is mostly the same — with some nice quality-of-life improvements in Godot 4's editor.
Keyframe Animation — Sprite Sheets and Properties
For Godot sprite animation, the most common approach is animating the frame property of a Sprite2D or AnimatedSprite2D. With AnimationPlayer you get precise control over timing, looping, and which frame plays when.
Here's how to set up a basic sprite animation from code. This example creates a simple four-frame walk cycle and plays it:
extends CharacterBody2D
@onready var anim_player: AnimationPlayer = $AnimationPlayer
@onready var sprite: Sprite2D = $Sprite2D
func _ready() -> void:
# Play the "idle" animation on load
anim_player.play("idle")
func _physics_process(delta: float) -> void:
var direction := Input.get_axis("move_left", "move_right")
velocity.x = direction * 200.0
# Flip sprite based on direction
if direction != 0:
sprite.flip_h = direction < 0
anim_player.play("walk")
else:
anim_player.play("idle")
move_and_slide()Notice we call anim_player.play() every frame — AnimationPlayer is smart enough to ignore the call if the same animation is already running. No need for an if anim_player.current_animation != "walk" guard.
Beyond sprite frames, AnimationPlayer can keyframe any property: a PointLight2D's energy for flickering torches, a ColorRect's color for screen flashes, or a shader_parameter for animated dissolves. Check out our Godot 4 Shader Tutorial for more on driving shaders with animations.
AnimationTree — State Machines for Complex Characters
AnimationPlayer.play() works great for simple cases, but as your character gains more states (idle, walk, run, jump, fall, attack, hurt, die…) you'll want smooth transitions and blending between them. That's where AnimationTree comes in.
Godot AnimationTree sits on top of AnimationPlayer. It reads the animations from the player but controls which one plays and how they blend. The main mode you'll use is the AnimationNodeStateMachine — a visual graph where each node is an animation and edges are transitions with conditions.
Setting up an AnimationTree state machine:
- Add an AnimationTree node as a sibling of AnimationPlayer
- Set the Tree Root to AnimationNodeStateMachine
- Assign the AnimationPlayer in the Anim Player property
- Add animation nodes for each state (idle, walk, run, jump, etc.)
- Draw transitions between states and configure conditions
- Set Active to true — the tree now controls playback
Transitions can be set to automatic (switch when the current animation ends), or manual (you call travel() from code). Each transition has a cross-fade time so animations blend smoothly instead of snapping. This approach pairs perfectly with a GDScript state machine pattern for game logic.
extends CharacterBody2D
@onready var anim_tree: AnimationTree = $AnimationTree
@onready var state_machine: AnimationNodeStateMachinePlayback = \
anim_tree["parameters/playback"]
func _physics_process(delta: float) -> void:
var direction := Input.get_vector(
"move_left", "move_right", "move_up", "move_down"
)
velocity = direction * 300.0
move_and_slide()
# Travel to the correct state
if velocity.length() > 10.0:
state_machine.travel("run")
else:
state_machine.travel("idle")
func _on_hit() -> void:
# Interrupt any state and play hurt animation
state_machine.travel("hurt")
func _on_attack_pressed() -> void:
state_machine.travel("attack")The travel() method finds the shortest path through the state graph and cross-fades through each transition. If a direct edge doesn't exist between the current state and the target, it will route through intermediate states automatically. For instant jumps (no blending), use start() instead.
Want to build clean, scalable state machines for your game logic too? Our State Machines micro-course teaches the same pattern used in professional Godot projects.
Want more Godot tricks?
Get our free GDScript Cheat Sheet with 20+ copy-paste snippets for signals, exports, state machines & more.
Blend Spaces — Smooth Movement Animation
State machines handle discrete states well, but what about continuous blending? A top-down character walking in any direction needs to smoothly interpolate between up, down, left, and right walk animations. That's what BlendSpace2D does.
Godot animation blending with BlendSpace2D maps a 2D input vector (like your movement direction) to a set of animations placed on a 2D graph. As the input changes, the engine automatically blends between the nearest animations. For 1D blending (like walk-to-run based on speed), use BlendSpace1D.
Here's a common setup for 8-directional movement blending:
extends CharacterBody2D
@onready var anim_tree: AnimationTree = $AnimationTree
const SPEED := 200.0
func _physics_process(delta: float) -> void:
var input_dir := Input.get_vector(
"move_left", "move_right", "move_up", "move_down"
)
velocity = input_dir * SPEED
move_and_slide()
# Feed direction into BlendSpace2D
# The parameter path matches your AnimationTree setup
if input_dir != Vector2.ZERO:
anim_tree["parameters/walk/blend_position"] = input_dir
anim_tree["parameters/idle/blend_position"] = input_dir
# Toggle between idle and walk blend spaces
var state_machine: AnimationNodeStateMachinePlayback = \
anim_tree["parameters/playback"]
if velocity.length() > 10.0:
state_machine.travel("walk")
else:
state_machine.travel("idle")In the AnimationTree editor, you'd place your directional animations at coordinates on the BlendSpace2D grid: walk_right at (1, 0), walk_left at (-1, 0), walk_down at (0, 1), walk_up at (0, -1), and diagonal variants at the corners. The engine triangulates between the three nearest points and blends them in real time.
BlendSpace1D is simpler and great for speed-based blending: place idle at 0, walk at 0.5, and run at 1.0, then feed in your normalized speed. You get buttery-smooth transitions with zero code logic. If you want even more code-driven animation juice, check out our Godot 4 Tween Tutorial for layering tweens on top of AnimationPlayer.
Code-Driven Animations — GDScript Control
Sometimes you need frame-perfect control that the visual editor can't give you. AnimationPlayer exposes a full GDScript API for creating, queuing, seeking, and blending animations programmatically.
Key methods you'll use:
play(name)— play an animation by nameplay_backwards(name)— play in reverse (great for retract animations)queue(name)— queue an animation to play after the current one finishesstop()— stop the current animationseek(time, update)— jump to a specific time in the animationspeed_scale— speed up or slow down playback globallyanimation_finished signal— react when an animation completes
The animation_finished signal is especially useful. Connect it to chain animations, trigger game logic after an attack lands, or return to idle after a one-shot animation. You can also use the Signal Bus pattern to broadcast animation events globally.
Want the complete GDScript reference at your fingertips? Grab our free cheat sheet — 20+ copy-paste snippets for animations, signals, tweens, and more.
Practical Example — Attack Combo System
Let's build something real: a 3-hit attack combo. The player presses attack during the current swing to queue the next hit. If they're too slow, the combo resets. This is a classic pattern for action games and it shows off AnimationPlayer's queue system and signals beautifully.
extends Node2D
@onready var anim_player: AnimationPlayer = $AnimationPlayer
var combo_step: int = 0
var can_combo: bool = false
var combo_window_timer: float = 0.0
const COMBO_WINDOW: float = 0.4 # seconds to input next hit
func _ready() -> void:
anim_player.animation_finished.connect(_on_animation_finished)
func _process(delta: float) -> void:
if combo_window_timer > 0.0:
combo_window_timer -= delta
if combo_window_timer <= 0.0:
_reset_combo()
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("attack"):
if combo_step == 0:
# Start the combo
combo_step = 1
anim_player.play("attack_1")
elif can_combo and combo_step < 3:
# Chain the next hit
can_combo = false
combo_step += 1
anim_player.play("attack_" + str(combo_step))
func _on_animation_finished(anim_name: String) -> void:
if anim_name.begins_with("attack_"):
if combo_step < 3:
# Open the combo window
can_combo = true
combo_window_timer = COMBO_WINDOW
else:
_reset_combo()
func _reset_combo() -> void:
combo_step = 0
can_combo = false
combo_window_timer = 0.0
anim_player.play("idle")The key insight: we use the animation_finished signal to open a short combo window. If the player presses attack within that window, we advance to the next animation. After the third hit (or if the window expires), we reset. Each attack_1, attack_2, and attack_3 animation would have Method Call tracks to spawn hitboxes, play SFX, and emit screen shake signals.
For a deeper dive into organizing game logic with signals, see our Signal Bus pattern guide.
Practical Example — Polished UI Animations
AnimationPlayer isn't just for gameplay — it's also the best tool for UI animations in Godot. Menu transitions, health bar pulses, inventory slot highlights — all of these benefit from keyframed animations rather than code-driven tweens because designers can tweak timing in the editor.
Here's a pattern for a reusable UI panel that slides in and fades out, controlled entirely from code:
extends Control
@onready var anim_player: AnimationPlayer = $AnimationPlayer
func open_menu() -> void:
visible = true
anim_player.play("slide_in")
# "slide_in" animates:
# - position.x: from 400 → 0 (slide from right)
# - modulate.a: from 0 → 1 (fade in)
# - scale: from 0.95 → 1.0 (subtle zoom)
func close_menu() -> void:
anim_player.play_backwards("slide_in")
await anim_player.animation_finished
visible = false
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel") and visible:
close_menu()The trick here is play_backwards(). Instead of creating separate "open" and "close" animations, we play the same animation in reverse. Combined with await, we can hide the panel only after the animation finishes — no timers, no callbacks, just clean async GDScript.
For simpler one-off UI effects (button bounces, notification pops), tweens are usually faster to set up. See our Godot 4 Tween Tutorial for when to use tweens vs. AnimationPlayer.
Get all our Godot courses in one pack — signals, shaders, state machines, and more. Save 50%+ with the bundle.
Advanced Animation Tips
Once you're comfortable with the basics, here are some pro techniques to take your animations to the next level:
Animation Libraries
Godot 4 introduced Animation Libraries, which let you organize animations into named groups. Instead of dumping 50 animations into one AnimationPlayer, you can separate them by context: "combat/slash_1", "movement/run", "ui/flash_damage". Access them with anim_player.play("combat/slash_1").
Root Motion
For 3D characters (or 2D characters with precise movement), enable root motion on your AnimationTree. This lets the animation itself drive the character's position — so a dodge roll animation actually moves the character body, eliminating the disconnect between visual and physics movement.
Call Method Tracks for SFX and Particles
Method Call tracks in AnimationPlayer are incredibly powerful. Drop them onto any frame to call a function on the animated node. Use this for:
- Playing footstep sounds at the exact frame the foot hits the ground
- Spawning dust particles during a landing animation
- Enabling/disabling hitbox CollisionShape2D during attack frames
- Emitting signals for screen shake or camera effects
Blending Layers with AnimationTree
AnimationTree supports AnimationNodeBlendTree mode (different from the state machine) where you can additively blend animations. Want a character to run and aim their weapon at the same time? Use an Add2 or Blend2 node to layer the upper-body aim animation on top of the lower-body run animation. This is common in 3D games but works for 2D too if your rig is set up with separate bone tracks.
Animation Cheat Sheet — Quick Reference
Here's a quick reference for the most common animation operations in GDScript. Bookmark this — you'll use it constantly:
# ─── AnimationPlayer ───────────────────────────
var ap: AnimationPlayer = $AnimationPlayer
ap.play("walk") # Play animation
ap.play("walk", -1, 2.0) # Play at 2x speed
ap.play_backwards("walk") # Play in reverse
ap.queue("idle") # Queue after current
ap.stop() # Stop immediately
ap.seek(0.5, true) # Jump to 0.5s, update
ap.speed_scale = 0.5 # Half speed globally
# Wait for animation to finish
await ap.animation_finished
# Check current animation
if ap.current_animation == "attack":
pass
# ─── AnimationTree State Machine ──────────────
var tree: AnimationTree = $AnimationTree
var sm: AnimationNodeStateMachinePlayback = \
tree["parameters/playback"]
sm.travel("run") # Blend to "run" state
sm.start("hurt") # Instant switch (no blend)
sm.get_current_node() # Current state name
# ─── BlendSpace Parameters ────────────────────
tree["parameters/walk/blend_position"] = Vector2(1, 0)
tree["parameters/speed/blend_position"] = 0.75Pro tip: name your animations consistently. Use idle, walk, run, attack_1, attack_2 — lowercase, underscore-separated, no spaces. This makes string-based lookups reliable and keeps your project organized as it scales. For more GDScript tips, check out our 5 GDScript Tricks Most Devs Don't Know post.
AnimationPlayer vs. AnimationTree vs. Tweens — When to Use What
Godot gives you three animation systems and they're designed to work together, not compete. Here's when to reach for each:
- AnimationPlayer — Sprite sheet cycles, multi-property keyframed sequences, anything you want to edit visually in the timeline
- AnimationTree — Complex characters with 4+ states, directional movement blending, layered animations (aim + run), root motion
- Tweens — Quick one-off code animations: button bounces, damage flash, camera shake, pickup effects. Zero setup, created inline
In practice, a well-animated character often uses all three: AnimationPlayer holds the raw animations, AnimationTree manages blending and state transitions, and tweens handle reactive effects like damage flashes and hit freezes.
Wrapping Up
Godot 4's animation system is one of the engine's biggest strengths. AnimationPlayer gives you a timeline editor that can animate literally anything. AnimationTree adds state machines and blend spaces for complex character rigs. And GDScript hooks let you drive it all from code with full control.
Start simple: get AnimationPlayer working with a walk and idle cycle. Then graduate to AnimationTree when your character has more than a few states. Add BlendSpace2D when you need directional blending. And layer tweens on top for that final layer of juice.
Here's what to explore next:
Get the Free GDScript Cheat Sheet + Weekly Godot Tips
Join indie devs getting our GDScript Cheat Sheet with 20+ copy-paste snippets for signals, exports, state machines, and more — plus weekly Godot tips and architecture tricks straight to your inbox.
Take your Godot skills further with our 15-minute micro-courses. Each one is focused, practical, and built for indie devs who ship.
Mastering GDScript Signals
Signal buses, typed signals, async patterns — the definitive signals course for Godot 4.
$4.99 · 15 min →State Machines in GDScript
Clean game logic with reusable state machines. Perfect for AI, UI, and game flow.
$4.99 · 15 min →Godot Pro Pack Bundle
All courses at a discount. Signals, shaders, state machines, multiplayer, and more.
$12.99 · Save 50%+ →