Coroutine not working second time

So I have been working on this coroutine for a long time now. Its a coroutine that heals you after you’ve been hit after 7 seconds. An example I can give is Brawl Stars where you heal after not taking damage for a while. So I have 2 coroutines. When the player is damaged, SoldierHealTimer() is triggered and a countdown starts. If the player attacks, is damaged, or is healed to full, the Coroutine is stopped. If not, the countdown goes to zero and SoldierHealingtoFull() is triggered and the player starts healing. If the player attacks, is damaged, or is healed to full, the coroutine stops, making way for the timer coroutine to start again. This is exactly what I want and it works perfectly the first time. The problem is, after the first coroutine is stopped (either the healing or timer coroutine, doesn’t matter) the SoldierHealTimer() Coroutine does not start at all for some reason. Please help, this error is really discouraging.

Some info I can give:

  • For some reason, the second time the the coroutine is called, it does everything before while statement EXCEPT for Debug.Log("Coroutine Started 2");
  • The Countdown, does not start at all, despite healTimerTime > 0
  • I have no idea why, but using StartCoroutine(healTimerCoroutine) / using a variable instead of SoldierHealTimer, works for me but using StartCoroutine(SoldierHealCoroutine()) doesn’t. If I use StartCoroutine(SoldierHealCoroutine()) a coroutine is instantly started once the other one ends. Other than that, If I could fix, it looping, StartCoroutine(SoldierHealCoroutine()) could work perfectly

Here is my code:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Events;
    using System;
    using System.Linq;
    
    public class GameHandeler : MonoBehaviour
    {
        public float healTimertime;
        private IEnumerator soldierhealTimerCoroutine;
        private IEnumerator soldierHealingCoroutine;
        public bool healIsRunning;
    
     public void Start()
        {
            //setup HealTimerCoroutine
            healTimertime = 7f;
            soldierhealTimerCoroutine = SoldierHealTimer();
            soldierHealingCoroutine = SoldierHealingToFull();
            healIsRunning = false;
        }
    
    
    public void Update()
        { 
            if (healthSystem.damaged == true || healthSystem.damaged2 == true && !healIsRunning)
            {
                healIsRunning = true;
                healthSystem.damaged = false;
                Debug.Log("Corountine Started");
                StartCoroutine(soldierhealTimerCoroutine);
            }
        }
    
        IEnumerator SoldierHealTimer()
        {
            Debug.Log("Coroutine Started 2");
            healIsRunning = true;
            healthSystem.damaged = false;
            swordAttack.attacking = false;
            swordAttack.attacking2 = false;
    
            while (healTimertime > 0)
            {
                healTimertime--;
                Debug.Log("Countdown: " + healTimertime);
                if (healthSystem.health == 100 || healthSystem.damaged == true || swordAttack.attacking == true)
                {
                    healTimertime = 7f;
                    healIsRunning = false;
                    healthSystem.damaged = false;
                    healthSystem.damaged2 = false;
                    swordAttack.attacking = false;
                    Debug.Log("Coroutine Stopped");
                    StopCoroutine(soldierhealTimerCoroutine);
                }
                yield return new WaitForSeconds(1f);
            }
    
            Debug.Log("Soldier Healing Coroutine Started");
            StartCoroutine(soldierHealingCoroutine);
    
        }
    
        IEnumerator SoldierHealingToFull()
        {
            healthSystem.damaged2 = false;
    
            if (healTimertime <= 0)
            {
                while (healthSystem.health < 100)
                {
                    if (healthSystem.health == 100 || healthSystem.damaged == true || healthSystem.damaged2 == true || swordAttack.attacking == true)
                    {
                        healTimertime = 7f;
                        healIsRunning = false;
                        healthSystem.damaged = false;
                        healthSystem.damaged2 = false;
                        swordAttack.attacking = false;
                        swordAttack.attacking2 = false;
                        Debug.Log("Healing Coroutine Stopped");
                        StopCoroutine(soldierHealingCoroutine);
                    }
                    if (healthSystem.health < 100 && healthSystem.damaged == false && healthSystem.damaged2 == false)
                    {
                        healToFull();
                    }
                    yield return new WaitForSeconds(1.0f);
                }
    
                healIsRunning = false;
                healTimertime = 7f;
                Debug.Log("Healing Coroutine Stopped");
                StopCoroutine(soldierHealingCoroutine);
            }
        }
    
    
        public void healToFull()
        {
            if (healthSystem.health < 100 && healthSystem.damaged == false && healthSystem.damaged2 == false)
            {
                healthSystem.Heal(10);
                Debug.Log("Healed!");
            }
        }
}

