I haven't used AnimatedSprite2D before. I don't like it very much compared to just animating a Sprite2D. The best way to handle something like this is with an AnimationTree, which the tutorial probably also was using. That said, it still can be done with AnimatedSprite2D just fine. Rather than setting a timer, try waiting for the animation to finish. Here is an example:
extends AnimatedSprite2D
var can_transition := true
func _ready() -> void:
try_play_anim(&"default")
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_accept"):
if await try_play_anim(&"attack", false):
try_play_anim(&"default")
func try_play_anim(anim:StringName, can_interrupt = true) -> bool:
if !can_transition:
return false
if can_interrupt:
play(anim)
return false
can_transition = false
play(anim)
await animation_finished
can_transition = true
return true
The try_play_anim function is a helper function and a coroutine. When we play the attack, we don't want it interrupted, so we set the sentinel bool can_transition and then turn it back off once it is done. If we play all of our animations through this function, it will guarantee that animations that shouldn't be interrupted will not be. In your movement code, you can check on the can_transition value to see if you are allowed to move.
This is an example of a very primitive state machine with only two states: can_transition=true and can_transition=false. For more complex sets of animations, it is better to make a fully fleshed out state machine on your own or to use the one Godot provides in AnimationTree.