[SOLVED] Coroutine - yield return null doesn't resume the method

Coroutines will never not be confusing. I’m trying to implement a visual cue when the player shoots and hits an enemy aircraft. A coroutine is called from the bullet GO when it collides with the enemy aircraft GO. The method below is the coroutine that is on the enemy GO:

// Shows the effects of being hit by a bullet.
    public IEnumerator DamageFX() {
        if (damageRunning)
            yield break;
        // Effects are different between player and enemies
        if (player)
        {

        } else {
            Renderer enemyR = GetComponentInChildren<Renderer>();
            if (enemyR.materials.Length > 1) {
                damageRunning = true;
                Debug.Log("<color=white>Flash white material</color>");
                enemyR.material = damageMat;
                yield return null;
                Debug.Log("<color=green>Change back to green material</color>");
                enemyR.material = enemyR.materials[1];
                damageRunning = false;
            }
        }
    }

The documentation says it should continue past the yield return null and print the “Change back to green material” but it doesn’t. Help would be greatly appreciated.

How do you call DamageFX()?

From the trigger collider that is on the bullet that the player shoots. It grabs the enemy GO and calls the coroutine on that GO. Here the code if you want the details:

void OnTriggerEnter(Collider c) {
        if (c.gameObject.transform.parent == null)
            return;
        if (c.gameObject.transform.parent.CompareTag("Enemy")) {
            Transform t = c.gameObject.transform.parent;
            Health h = t.gameObject.GetComponent<Health>();
            if (h) {
                h.ChangeHealth(-power);
                StartCoroutine(h.DamageFX());
            }

            // It did its job. Remove it
            PoolController.Instance.Destroy(gameObject);
        }
    }

Here’s your problem:

PoolController.Instance.Destroy(gameObject);

You destroy the GameObject on which the coroutine is running.

A coroutine runs on the MonoBehaviour that you called StartCoroutine on. If that MonoBehaviour is disabled/destroyed, all its coroutines stop. And destroying a GameObject destroys the MonoBehaviours on the GameObject.

2 Likes

You need to yield at the end

public IEnumerator DamageFX() {
        if (damageRunning)
            yield break;
        // Effects are different between player and enemies
        if (player)
        {
        } else {
            Renderer enemyR = GetComponentInChildren<Renderer>();
            if (enemyR.materials.Length > 1) {
                damageRunning = true;
                Debug.Log("<color=white>Flash white material</color>");
                enemyR.material = damageMat;
                yield return null;
                Debug.Log("<color=green>Change back to green material</color>");
                enemyR.material = enemyR.materials[1];
                damageRunning = false;
            }
        }
yield return null
    }

No you don’t.

You would only have to put that if there was no other yield statement in the code… and that’s just because an iterator function must have a yield somewhere (or return an empty enumerator).

3 Likes

lordofduct is right, you’re destroying the thing running the coroutine.

The fix is to run the coroutine on the health, which is a thing a lot of people don’t realize they can do:

Health h = t.gameObject.GetComponent<Health>();
if (h) {
    h.ChangeHealth(-power);
    h.StartCoroutine(h.DamageFX());
}

This is a with great responsibility situation. Sticking coroutines onto other objects is a bit hairy, as it can be hard to follow the logic. In this case, I believe that DamageFX should just be a normal method, which starts a coroutine in Health.

@WarmedxMints_1 , no. That makes no sense. Sticking a yield return null at the end of the coroutine only makes it return one frame later, which is a silly thing to do.

2 Likes

Yes, Baste, you and lordofduct saved this feature. I didn’t know you could do that with coroutines, but I went ahead and changed DamageFX to a normal method that calls a separate coroutine. Makes it a little more readable.

Thanks!