Search Unity

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

Discussion in 'Scripting' started by cyangamer, Jun 4, 2017.

  1. cyangamer

    cyangamer

    Joined:
    Feb 17, 2010
    Posts:
    234
    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:
    Code (CSharp):
    1. // Shows the effects of being hit by a bullet.
    2.     public IEnumerator DamageFX() {
    3.         if (damageRunning)
    4.             yield break;
    5.         // Effects are different between player and enemies
    6.         if (player)
    7.         {
    8.  
    9.         } else {
    10.             Renderer enemyR = GetComponentInChildren<Renderer>();
    11.             if (enemyR.materials.Length > 1) {
    12.                 damageRunning = true;
    13.                 Debug.Log("<color=white>Flash white material</color>");
    14.                 enemyR.material = damageMat;
    15.                 yield return null;
    16.                 Debug.Log("<color=green>Change back to green material</color>");
    17.                 enemyR.material = enemyR.materials[1];
    18.                 damageRunning = false;
    19.             }
    20.         }
    21.     }
    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.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    How do you call DamageFX()?
     
  3. cyangamer

    cyangamer

    Joined:
    Feb 17, 2010
    Posts:
    234
    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:
    Code (CSharp):
    1. void OnTriggerEnter(Collider c) {
    2.         if (c.gameObject.transform.parent == null)
    3.             return;
    4.         if (c.gameObject.transform.parent.CompareTag("Enemy")) {
    5.             Transform t = c.gameObject.transform.parent;
    6.             Health h = t.gameObject.GetComponent<Health>();
    7.             if (h) {
    8.                 h.ChangeHealth(-power);
    9.                 StartCoroutine(h.DamageFX());
    10.             }
    11.  
    12.             // It did its job. Remove it
    13.             PoolController.Instance.Destroy(gameObject);
    14.         }
    15.     }
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    Here's your problem:
    Code (csharp):
    1. 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.
     
    Ryiah and cyangamer like this.
  5. WarmedxMints

    WarmedxMints

    Joined:
    Feb 6, 2017
    Posts:
    1,035
    You need to yield at the end

    Code (CSharp):
    1. public IEnumerator DamageFX() {
    2.         if (damageRunning)
    3.             yield break;
    4.         // Effects are different between player and enemies
    5.         if (player)
    6.         {
    7.         } else {
    8.             Renderer enemyR = GetComponentInChildren<Renderer>();
    9.             if (enemyR.materials.Length > 1) {
    10.                 damageRunning = true;
    11.                 Debug.Log("<color=white>Flash white material</color>");
    12.                 enemyR.material = damageMat;
    13.                 yield return null;
    14.                 Debug.Log("<color=green>Change back to green material</color>");
    15.                 enemyR.material = enemyR.materials[1];
    16.                 damageRunning = false;
    17.             }
    18.         }
    19. yield return null
    20.     }
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    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).
     
    Ryiah, cyangamer and TaleOf4Gamers like this.
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    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:

    Code (csharp):
    1. Health h = t.gameObject.GetComponent<Health>();
    2. if (h) {
    3.     h.ChangeHealth(-power);
    4.     h.StartCoroutine(h.DamageFX());
    5. }

    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, 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.
     
    Ryiah and cyangamer like this.
  8. cyangamer

    cyangamer

    Joined:
    Feb 17, 2010
    Posts:
    234
    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!