The first one disables the CollisionShape2D at the end of the frame. The second one enables the CollisionShape2D at the end of the frame.
So what’s happening is all your other die code is running, then at the end of the frame you’re disabling and re-enabling the CollisionShape2D before anything else happens, which triggers a second collision. So the code runs again.
For whatever reason, that run probably sets them to run at the end of the next from and at that point, the player has been moved. So the cycle stops.
Easiest solution is to not defer those calls. Since you’re not in a _process() or _physics_process() function, you should be ok.
There’s an await sprite.animation_finished between disabling and re-enabling the collision shape, so it shouldn’t happen at the same time.
Also, there has been another topic recently, where disabling a collision shape without set_deferred() was fine inside _physics_process(), but not in response to a signal, so I definitely wouldn’t recommend omitting it here.
For solving the issue: Maybe the player is hitting two different spike objects during the same _physics_process()? That should call die() multiple times.
I’ve tested it on just a singular spike and it still dies twice.
My theory
It’s calling the die() function twice. The die() function is called from the Spike scene whenever the player enters it. Unfold below for spike.gd script.
spike.gd
extends Area2D # spike.gd
# connected to body_entered signal
func _on_body_entered(body: CharacterBody2D) -> void:
if body.name == "Player":
body.die()
Therefore, Spike.body_entered is being emitted twice.
I tried this. It worked… the first time. Not the second.
EDIT: Now it’s working all the time. My brain hurts.
EDIT 2: Now it’s not working. I see why people recommend set_deferred().
Well, for preventing the function to run twice I guess it should be enough to check the current animation:
func die() -> void:
if sprite.animation == "die":
return
# original code block
For properly solving the source of this issue, I’m pretty much out of ideas. I had been thinking about if this could be caused by the spike’s signal being connected to an additional spike instance (both of them calling die() once), but then removing set_deferred() shouldn’t change anything?
It could also be related to the player (and his movement), but then I wouldn’t expect it to happen consistently, but only occasionally. Though I think this would be the most likely possibility left.
Add a breakpoint on this line, and trace back once it stops there. You’ll hopefully see and understand what’s happenning when the player dies more than once.
This works, but I always fix it with a flag, flags for the win.
#At top of player with other variables:
# Flags
var isAlreadyDead := false ## Death flag, set by die(), could also set it elsewhere for immortality buff.
func die() -> void:
# Flag check, if the function runs once it will stop here.
if isAlreadyDead:
return
# Flag set, immediately so the await doesn't allow for another frame's of damage to come through
isAlreadyDead = true
$CollisionShape2D.set_deferred("disabled", true)
is_in_control = false
sprite.play("die")
await sprite.animation_finished
position = player_spawn_point.position
sprite.stop()
if health > 0:
set_health.emit(health - 1)
health -= 1
is_in_control = true
$CollisionShape2D.set_deferred("disabled", false)
This better solves the problem if the animation is done playing and yet somehow despite the collision being disabled, which it could not be due to the await, the second will stop.