How can I get around using yield in process?

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

This is my current code:

func _process(delta):
    if health <= 0:
	    queue_free()

    if playerInRange:
	    attack()
	    yield(get_tree().create_timer(3.0), "timeout")

func attack():
    sprite.animation = "attack"
    yield(sprite, "animation_finished")
    if playerInAttackRange and !hasDamaged:
	    hasDamaged = true
	    player.damage(1, self)
    hasDamaged = false
    sprite.animation = "idle"

The idea being that the enemy will, every 3 seconds, play an attack animation, and deal damage to the player if they are in range of it.

The problem is that the yield function for 3 seconds doesn’t appear to be doing anything, the attack animation simply ends up looping, and going into the attack range causes the game to crash because of player.damage being called over and over at high speed (damage gives a print message which overflows)

After searching on google I think I determined that I shouldn’t be using yield in this way in the process function, but I can’t come up with any other way to achieve my desired result. Would anyone be able to let me know what I’m doing wrong here?

:bust_in_silhouette: Reply From: Ninfur

One simple way would be keep track of how much time has passed since the last attack. This can be done by accumulating the delta value (time between frames), and then reset it to 0 once it has reached a threshold.

var elapsed: float

func _process(delta):
    elapsed += delta

    if elapsed > 3.0:
        attack()
        elapsed = 0
:bust_in_silhouette: Reply From: Ilmari

You are only yielding that particular execution of the _process() function, which is different from the _process() instance that will happen a few milliseconds after it.

Your description of what you wanna achieve sounds different from the code. Do you want to have the enemy to run the attack animation every three seconds, or only if the player is in range? The code sounds like the latter, your description like the former.

If it should run the animation whether or not the player is there, you should add a three-second Timer to the enemy and have the timer trigger the animation, and either have the timer on repeat or restart it at the end of the animation.

If you only want the attack to happen if the player is near, add an Area to the enemy (you probably have, since you have playerInRange()?) and connect the attack code to “area_entered”, and then have that first disable the Area trigger, and then start a three-second Timer which re-enables the Area on timeout.

You could (and probably should) remove the _process() function implementation altogether if there’s nothing else in it, and have whatever can damage the enemy execute a check (either directly, or calling a function in the enemy code) for whether or not it got reduced to zero hitpoints. _process() should only be used for things that really do require checking or adjusting every frame for example, you can get rid of most other needs for it with a combination of event-based signaling and Timers instead. Having a Timer execute every 0.1 seconds may not sound like that big of a difference to running a check every frame, but for the machine running the code, it is, and having a bunch of _process() instances running at all times is gonna start slowing things down. With basic games it may well be that only one _process() is needed, the one handling the controls.