Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Delaying a Destroy() within a coroutine

Discussion in 'Scripting' started by qkjosh, Feb 4, 2016.

  1. qkjosh

    qkjosh

    Joined:
    Nov 29, 2015
    Posts:
    33
    Hi, I am trying to figure out why I'm receiving a missing reference exception in the following code.

    I first instantiate a particle system as a child game object of another game object. Then, on a subsequent key press, I run a coroutine to handle the removal of that particle system. When it is run the first time, it marks the particle system to be destroyed two seconds from now. However, if this coroutine gets run again while the particle system still exists, it queues up the Destroy method on an object already marked for removal. The item gets destroyed, and then when the destroy method gets called a second time, "particles" no longer references an existing game object. At least, that's my suspicion.

    Is there a way to get around this? I thought a simple null check would be sufficient, but apparently not.

    Code (CSharp):
    1. foreach (Transform ps in targetItem.transform)
    2. {
    3. StartCoroutine(destroyHoldEffect(ps.gameObject));
    4. }
    Code (CSharp):
    1.     IEnumerator destroyHoldEffect(GameObject particles)
    2.     {
    3.             while (particles.GetComponent<ParticleSystem>().time > 0.5f)
    4.             {
    5.                 yield return null;
    6.             }
    7.  
    8.             particles.GetComponent<ParticleSystem>().loop = false;
    9.  
    10.             Destroy(particles, 2f);
    11.     }
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You could always put in a WaitForSeconds(2) and then a null check. Instead of using the delayed destroy.
     
  3. qkjosh

    qkjosh

    Joined:
    Nov 29, 2015
    Posts:
    33
    Hmm, thanks. That's what I thought, but the following code still gives me the same error:

    EDIT: I believe the while statement is causing the issue now.

    Code (CSharp):
    1.     IEnumerator destroyHoldEffect(GameObject particles)
    2.     {
    3.         while (particles.GetComponent<ParticleSystem>().time > 0.5f)
    4.         {
    5.             yield return null;
    6.         }
    7.          
    8.         particles.GetComponent<ParticleSystem>().loop = false;
    9.  
    10.         yield return new WaitForSeconds(2f);
    11.         if (particles)
    12.         {
    13.             Destroy(particles);
    14.         }
    15.     }
     
    Last edited: Feb 4, 2016
  4. Teravisor

    Teravisor

    Joined:
    Dec 29, 2014
    Posts:
    654
    particles.GetComponent<ParticleSystem>() asserts particles variable isn't null, otherwise you're calling a null.GetComponent() - and null is not an object reference. I'd insert if(particles==null)yield break; (to force end the coroutine) before it if you're so bad at managing what got deleted and what did not...
     
    Last edited: Feb 4, 2016
  5. qkjosh

    qkjosh

    Joined:
    Nov 29, 2015
    Posts:
    33
    Thanks for the suggestion. I'll admit I'm pretty new to C# and OOP so I'm still figuring out how these things work.
    It doesn't seem like I am able to simply call "return" on its own though. Yield return null is fine syntactically, but evidently not what you intended.
     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    More options.
    • Check if the coroutine is running and don't start a new one if it is
    • Run the coroutine on the destroyed object. Coroutines will stop running once destroy is called
    • Check for null inside the while statement and call yield return break. This will stop the coroutine.
     
  7. qkjosh

    qkjosh

    Joined:
    Nov 29, 2015
    Posts:
    33
    Thanks for the ideas, both of you. I gave them a try but was still getting errors, either because I'm implementing them incorrectly or misunderstanding the concept. Anyways, I decided to simplify my coroutine and only allow one instance of particle system at a time - now I no longer get the errors. For reference, here's the "fix".

    Code (CSharp):
    1.     IEnumerator destroyHoldEffect(GameObject particles)
    2.     {
    3.             yield return new WaitForSeconds(0.5f);
    4.             if (particles)
    5.             particles.GetComponent<ParticleSystem>().loop = false;
    6.  
    7.             yield return new WaitForSeconds(1f);
    8.             if (particles)
    9.             Destroy(particles);
    10.     }
     
  8. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199

    To end a coroutine (IEnumerator), you do a yield break, not a return - returning won't compile outside of a void method.
     
    Teravisor and ThermalFusion like this.
  9. Teravisor

    Teravisor

    Joined:
    Dec 29, 2014
    Posts:
    654
    You're right, edited that post to fix it.
     
  10. A.Killingbeck

    A.Killingbeck

    Joined:
    Feb 21, 2014
    Posts:
    483
    You could simply create a script like: PrepareToBeDestroyed which gets added when you "want" to destroy it. Then you could perform a check either inside your coroutine or before it for the existence of that script. If it exists, don't start the coroutine.

    e.g.


    Code (CSharp):
    1. public class PrepareToBeDestroyed : MonoBehaviour
    2. {
    3.    public void DestroyAfter(float seconds)
    4. {
    5.     Destroy(gameObject,seconds);
    6. }
    7. }
    Code (CSharp):
    1. foreach (Transform ps in targetItem.transform)
    2. {
    3. if(ps.gameObject.GetComponent<PrepareToBeDestroyed>() == null)
    4. {
    5. StartCoroutine(destroyHoldEffect(ps.gameObject));
    6. }
    7. }
    Code (CSharp):
    1. IEnumerator destroyHoldEffect(GameObject particles)
    2.     {
    3.             while (particles.GetComponent<ParticleSystem>().time > 0.5f)
    4.             {
    5.                 yield return null;
    6.             }
    7.             particles.GetComponent<ParticleSystem>().loop = false;
    8.          PrepareToBeDestroyed ppD=  particles.AddComponent<PrepareToBeDestroyed>();
    9.             ppD.DestroyAfter(2.0f);
    10.     }