Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only.

    Please, do not make any changes to your username or email addresses at id.unity.com during this transition time.

    It's still possible to reply to existing private message conversations during the migration, but any new replies you post will be missing after the main migration is complete. We'll do our best to migrate these messages in a follow-up step.

    On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live.


    Read our full announcement for more information and let us know if you have any questions.

Question Coroutine ends prematurely if called from another script

Discussion in 'Scripting' started by Unideer, Feb 21, 2024.

  1. Unideer

    Unideer

    Joined:
    Feb 24, 2023
    Posts:
    21
    Code (csharp):
    1. Projectile/Script A:
    2.     void OnTriggerEnter2D(Collider2D other)
    3.     {
    4.         if (other.CompareTag("Enemy"))
    5.         {
    6.             StartCoroutine(other.GetComponent<EnemyMovement>().Slow());
    7.         }
    8.     }
    9.  
    10. Enemy/Script B:
    11.     public IEnumerator Slow()
    12.     {
    13.         Debug.Log("Coroutine Started");
    14.         yield return new WaitForSeconds(2f);
    15.         Debug.Log("Coroutine Resumed");
    16.     }
    17.  
    When projectile hits the enemy, 'Debug.Log("Coroutine Started");' will show. However, after 2s, 'Debug.Log("Coroutine Resumed");' will never show.

    Am I not suppose to use coroutine to call an IEnumerator from another script?[/CODE]
     
    Last edited: Feb 21, 2024
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,647
    Ryiah likes this.
  3. Unideer

    Unideer

    Joined:
    Feb 24, 2023
    Posts:
    21
    Firstly, will do.

    Oops, i think i know why, the projectile is destroying itself so any scheduled coroutine will be destroyed as well?

    If so, i think i'll just make a separate function to hold the coroutine call on the enemy script, letting the projectile call it so coroutine happens on the enemy.

    So is it the best approach? It's just this way I'll have to juggle parameters between functions making things a bit messy.

    Thanks
     
    Last edited: Feb 21, 2024
    Ryiah and Bunny83 like this.
  4. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,533
    It's a common gotcha situation. From the docs:
    Stopping coroutines
    To stop a coroutine, use StopCoroutine and StopAllCoroutines. A coroutine also stops if you’ve set SetActive to false to disable the GameObject
    the coroutine is attached to. Calling Destroy(example) (where example is a MonoBehaviour instance) immediately triggers OnDisable and Unity processes the coroutine, effectively stopping it. Finally, OnDestroy is invoked at the end of the frame.
    Note: If you’ve disabled a MonoBehaviour by setting enabled to false, Unity doesn’t stop coroutines.
     
    Last edited: Feb 21, 2024
  5. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    Specifically, your telling Projectile to be the one to run the coroutine FROM the projectile. its not running on the enemy script. Your use of StartCoroutine() is implicitly "this.StartCoroutine()" so your telling the projectile script to be responsible for running it. instead you'll want something like:
    Code (CSharp):
    1.     void OnTriggerEnter2D(Collider2D other)
    2.     {
    3.         if (other.CompareTag("Enemy"))
    4.         {
    5.             var enemy = other.GetComponent<EnemyMovement>();
    6.             enemy.StartCoroutine(enemy.Slow());
    7.         }
    8.     }
    so that the EnemyMovement script will be the one responsible for running the coroutine
     
    Ryiah, Bunny83 and spiney199 like this.
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,261
    Yes, you actually are spot on. You "could" have done this:

    Code (CSharp):
    1. EnemyMovement em = other.GetComponent<EnemyMovement>();
    2. em.StartCoroutine(em.Slow());
    3.  
    Though using a wrapper method that actually starts the coroutine is generally the better approach as what I just showed kinda violated the prime principle of OOP. We schedule stuff on another object. The object should be in charge of that. Though it is a possiblity as long as you are aware of those quirks.

    Note that coroutines are not really methods but objects. When you call "Show()" you don't run your coroutine, you just create a statemachine object. That object is then interated by Unity's coroutine scheduler once you hand it over to StartCoroutine. Though as I realised yourself, you scheduled the iterator object on the projectile. For Coroutines it's usually best practise to keep them private and create explicit methods to start them.
     
    spiney199 and Ryiah like this.