Quick answer: In Godot 4, stop() halts playback but does not reset animated properties. The node stays in whatever state the animation left it in. Use stop() followed by seek(0, true) to jump back to frame 0, or play the special RESET animation to restore all properties to their default values.
You call animation_player.stop() expecting your sprite to snap back to its idle pose. Instead, it freezes on the last frame of the attack animation, stuck mid-swing forever. This catches almost everyone who works with AnimationPlayer in Godot 4 for the first time, because the behavior changed from what many developers expect — and from how some other engines handle it.
Why stop() Doesn’t Reset
In Godot 4, AnimationPlayer.stop() does exactly one thing: it stops the playback clock. It does not rewind the animation to the beginning. It does not restore the animated properties to their pre-animation values. It does not seek to frame zero. The node that was being animated retains whatever property values the animation had set at the moment stop() was called.
This is intentional. The Godot developers designed it this way because automatically resetting properties on stop would break use cases where you want to stop an animation and leave the result in place — for example, stopping a door-opening animation after the door has finished opening. The door should stay open, not snap back to closed.
# This DOES NOT reset the animation
func cancel_attack():
$AnimationPlayer.stop()
# Sprite is still on whatever frame it was showing
# This DOES reset to the beginning
func cancel_attack():
$AnimationPlayer.stop()
$AnimationPlayer.seek(0, true)
# Sprite is now on frame 0 of the current animation
The true parameter in seek(0, true) is important. It forces the AnimationPlayer to update the animated properties immediately even though playback is stopped. Without it, the seek position is stored internally but the visual state does not change until the next time the animation plays.
Using the RESET Animation
Seeking to frame 0 resets the animation to its starting keyframe, but that starting keyframe may not be the node’s original state. If your attack animation starts with the sword already raised, seeking to frame 0 shows the raised sword, not the idle pose. For a true reset to pre-animation defaults, you need the RESET animation.
The RESET animation is a special animation recognized by Godot’s AnimationPlayer. It contains a single keyframe at time 0 for every animated property, set to the default value that property should have when no animation is playing. You can create it automatically from the AnimationPlayer toolbar.
# Creating and using the RESET animation
# In the editor: AnimationPlayer -> Animation menu -> Save RESET Animation
# This captures the current values of all animated properties
# In code: play the RESET animation to restore defaults
func return_to_idle():
$AnimationPlayer.stop()
$AnimationPlayer.play("RESET")
# Or, if you want an instant reset without playing through:
func instant_reset():
$AnimationPlayer.play("RESET")
$AnimationPlayer.seek(0, true)
$AnimationPlayer.stop()
If the Save RESET Animation option is not visible, make sure you have at least one animation with at least one track in your AnimationPlayer. The option only appears when there are tracks to reset.
Handling Reset with AnimationTree
If you are using an AnimationTree instead of a bare AnimationPlayer, the reset behavior is slightly different. AnimationTree blends between animations, so stopping one animation causes it to blend toward whatever the tree’s default state is. If you have not configured a default state, the tree may blend toward zeroed-out values, causing your character to collapse or disappear.
# With AnimationTree, set the default blend state
func _ready():
var state_machine = $AnimationTree.get("parameters/playback")
state_machine.start("idle") # Set a default state
# To reset, travel back to the idle state
func cancel_and_reset():
var state_machine = $AnimationTree.get("parameters/playback")
state_machine.travel("idle") # Blends smoothly to idle
With AnimationTree, you generally do not need to call stop() at all. Instead, transition to an idle state that represents the reset pose. The tree handles blending, and your character transitions smoothly rather than snapping to a new pose.
Common Patterns and Workarounds
For one-shot animations like attack effects or hit flashes, a clean pattern is to connect to the animation_finished signal and reset there. This ensures the animation always cleans up after itself regardless of how it was triggered.
# Auto-reset pattern for one-shot animations
func _ready():
$AnimationPlayer.animation_finished.connect(_on_animation_finished)
func play_hit_flash():
$AnimationPlayer.play("hit_flash")
func _on_animation_finished(anim_name: String):
if anim_name == "hit_flash":
$AnimationPlayer.play("RESET")
For looping animations that need to be interrupted, combine stop() with an explicit state reset. Store the properties you care about before the animation starts and restore them manually when you stop. This is more work than using the RESET animation but gives you precise control over which properties revert and which stay.