How in the world do you get a looping animation to stop and go away?

Godot Version

4.4.1.stable

Question

I’m going in circles here banging my head…this should be a basic function of animationplayer in script. I should be able to go “animationplayer.stop()” and it just ends and clears the animation from playing/existing. But .stop() just resets the animation (why isn’t that .reset???). What I want simply is nowhere to be found in the documentation and somehow has never been asked before!

Here’s what I have:

  • 2 animations: “charge 1” and “charge2”
  • charge1 starts playing when the player starts holding down a key.
  • charge1 is not looping and is set in the Animation timeline to play charge2 once it completes.
  • charge2 is looping and is intended to loop over and over until the player releases the held-down key.
  • However, no matter what I do, charge2 plays infinitely even after the key is released since there is no apparent way to make it stop. again, WTF. It’s permanately there at this point.
  • Input.is_action_pressed doesn’t work here as it makes infinite bullets appear
func _process(_delta):
	if Input.is_action_just_pressed("fire"):
		animation_player.play("charge1")

	if Input.is_action_just_released("fire"):
		animation_player.stop()

Is animationplayer and animation_player intentionally two different variables?

typo just in here, sorry. Good catch though! I re-wrote it just a bit to make things simpler here.

So I just looked at the documentation, because I’ve never had this problem before. After reading it I didn’t think the problem was the AnimationPlayer. So I setup a little test and I can indeed press a button and stop() or pause() my looping idle animation. When I press the attack button, that animation starts and my idle animation continues as before whether stopped or paused.

Potential Solution #1
I think inadvertently you may not have given us all the information that is relevant. I suspect that you are using a controller and pressing either the right or left trigger - which is not a button. At least not as far as Godot is concerned. Triggers send a value from 0 to 1.0 telling you how far in they are pressed. And what you might be experiencing is that after action is released, it is also just pressed because it isn’t a boolean value.

If you’re not using a trigger on a controller, something else is going on. However I think altering your code might help. If it’s just a frame of input, then this may solve it.

func _process(_delta):
	if Input.is_action_just_released("fire"):
		animation_player.stop()
	elif Input.is_action_just_pressed("fire"):
		animation_player.play("charge1")

Potential Solution #2
It also might not be that. It might be that you have this in _process() instead of _physics_process(). I’m not sure why you chose to use _process(), and there may be a very good reason; but my gut tells me this is better off in _physics_process() because playing an animation is technically something you want framerate dependent.

Potential Solution #3
A third potential solution is to play the RESET animation when you want to stop. It’s one frame long, sets everything back to default and isn’t looped.

3 Likes

I’ve always struggled with .stop(). I actually dread it bc it never works for me. It never has.

PS1: Just using a keyboard, but that’s a good thought. Having the released loop first makes sense.
PS2: I’m not sure I necessarily have a good reason, but regardless the .stop() command should work in both.
PS3: I did try this before asking; it also did not work and my looping animation continued.

What I got to work was just not doing .stop() at all and rather creating once instance that plays charge1 and charge2, then when I want it to go away (fire bullet on action released) I queue free that instance and create another (fire_bullet) with the next thing in its place. I did leave a lot out of my code in the original post as I figured it wasn’t necessary, but here you go. It is working now, just with the oddity that I’m removing an instance and creating another. It’s really not such a big deal though, as the charge

## Constants
const Bullet = preload("res://player_bullet.tscn")


## Variables (export, normal, onready)
@onready var blaster_sprite = $BlasterSprite
@onready var muzzle = $BlasterSprite/Muzzle
@onready var charge_timer = $ChargeTimer
@onready var remote_transform_2d = $BlasterSprite/Muzzle/RemoteTransform2D

var charge


## Functions
func _process(_delta):
	blaster_sprite.rotation = get_local_mouse_position().angle()
	if Input.is_action_just_pressed("fire"):
		charge_timer.start()
		charge_bullet()
	if Input.is_action_just_released("fire"):
		fire_bullet()
	if Input.is_action_pressed("fire") == false and charge != null:
		charge.queue_free()


# function for charging up shots.
func charge_bullet():
	charge = Utilities.instantiate_scene(Bullet, muzzle.global_position)
	var charge_path = charge.get_path()
	remote_transform_2d.remote_path = ""
	remote_transform_2d.remote_path = charge_path
	remote_transform_2d.force_update_cache()
	var charge_animation = charge.get_node("Projectile/AnimationPlayer")
	charge_animation.play("charge1")


# function for actually firing the shots.
func fire_bullet():
	var bullet = Utilities.instantiate_scene(Bullet, muzzle.global_position)
	var bullet_animation = bullet.get_node("Projectile/AnimationPlayer")
	bullet.rotation = blaster_sprite.rotation
	if charge_timer.is_stopped():
		bullet_animation.play("charge_shot")
	else:
		bullet_animation.play("fire")

Glad you got it working. It’s still really weird. Something else is going on, but might be time to move on.

TBH, I don’t think I’ve ever used stop() on an AnimationPlayer. But then I typically use them to keep running forever on something that always has an animation running (like a player or enemy with a default idle animation) or as one-shots like with splash screens.