Player health getting spammed

I’m trying to make an enemy wait for the player to do something, then after 2 seconds attack the player and wait for player input again.

Here’s the code so far:

void Update() {

        if (playerTurn == false) StartCoroutine(CycleEnemies());
    }

    public void Attack() {

        if (playerTurn == true) enemies[0].GetComponent<EnemyStats>().enemyHealth -= playerStatsHandler.attack;
        playerTurn = false;
    }

    IEnumerator CycleEnemies() {

        foreach (GameObject enemy in enemies) {

            yield return new WaitForSeconds(2);

            playerStatsHandler.TakeDamage(enemy.GetComponent<EnemyStats>().enemyAttack);

            if (System.Array.IndexOf(enemies, enemy) == enemies.Length - 1) {

                playerTurn = true;
            }
        }
    }

The player presses a UI button which triggers “Attack”, which then sets “playerTurn” to false, allowing “CycleEnemies” to run.

What should happen is each enemy (currently only have one for simplicity) waits 2 seconds and attacks the player, if the enemy is the last in the array of enemies, it will set “playerTurn” to true, not letting the method run again on its own, and waiting for the player to press the button again.

What happens instead, is the enemy waits 2 seconds, then every frame takes the player’s health down forever.

I did google multiple things, like “WaitForSeconds only running once”, everything I found hasn’t worked so far.

This is the only thing in “TakeDamage”:
health -= damage;

And the method is “public void”.

Did you check that the boolean is being set to false constantly? How are you using the attack function? There’s clearly something being triggered each frame in the update somehow if it’s just being spammed. I think also you might be playing the coroutine each frame which might be why you’re having problems too but I’ll need to double check that, you just want it to happen once.

The bool is not being set to false constantly (just checked). And yep, the coroutine is getting spam-called.

1 Like

Added a second bool “enemyAttacking”, checked for it along with “playerTurn”, and set it to true at the start of the coroutine and back to false at the end. Seems to be working fine now.

2 Likes

Nice, I’ve run into issues with coroutines doing that myself.

Update is called every frame, so you’re invoking CycleEnemies again every frame when playerTurn is false. What you need to do is to only invoke CycleEnemies once in your update loop, then wait until it has finished.

One way to achieve this is to use a coroutine instead of the Update function.

    private IEnumerator updateLoop;

    void OnEnable()
    {
        updateLoop = UpdateLoop();
        StartCoroutine(updateLoop); // start the update loop
    }

    IEnumerator UpdateLoop()
    {
        while(true)
        {
            if(!playerTurn)
            {
                yield return CycleEnemies(); // wait until CycleEnemies has finished
            }
            yield return null; // wait until next frame
        }
    }

    public void Attack()
    {
        if(playerTurn)
        {
            enemies[0].GetComponent<EnemyStats>().enemyHealth -= playerStatsHandler.attack;
            playerTurn = false;
        }
    }

    IEnumerator CycleEnemies()
    {
        foreach(var enemy in enemies)
        {
            yield return new WaitForSeconds(2f);

            playerStatsHandler.TakeDamage(enemy.GetComponent<EnemyStats>().enemyAttack);
        }

        playerTurn = true;
    }

    private void OnDisable()
    {
        StopCoroutine(updateLoop);
    }
1 Like