Godot AnimationPlayer Basics: Set Keys and Build a Door That Opens When You Step on It

Created: 2026-09-22Last updated: 2026-10-10

Set two keys in AnimationPlayer and build a door that opens when you step on a switch and closes when you leave.

When you want to play a fixed movement, like a door that opens when you step on a switch and closes when you leave, you set keys in AnimationPlayer. Set two keys, one at the start and one at the end, and Godot fills in the movement in between.

But it's common to think you've set the keys and then find that nothing moves when you play. If you can't see that one row of the timeline corresponds to one Inspector property, you won't know where to fix it.

This article covers how the timeline and the Inspector correspond, and how to build a door that opens when you step on a switch.

The Inspector's Position connects to one row of the timeline, and with the playhead at 0.25 seconds, y is 268

What You'll Learn

  • How one timeline row corresponds to one Inspector property, and what the key icon writes
  • The order of playhead, value, and key icon, and what happens when you break that order
  • Four things to check when a key you set doesn't move anything on playback
  • Where reverse playback starts from, and where the name of a finished animation is kept

Tested with: Godot 4.7

Sponsored

How the timeline and the Inspector correspond

Select an AnimationPlayer and the Animation panel opens at the bottom of the screen. Create a new animation there, and you get a screen with a list of tracks (one row per property you animate) on the left half and a time ruler on the right half. Above the ruler is a vertical line pointing at the current time (the playhead).

Select a node in this state, and a key icon appears at the right edge of each Inspector property. Click it, and a single point, "at this time, this property has this value right now," is recorded on that property's row. That single point is a key.

Clicking a property's key icon in the Inspector creates one row on the timeline for that property, with keys lined up on it

Each row in the track list points to which node's which property. A row named Door:position handles the position of the node called Door. Sprite2D:modulate would be Sprite2D's color.

To move the door from the closed position to the open position, the same row needs two keys with different times and values. If 0.00 seconds holds (320, 300) and 0.50 seconds holds (320, 236), playback runs through values that fill in between them. This "filling in between" calculation is called interpolation.

It isn't just a tool for swapping art. Any property with a key icon, whether position, color, or angle, can be animated the same way. Lining up hitboxes and sounds with art changes is in the article on combining an attack into one AnimationPlayer.

In other engines: Godot plays property changes with AnimationPlayer. For moving things over time like a door, it's close to an Animation Clip made in Unity's Animation window, or an implementation using a Timeline in Unreal Engine.

The order for clicking the key icon

Almost every case of a key not getting set comes down to order.

Move the playhead, check the value, click the key icon. Change the value and then move the playhead, and the changed value is reverted
  1. Move the playhead to 0.00 seconds
  2. Check the closed value in the Inspector
  3. Click the key icon
  4. Move the playhead to 0.50 seconds
  5. Enter the open value and click the key icon again

Only move the playhead before changing the value. Move the playhead after changing the value, and it's overwritten with the value at the new time. Once a track exists, moving the playhead pours in the value for that time every time.

"Move the door first, then decide the time" is the most common way to get stuck. The changed value disappears, so you carry on thinking you set the key.

Four things to check when setting keys

If you can click the key icon but nothing moves on playback, check these four things.

Sponsored

It went into a different animation. If the panel's dropdown is still on RESET, the key goes into a separate animation called RESET. RESET is a special animation holding the initial pose that each property is set back to when saving. Playing open does nothing because it has no tracks. Before clicking the key icon, check that the dropdown shows open.

Clicking the key icon with the dropdown still on RESET creates a track in RESET, while open, the one you play, stays empty

You clicked again at the same time. Clicking the key icon at the same time on the same row doesn't add a key; it overwrites the value. The track may look like it grew, but it still holds a single point. To make a second point, move the playhead first.

It's left outside the length. A newly created animation is 1 second long. Set a key at 1 second and then shrink the length to 0.5, and that key isn't deleted. It stays, outside the range, so it never plays. Decide the length first.

You're only moving the art. The key goes onto the property of the node you currently have selected. Click it with Sprite2D, the door's art, selected, and you get a Door/Sprite2D:position row. On playback only the art moves up, and the collision stays where it was. To move the whole door, select not the art but the parent that holds the art and the collision together.

Clicking the key icon with Sprite2D selected raises only the art, while the collision stays in place and blocks the passage

Interpolation and update mode

On the right side of a track sit two settings that look alike.

  • Interpolation: how to fill in between keys. Linear connects them in a straight line, Nearest holds the previous key's value until the next key is reached, and Cubic draws a smooth curve. Linear is enough for the door's position
  • Update mode: when to write the value. Continuous writes every frame, and Discrete writes only when passing a key. Use Continuous for position and color, and Discrete for properties that are only on or off, like checkboxes
Interpolation is how to fill in between keys, and update mode is when to write the value. They look alike but are separate settings

