AnimatedSprite.play() freeze with user _input() on last frame

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By indruckbalazs

Hi!

I’m a beginner Godot engine user and I have a question regarding AnimatedSprite and handling user input with _input().

I’m following through this nice tutorial: https://www.davidepesce.com/2019/10/23/godot-tutorial-8-2d-sprite-animation/

When I tested the game I started to initiate an attack by pressing the space key repeatedly. If I press the space fast enough, sometimes the attack animation is stuck on the last frame. I still can move around, but neither the walk animation nor the idle animation will play anymore.

However, when I press the fireball key (ctrl key), it can transition to the fireball animation and when that’s over, the walk and idle animations work again.

Also, this behaviour happens with the fireball animation. If I press ctrl fast enough it will stuck on the last frame and it can only transition forward if I press attack.

In the tutorial, this problem is solved in the 11th part, when we introduce a global cooldown for attack animation and we can’t attack (theoretically) infinitely fast.

However, I have a question: I tried to solve the above issue by writing the following if statement before attacking (I check if an attack animation is already going on):

if not attack_playing:

Still, the bug hasn’t disappeared from that and I don’t understand why. I also tried to debug it with print statements, but I couldn’t find anything interesting with that. Animation is stuck, it will not call the animation_finished signal (so attack_playing will not be reset) and walking and idle animations will not play, only with fireball can we transition again (of course in the case of my solution, we can’t move forward with the other type of attack because of the if condition…).

I can only think that the attack/fireball animation somehow conflicts with user input. Can anyone help me with this?

I paste my code below (the global cooldown solution from tutorial 11 is commented out in _input()):

extends KinematicBody2D

# Initial value (0,1) to face down
var last_direction = Vector2(0,1)

# Player stats
var health = 50
var health_max = 100
var health_regeneration = 1
var mana = 100
var mana_max = 100
var mana_regeneration = 2

# Player movement speed
export var speed = 75

signal player_stats_changed

# Attack variables
var attack_cooldown_time = 1000
var next_attack_time = 0
var attack_damage = 30

var attack_playing = false


func _ready():
	emit_signal("player_stats_changed", self)


func _process(delta):
	# Regenerates mana
	var new_mana = min(mana + mana_regeneration * delta, mana_max)
	if new_mana != mana:
		mana = new_mana
		emit_signal("player_stats_changed", self)
		
	# Regenerates health
	var new_health = min(health + health_regeneration * delta, health_max)
	if new_health != health:
		health = new_health
		emit_signal("player_stats_changed", self)


func _physics_process(delta):
	# Get player input
	var direction: Vector2
	direction.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
	direction.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
	
	# Normalize for directional speed
	if abs(direction.x) == 1 and abs(direction.y) == 1:
		direction = direction.normalized()
		
	# Apply movement
	var movement = speed * direction * delta
	if attack_playing:
		movement = 0.3 * movement  # when attacking, movement speed is slower
	movement = move_and_collide(movement)
	
	# Animates player based on direction
	if not attack_playing:
		animates_player(direction)


func animates_player(direction: Vector2):
	if direction != Vector2.ZERO:
		# Update last direction
		last_direction = direction
		
		var animation = get_animation_direction(last_direction) + "_walk"
		
		# Play walk animation
		$Sprite.play(animation)
	else:
		# Choose idle animation based on last movement direction and play it
		var animation = get_animation_direction(last_direction) + "_idle"
		$Sprite.play(animation)


func _input(event):
	if not attack_playing:
		if event.is_action_pressed("attack"):
			# Check if player can attack
			#var now = OS.get_ticks_msec()
			#if now >= next_attack_time:
			attack_playing = true
			var animation = get_animation_direction(last_direction) + "_attack"
			$Sprite.play(animation)
			# Add cooldown time to current time
			#next_attack_time = now + attack_cooldown_time
		elif event.is_action_pressed("fireball"):
			if mana >= 25:
				mana = mana - 25
				emit_signal("player_stats_changed", self)
				attack_playing = true
				var animation = get_animation_direction(last_direction) + "_fireball"
				$Sprite.play(animation)


func get_animation_direction(direction: Vector2):
	var norm_direction = direction.normalized()
	if norm_direction.y >= 0.707:
		return "down"
	elif norm_direction.y <= -0.707:
		return "up"
	elif norm_direction.x <= -0.707:
		return "left"
	elif norm_direction.x >= 0.707:
		return "right"
	
	return "down"


func _on_Sprite_animation_finished():
	attack_playing = false