Lesson 22

The Animation Player

Four animations in one node. Build an AnimationPlayer from nothing, learn what every control in the panel is for, and see the exact numbers that turn one picture into idle, walk, run and jump.

Loading the game engine. This takes a moment the first time.

Simple Animation played two animations without explaining the node that played them. This lesson opens the AnimationPlayer up. The hero on the left has four animations in one node (idle, walk, run and jump), and the strip on the right is a second AnimationPlayer running all four side by side so you can see the frames they are made of.

Nothing here is a new kind of picture. Every one of those animations is the same trick: change the Sprite2D's "frame" number, over and over, fast.

Notice how little this file knows. It never says what idle looks like, how many frames walk has, or how fast run goes. All of that is in the scene, saved on the AnimationPlayer. This script only ever picks a name.

That split is the point of the whole lesson. An artist can retime every animation in the player scene without opening this file, and you can rewrite every rule below without touching a single keyframe.

WHAT AN ANIMATIONPLAYER ACTUALLY IS

It is a node that changes OTHER nodes' properties over time. That is the whole idea, and it is bigger than it sounds. It is not "the sprite node" or "the animation node", and it does not draw anything at all. It reaches into a property that already exists somewhere in the scene and rewrites it as the clock runs.

So the AnimationPlayer can animate anything you can see in the Inspector: a sprite's frame, a node's position, a colour, a scale, whether something is visible. If you can set it by hand, an AnimationPlayer can set it for you, sixty times a second.

That is why it lives in the SCENE and not in this script. An animation is data, a list of times and values, rather than code. Our code only ever says which one to play.

THE PIECES, FROM BIGGEST TO SMALLEST

  • AnimationPlayer is the node. It holds a whole library of animations and plays one of them at a time.
  • An animation is one named performance: "walk". It has a length in seconds, and it either loops or it does not.
  • A track is one row inside an animation, aimed at ONE property of ONE node, written as node:property, like Sprite:frame. An animation that moves two things has two tracks.
  • A keyframe is one dot on a track: "at 0.2 seconds, this property is 8." Godot fills in the times between the dots for you.
The AnimationPlayer panel, with every control named.
The AnimationPlayer panel, with every control named.

Read those four in order and the panel above stops looking like a mixing desk: a player holds animations, an animation holds tracks, and a track holds keyframes.

THE SETUP, STEP BY STEP

  1. Select the node the animation belongs to, which here is the player's root, and add an AnimationPlayer as a CHILD of it. Keeping it inside the player scene is what lets you drop that player into any level and have its animations come along.
  2. Click it. The Animation panel opens along the bottom of the editor.
  3. Press Animation -> New, and name it "idle". You now have an empty animation, one second long.
  4. Set the length. The box at the bottom right of the panel is the whole animation's duration in seconds. Ours is 0.9.
  5. Turn the Loop button on, next to the length box. An idle has to repeat forever, so this one loops.
  6. Now make a track. Select the Sprite2D, find "Frame" in the Inspector, and click the key icon beside it. Godot creates a Sprite:frame track and drops a keyframe at the playhead in one move.
  7. Move the playhead along the timeline, change Frame, key it again. Six keys later you have an idle.
  8. Press Animation -> New again for "walk", and repeat. Same node, same property, different numbers. The library grows; the setup does not.

WHAT EACH PART OF THE PANEL DOES

ANIMATION
The dropdown naming the animation you are editing. Every animation in this player is in that list, and the New, Duplicate and Rename commands are in the menu beside it.
TIMELINE
The ruler across the top, measured in seconds. The blue line is the playhead: whatever it is sitting on is what the scene above is showing you right now.
TRACK LIST
One row per property being animated, down the left. The name is always node:property.
KEYFRAMES
The diamonds on a track. Drag one to change WHEN it happens; double-click to change WHAT it sets.
LENGTH
How many seconds the animation runs for. Shorter is faster, because the same keys have less time to get through.
LOOP
Whether it starts again at the end. On for idle, walk and run. Off for jump.
SNAP
Rounds keyframes to a neat fraction of a second as you drag them. Leave it on. Animation frames that land on 0.0997 are how a cycle ends up looking uneven.
UPDATE MODE
Hidden until you right-click a track, and the setting that bites everybody once. See below.

DISCRETE, OR YOUR SPRITE SHEET SMEARS

By default a track is CONTINUOUS: Godot blends between your keyframes, so a value that goes from 0 to 5 passes through 2.5 on the way. That is perfect for a position sliding across the screen. It is nonsense for a frame number, because there is no drawing halfway between frame 2 and frame 3.

Right-click the track, choose Update Mode, and pick DISCRETE. Now the value snaps from one keyframe straight to the next and holds it until the following key. That is what flipbook animation is.

Every frame track in this lesson is Discrete. If your own sprite animation ever looks like it is playing frames you never keyed, this is why.

THE FOUR STATES, AND THE NUMBERS THAT MAKE THEM

hero.png is a 288x192 sheet: 6 frames across, 4 rows down, 48 pixels each. Setting hframes to 6 and vframes to 4 on the Sprite2D slices it into 24 numbered frames, counting left to right and then down. So the ROW you want decides the numbers you key, and nothing else about the setup changes.

EVERY ROW, AND WHAT IT IS SET TO

  • idle is frames 0-5, length 0.9, loop on. Breathing, and little else.
  • walk is frames 6-11, length 0.6, loop on. Six phases of one step.
  • run is frames 12-17, length 0.42, loop on. The same six phases, bigger, with less time to get through them.
  • jump is frames 18-23, length 0.6, and loop OFF. Crouch, launch, rise, fall, land.
