I haven't touched this project in a long while so I might not be able to explain everything, but here's what I did.
My enemies now have this method in their controller script:
func hit():
# do fancy hit stuff (animations, dmg etc.)
$FlashAnimationPlayer.play("flash")
hp -= rng.randi_range(2,4)
if hp <= 0 and alive:
alive = false
main_node.decrease_mob_count(1)
$AnimationPlayer.play("die")
yield($AnimationPlayer, "animation_finished")
queue_free()
And my bullets correspondingly have this:
func _on_Bullet_body_entered(body):
if body.has_method("hit"):
body.hit()
# other stuff below
$AnimationPlayer.play("explode")
alive = false
yield($AnimationPlayer, "animation_finished")
queue_free()
To connect the signals, I use this in my bullet controller script:
func _ready():
connect("body_entered", self, "_on_Bullet_body_entered") <-- this!
get_node("VisibilityNotifier2D").connect("screen_exited", self, "_on_screen_exited")
Hope this helps!