Thx for helping me, reply if you need more info

You’re doing several bad / dangerous things here.

First of all you are using too many coroutines. It looks like you have mutually exclusive coroutines. So when one coroutine runs, the other should stop. It would make much more sense to create one coroutine that does all this logic in one big loop. You would just start the coroutine once at start and have all the logic inside the coroutine.

Next issue is you may have the wrong idea what a coroutine actually is. A coroutine is not a method. A coroutine is a statemachine object. When you call your “generator method” it actually creates a statemachine which is returned. You create those inside Start. When you pass this statemachine object to StartCoroutine you essentially “register” this instance to Unity’s coroutine scheduler and it starts “iterating” the statemachine. Based on the yielded values the scheduler determines when this coroutine should be re-scheduled the next time.

When you call StopCoroutine you just “unregister” that statemachine, however the current execution will run up to the next yield statement. Since the coroutine is no longer registered it will essentially stop there. However when you pass the same statemachine instance again to StartCoroutine it is still in the last state it was before so you essentially continue where you left off the last time. If the coroutine / statemachine actually reached the end (actually reaching the end or if it hits a yield break statement) the coroutine is essentilly dead. Passing it again to StartCoroutine has no effect since when Unity’s scheduler tries to run the “next step” MoveNext will just return false since the statemachine reached the end.

To actually restart the coroutine you have to create a new statemachine. So you have to call your generator methods again (SoldierHealTimer and SoldierHealingToFull) to get a new, fresh statemachine. However as mentioned above you just overcomplicate everything by using several coroutines. Creating such a statemachine will allocate memory. Starting and stopping coroutines also isn’t for free. So it makes more sense to use a single coroutine that runs always in the background and does everything.

Next thing is the && has a higher priority than the || operator. That means this line:

if (healthSystem.damaged == true || healthSystem.damaged2 == true && !healIsRunning)

Is actually executing this:

if (healthSystem.damaged == true || (healthSystem.damaged2 == true && !healIsRunning))

So your “healIsRunning” is ignored when your first system is true. That means you would re-schedule the same coroutine instance every frame. Who knows what will actually happen in this case. By creating a single coroutine there’s no need for all those blocking bool variables.

Next those conditions are actually mutually exclusive:

while (healthSystem.health < 100)
{
    if (healthSystem.health == 100 ....

the while loop can only make another iteration when health is smaller than 100. So the condition to check if it’s 100 can never be true and therefore is pointless.

You can get really fancy inside a coroutine as long as you make sure you yield inside every loop you create. It’s hard to determine what your coroutines actually do. However here’s a more general example how you can create a coroutine that “idles” until a certain condition is true and then does things in order:

void Start()
{
    StartCoroutine(Healing());
}
IEnumerator Healing()
{
    while (true)
    {
        // wait until "condition" is true
        while(!condition)
            yield return null;
        
        // start the waiting period of 7 seconds
        // if you just want to wait without further conditions you could simply use WaitForSeconds
        float t = 7f;
        while (t > 0)
        {
            t -= Time.deltaTime;
            yield return null;
        }

        // wait period is over, start the healing
        while(notHealed)
        {
            // do your healing
            yield return new WaitForSeconds(1);
        }
    }
}

Note that his is just an example. However the overall cycle should be much clearer that way. It’s also way better for performance. So the coroutine never actual stops. While it keeps looping in the first while loop waiting for the condition to become true it’s like checking this condition in Update. The nice thing about such a coroutine is, once the actual code runs, the “condition wait loop” will not run and waste processing power (like an Update method does for example).