Nearest isn't "snap to the closer key." With keys at 0.00 and 0.50 seconds, it's still the 0.00-second value at 0.49 seconds.

The length field and the loop button sit at the top right of the timeline. For an animation like a door that "opens and is done," leave looping off. Turn it on, and the fully open door jumps to the closed pose and starts opening again.

Reverse playback, and the state after playback ends

Playing is one line: play("open"). For the reverse direction, use play_backwards("open").

If the same animation is partway through playing, play_backwards() goes back from that position. Call it when the door has opened to 0.2 seconds, and it closes from 0.2 seconds. It only jumps to the end before going back when switching from a different animation, or when the position is at 0 seconds.

Called partway through the same animation, it goes back from that position. From a different animation or at 0 seconds, it goes back from the end

The state after playback ends has its own trap. When playback stops, current_animation becomes an empty string. The animation name is kept in assigned_animation instead. The position and length stay at the end values, so check "is it already fully open?" with three things: it isn't playing, assigned_animation is "open", and the position equals the length (_is_fully_open() in the hands-on).

Call play("open") without checking this, and playback restarts from 0 seconds. The fully open door closes once and then opens again.

Sponsored

Hands-On: A Door That Opens When You Step on It

A trap door in a dungeon, a starting gate in a racing game, a hidden door in an escape game—"opens when you approach, closes when you leave" has the same shape in all of them. You'll build it up to the door moving 64 pixels up out of the way to open the passage, then going back once you've passed through.

① What to place

