[SOLVED]Sword animation is tweaking out

Hello. My sword animation is breaking. I have read through the code about 20 times now, and I have no idea what’s going on. Nothing I do fixes it. The sword basically just only works sometimes, always stops working, or the player starts bugging out. Here is my scene tree.

Here is my code

And here’s a video of what happens.(sorry for the exceedingly bad quality.) Additionally, I only clicked 3-4 times total during the video.

Please help, thank you.

Using await like that creates an unmanageable pile of coroutines.

As one example: What happens if “swordSlash” starts getting pressed while “swordArmReturnOne” is playing from line 66? You’ll have one coroutine waiting in line 69, while the pressed button starts a new animation and waits for the same signal in line 52. When that animation is finished, both coroutines will continue: One sets spellcastingYN to false, the other will start playing the slash animations.

And this is just one example, there are probably dozens of ways this could break in similar ways.

Can you explain what you expect to happen versus what really happens?

Can you paste your code instead of a screenshot too?

What’s the alternative to await?

I expect it to only hit once if I click once, and then go back and forth if I hold down my mouse. Here is my code(please tell me if I formatted it wrong. I am new to using this forum)

extends CharacterBody2D

class_name Player

@onready var animatedSprite = $AnimatedSprite2D
@onready var animationPlayer = $AnimationPlayer
@onready var swordCollisionShape = $swordSlashCollision/CollisionShape2D
@onready var Fireball = preload("res://tscnFolder/fireball.tscn")
@onready var heal=preload("res://tscnFolder/heal_body.tscn")
@onready var bullet = preload("res://tscnFolder/bullet.tscn")

@export var playerHealth = 100
@export var mana = 100
@export var speed=100
@export var swordDamage = 50
@export var bulletDamage = 25

var shootingContinue = false
var slashingContinue = false
var slashAnimNumber: int
var spellcastingYN = false
var invincible = false

var spellcast1Ready = true
var spellcast2Ready = true

func _velocityControl():
	if velocity.y>0:
		velocity.y-=1
	elif velocity.y<0:
		velocity.y+=1
	if velocity.x>0:
		velocity.x-=1

func _ready() -> void:
	swordCollisionShape.disabled = true
	position = Vector2(0,0)

func move():
	var input_direction = Input.get_vector("Left","Right","Up","Down")
	if input_direction:
		if spellcastingYN == false:
			$AnimatedSprite2D.play("walking")
	elif spellcastingYN == false:
		$AnimatedSprite2D.play("idle")
	velocity = input_direction * speed

func _input(event):
	if event.is_action_pressed("swordSlash"):
		spellcastingYN = true
		animationPlayer.play("swordSlashInitial")
		await animationPlayer.animation_finished
		slashingContinue = true
		slashAnimNumber = 1
		while slashingContinue:
			await animationPlayer.animation_finished
			animationPlayer.play("swordSlashContinueOne")
			slashAnimNumber = 2
			await animationPlayer.animation_finished
			animationPlayer.play("swordSlashContinueTwo")
			slashAnimNumber = 1
	if event.is_action_released("swordSlash"):
		slashingContinue = false
		await animationPlayer.animation_finished
		if slashAnimNumber == 1:
			animatedSprite.play("swordArmReturnOne")
		elif slashAnimNumber == 2:
			animationPlayer.play("RESET")
		await animatedSprite.animation_finished
		spellcastingYN = false
		

Instead of that, do this:

func _ready() -> void:
	animationPlayer.animation_finished.connect(_on_animation_complete)

func _on_animation_complete() -> void:
	if Input.is_action_pressed("swordSlash"):
		match(slashAnimNumber):
			1:
				animationPlayer.play("swordSlashContinueOne")
			2:
				animationPlayer.play("swordSlashContinueTwo")
# etc...

I guess the easiest way is to use an AnimationTree with an AnimationNodeStateMachine for controlling the animation player. You’d have to create your animation sequence in their, then just pass the relevant inputs into it and it will handle the sequencing.

If you don’t want to use an AnimationTree, you can do something like dragonforge suggested. The animation_finished signal will even give you the name of the finished animation as parameter, so you can just use the previous animation and the current input to set the next animation.

This works well, thank you. I have a question though: Why does the function get called on ready, rather than delta? How does the function continue after ready? Additionally, how would I make this loop? After ~an hour of trying to make it loop, I am very confused. Thank you.

1 Like

You should read this step-by-step guide of connecting signals in the Godot docs. The reason it works, is you’re telling it every time the signal happens to call that method.

To get it working with multiple animations and looping you can do this:

const SWORD_SLASH_START = "swordSlash"
const SWORD_SLASH_CONTINUE_1 = "swordSlashContinueOne"
const SWORD_SLASH_CONTINUE_2 = "swordSlashContinueTwo"

func _ready() -> void:
	animationPlayer.animation_finished.connect(_on_animation_complete)

func _on_animation_complete(animation_name: String) -> void:
	if Input.is_action_pressed("swordSlash"):
		match(animation_name):
			SWORD_SLASH_START:
				animationPlayer.play(SWORD_SLASH_CONTINUE_1)
			SWORD_SLASH_CONTINUE_1:
				animationPlayer.play(SWORD_SLASH_CONTINUE_2)
			SWORD_SLASH_CONTINUE_2:
				animationPlayer.play(SWORD_SLASH_START)

The constants aren’t necessary, but it’s good programming practice. Also, now you don’t need the slashAnimNumber variable anymore. You can delete it.

While we are on the subject of naming conventions, in Godot the standard is snake_case for variable names. You are using what’s known as camelCase. Using standards like this makes your code more readable by others, and it makes it easier for you to understand other code due to the cognitive load of reading code being later. For example, when I see SWORD_SLASH_CONTINUE_2 in code I know that it is an immutable constant in code that cannot be changed - and I know I can find the defition at the very top of the file if I want to see what it is or edit it.

P.S. Glad that solution worked for you. That’s my 150th accepted solution on here! Woo!

Thanks, this is really helpful.

1 Like