Coroutine ends prematurely if called from another script

Projectile/Script A:
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Enemy"))
        {
            StartCoroutine(other.GetComponent<EnemyMovement>().Slow());
        }
    }

Enemy/Script B:
    public IEnumerator Slow()
    {
        Debug.Log("Coroutine Started");
        yield return new WaitForSeconds(2f);
        Debug.Log("Coroutine Resumed");
    }

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]

Firstly, please use code tags when posting code: Using code tags properly

Secondly, is said projectile destroying itself, or getting destroyed, or is the game object getting disabled at all?

1 Like

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

2 Likes

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.

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:

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Enemy"))
        {
            var enemy = other.GetComponent<EnemyMovement>();
            enemy.StartCoroutine(enemy.Slow());
        }
    }

so that the EnemyMovement script will be the one responsible for running the coroutine

3 Likes

Yes, you actually are spot on. You “could” have done this:

EnemyMovement em = other.GetComponent<EnemyMovement>();
em.StartCoroutine(em.Slow());

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.

2 Likes