Make one door that blocks a passage, in a new scene.

  • Put a Node2D at the root, name it Level, and attach level.gd
  • Add an AnimatableBody2D under Level as Door. Position (320, 300). Turn on Sync To Physics
  • Under Door, place a Sprite2D (icon.svg as its Texture, Scale (0.5, 0.5)) and a CollisionShape2D (RectangleShape2D, size (64, 64))
  • Add an AnimationPlayer under Level. In the Inspector, set Process inside Callback Mode to Physics
  • Add two StaticBody2Ds under Level: WallTop (position (320, 236)) and WallBottom (position (320, 364))
  • Give each of the two walls a CollisionShape2D child (RectangleShape2D, size (640, 64))
  • Add an Area2D (a range that only reports overlaps and doesn't stop bodies) under Level as Switch. Position (320, 300). Give it a CollisionShape2D child (RectangleShape2D, size (256, 128))
  • Add a CharacterBody2D under Level as Player. Position (140, 300). Put it in the group player (a name tag on the node) in the Groups dock, and create and attach player.gd
  • Under Player, place a Sprite2D (icon.svg as its Texture, Scale (0.3, 0.3)) and a CollisionShape2D (CircleShape2D, radius 20)
The door blocks the middle of the passage, and the switch's range is four times the door's width, covering from in front of the door to the far side

Switch and the walls are only shapes, so you won't see them when you run. Turn on Visible Collision Shapes from the Debug menu at the top of the editor.

The passage formed by the top and bottom walls is the band where y runs from 268 to 332, and the closed door blocks it exactly. Raise the door 64 pixels and it moves to 204 to 268, completely out of the passage.

Make Switch wider than the door. If it only covers the area in front of the door, you leave the switch the moment you enter the passage, and the door comes down before you pass through. Because it's wide, the door and walls overlap Switch too, so body_entered, which arrives when a body enters the range, comes for them as well. That's why the code tells the player apart by group.

The door is an AnimatableBody2D rather than a StaticBody2D so that, as a moving door, it can push things. Moving a StaticBody2D doesn't count as movement that pushes things. Line up Sync To Physics with AnimationPlayer's Process set to Physics, and the door moves at the same timing as the physics update, so whatever it pushes doesn't jitter.

② Set two keys

Select the AnimationPlayer and create a new animation named open in the panel at the bottom. First set the length to 0.5, and leave looping off.

Then select Door in the scene tree and go in this order. Selecting Door, not Sprite2D, is the important part.

  1. Move the playhead to 0.00 seconds
  2. Check that Transform > Position in the Inspector is (320, 300), and click the key icon at its right edge
  3. When asked whether to create a track, leave Use Bezier Curves off (turning it on makes a different kind of track), leave Create RESET Track(s) on, and create it
  4. Move the playhead to 0.50 seconds
  5. Change Position's y to 236 (64 pixels up)
  6. Click Position's key icon again

If one row called Door:position appears with keys at 0.00 and 0.50 seconds, you've succeeded. If it says Door/Sprite2D:position, select again and redo it.

Select Switch, and connect body_entered and body_exited to Level from the Signals dock.

# level.gd
extends Node2D

@onready var anim: AnimationPlayer = $AnimationPlayer

func _on_switch_body_entered(body: Node2D) -> void:
	if not body.is_in_group("player"):
		return
	if _is_fully_open():
		return                             # calling play when fully open goes back to 0 seconds
	anim.play("open")

func _on_switch_body_exited(body: Node2D) -> void:
	if not body.is_in_group("player"):
		return
	anim.play_backwards("open")            # if partway, go back from that position

func _is_fully_open() -> bool:
	if anim.is_playing():
		return false                       # not fully open while it's still moving
	if anim.assigned_animation != "open":
		return false                       # current_animation becomes empty when it finishes
	return is_equal_approx(anim.current_animation_position, anim.current_animation_length)   # compare allowing for floating-point error
# player.gd
extends CharacterBody2D

const SPEED := 300.0

func _physics_process(_delta: float) -> void:
	velocity = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") * SPEED
	move_and_slide()

③ Run it and check

With the playhead at 0.00, 0.25, and 0.50 seconds, the door rises through 300, 268, and 236, and once fully open the passage is clear

Before running, move the timeline's playhead by hand. With Door still selected, put the playhead at 0.25 seconds, and the Inspector's Position is (320, 268). That's exactly halfway between 300 at 0.00 seconds and 236 at 0.50 seconds, a number you never set yourself. Interpolation is filling in between. If it stays at 300, either there's no key at 0.50 seconds, or interpolation is Nearest or the update mode is Discrete.

Once you've checked, move the playhead back to 0.00 seconds before saving. AnimationPlayer's Reset On Save (on by default) sets things back to the RESET values when saving. Even if you save while showing a different time, the closed position is what gets saved.

Run it and walk right with the right arrow key. When you enter the switch's range, the door starts rising. The player arrives before the door finishes rising, so it catches on the door's bottom corner for a moment, and about 0.4 seconds after entering the switch you can pass underneath. If only the door's art rises and you can't get through, you set the keys on the Sprite2D row. Even after passing under the door you're still inside the switch, so the door stays open. Walk to the right edge and out of the switch, and the door comes down and blocks the passage.

To finish, step just a little into the left edge of the switch, stop, and leave to the left before the door finishes rising. The door starts closing from the height it reached, and never jumps to the top first before closing. You set only two keys, at 0.00 and 0.50 seconds, and interpolation fills in every height in between.

Two points.

  • The key icon attaches to "that property of the node you have selected right now": click it with Sprite2D selected, and only the art moves while the collision stays in place. Check every time that the row's name matches the node you want to move
  • A stopped animation's name is in assigned_animation: current_animation only returns the name of what's playing. It becomes empty when playback stops, so comparing names there never matches

If you want to add sounds or effects when it finishes closing or opening, go on to the article on combining hitboxes and sound into one AnimationPlayer.

Troubleshooting

SymptomCauseFix
The door is open at startupYou saved while showing the open time, with no RESET track. Or RESET holds the open poseSwitch the dropdown to RESET and re-set the Door:position key at (320, 300). If there's no RESET, move the playhead back to 0.00 seconds before saving
After moving the door somewhere else, it jumps back to its original position the moment it playsThe key records the exact position (320, 300) as seen from the parent LevelRe-set the keys after moving the door. If you make the door its own scene and set keys on a child's position inside that scene, it won't depend on where it's placed (keys on the root's position jump to those coordinates no matter where it's placed)
After duplicating the door, only one of them movesA track points to its target by name, like Door:position. The duplicated Door2 isn't a targetGive each door its own AnimationPlayer. Make the door its own scene and place it as instances

Bonus: Good to Know First

A Node2D's rotation turns around that node's origin. When you want to turn it around one edge like a hinge, put a parent node at the pivot point, place the art offset as its child, and animate the parent's rotation. Give the door's art itself an angle, and it turns around its middle.

You can also build it so the collision is removed only while the door is open. The key icon works on CollisionShape2D's Disabled too. In that case, set the update mode to Discrete and put keys only at the times you want it to switch. If you can move the whole door, as in the hands-on, leaving the collision alone keeps it to a single state.

For movement whose destination isn't decided until runtime, like where a picked-up coin flies off to, Tween is a better fit than setting keys. Keys are the tool for when the destination is decided from the start.

How it divides the work with art swapping is in the comparison with AnimatedSprite2D, and stopping controls while showing a door opening is in Cutscene Basics.

Summary

  • One row is one Inspector property: the key icon writes "at this time, this property has this value right now"
  • Playhead, value, then key icon: change the value and then move the playhead, and it's overwritten with the value at the new time
  • Decide the length first: shrink it later, and keys left outside stay there and never play
  • The key icon attaches to the selected node: to move the whole door, select the parent, not the art
  • play_backwards() goes back from partway: partway through the same animation, it goes back from that position. A stopped animation's name is kept in assigned_animation

The node you select before clicking the key icon decides what moves. Is what you want to move right now the art, or the container that holds the art and the collision together?

Read nextNext in the learning path
Animation

Advanced AnimationPlayer Techniques

Read this note
Godot Notes in this section104