The sheet, and the keyframes each row turns into.
The sheet, and the keyframes each row turns into.

Look at what actually differs between walk and run: six keyframes either way, six numbers either way, one Discrete Sprite:frame track either way. Only the numbers in the keys and the number in the length box change. Run is not a harder animation than walk. It is the same animation pointed at a different row of the picture and given less time to finish.

THE ONE THAT MUST NOT LOOP

Jump is the odd one out, and deliberately. Leave Loop on and the hero pogos forever, because the animation has nowhere to stop. Turn it off and the animation runs once, holds its last frame, and fires the animation_finished signal, which is how this script knows it is allowed to go back to standing around. That rule is written further down this file.

Watch someone do it

Nothing loads from YouTube until you press play.

Godot 4 Animation Player Tutorial

Gwizz · 3:38

A tour of the panel itself. Watch someone make a new animation, key a property and set the length and loop, which is the part of this lesson that is a mouse moving rather than words.

Watch on YouTube

How to Use AnimationPlayer for Frame Animations

MrWonderHow · 3:24

The frame track specifically, which is the exact kind of track our four animations are built from, in a recent version of the editor.

Watch on YouTube

Godot 4 Sprite Sheet Animation, 3 Different Ways

Coding Quests · 4:36

AnimationPlayer next to AnimatedSprite2D. Worth four minutes for the answer to the question this lesson will leave you with: which one should I use, and why?

Watch on YouTube

These are other people's tutorials, not ours. They are here because some of this lesson is a mouse moving around a panel, and that is easier to watch than to read.

New code in this lesson

None of the earlier lessons use these. Each one shows the line where it appears here.

animation_finished

The signal an AnimationPlayer sends when an animation ends, with its name.

player.gd
anim.animation_finished.connect(_on_animation_finished)

elif

Short for "else if": another condition to try when the if above it was false.

player.gd
elif running:

The scenes

■ UI node · ■ 2D node · % unique name · .tscn instanced scene

game.tscn
  • Game Node2D
  • Player CharacterBody2D player.tscn
  • Showcase Node2D
  • Title Label
  • IdleSprite Sprite2D
  • WalkSprite Sprite2D
  • RunSprite Sprite2D
  • JumpSprite Sprite2D
  • IdleLabel Label
  • WalkLabel Label
  • RunLabel Label
  • JumpLabel Label
  • ShowcasePlayer AnimationPlayer
player.tscn
  • Player CharacterBody2D player.gd
  • Sprite Sprite2D %
  • CollisionShape2D
  • AnimationPlayer %

FileSystem

  • game.tscn
  • player.tscn
  • player.gd

The code

extends CharacterBody2D

## Walking speed. "@export" puts these at the top of the Inspector so you can
## retune them without editing this file.
@export var walk_speed: float = 130.0
## Running speed, used while the run key is held.
@export var run_speed: float = 260.0

# "%AnimationPlayer" means "the node named AnimationPlayer in this scene."
@onready var anim: AnimationPlayer = %AnimationPlayer
@onready var sprite: Sprite2D = %Sprite

# True from the moment we start the jump until the jump animation finishes.
# While it is true, nothing else is allowed to change the animation.
var jumping: bool = false


func _ready() -> void:
	# "animation_finished" is a signal the AnimationPlayer sends when a
	# NON-looping animation reaches its end. Jump is the only one of our four
	# with Loop turned off, so it is the only one that will ever arrive here.
	anim.animation_finished.connect(_on_animation_finished)


func _physics_process(_delta: float) -> void:
	var direction: Vector2 = Input.get_vector(
		"move_left", "move_right", "move_up", "move_down"
	)
	var running: bool = Input.is_action_pressed("run")

	velocity = direction * (run_speed if running else walk_speed)
	move_and_slide()

	# The sheet only draws the hero facing right, so walking left mirrors it.
	# Standing still keeps whichever way we faced last.
	if direction.x != 0.0:
		sprite.flip_h = direction.x < 0.0

	# The guard. A jump owns the animation until it has played itself out, so
	# everything below is skipped while one is running. Without this line,
	# walking during a jump would cut the jump off on its second frame.
	if jumping:
		return

	if Input.is_action_just_pressed("jump"):
		jumping = true
		play("jump")
		return

	# Three situations, three names. This is the whole "state machine": pick
	# the situation, play its animation.
	if direction.length() == 0.0:
		play("idle")
	elif running:
		play("run")
	else:
		play("walk")


## Start an animation, unless it is already the one playing.
##
## This check is not a tidiness thing, it is the difference between working and
## broken. "_physics_process()" runs about 60 times a second, so calling
## "anim.play("walk")" from it directly would restart walk 60 times a second
## and the hero would sit frozen on walk's first frame forever.
##
## Comparing against "current_animation" is enough on its own here. When a
## looping animation is going, that property holds its name; when the
## non-looping jump ends, Godot clears it back to an empty string.
## The parameter is "animation_name" rather than "name" because every Node
## already HAS a "name", and Godot warns about the shadowing.
func play(animation_name: String) -> void:
	if anim.current_animation != animation_name:
		anim.play(animation_name)


## Runs when a non-looping animation ends. Godot hands us its name.
func _on_animation_finished(finished: StringName) -> void:
	if finished == "jump":
		# Release the guard. The next "_physics_process()" will look at the
		# keys again and go back to idle, walk or run on its own.
		jumping = false