Coroutine on Don't Destroy On Load stops running

Hi there,

I’m new to loading scenes. The scenario is this; on a field level, I bump into a monster. OnCollisionEnter sends a message to a GameManager that is on a Don’t Destroy on Load gameobject.
The GameManager’s coroutine that is called is as following:

    public IEnumerator StartBattle(List<GameObject> enemies)
    {
        UnityEngine.SceneManagement.SceneManager.LoadScene("Battle Scene");
        yield return new WaitForSecondsRealtime(.5f);
        Debug.Log("Does the app reach here?"); // Doesn't reach here
        BattleSystem battleSystem = GameObject.FindGameObjectWithTag("Battle System").GetComponent<BattleSystem>();
      


        yield break;
    }

It will execute the Load Scene, and the wait. If I remove the wait it will give me a bug that it can’t find the BattleSystem (presumably because the new scene hasn’t quite loaded yet). So, I added the wait.
However, the coroutine does not continue running after that. The Debug.Log(“Does the app reach here”) is never called. I’m stumped; this thing is on a DDOL object yet stops.

I saw some video’s on scene loading and I can’t work with constants here, nor playerprefs as it’s a Windows program. Do I really have to save things in between scenes or am I missing a way that this coroutine can keep running?

I got the wanted behaviour without a coroutine. I now use an enumerator that keeps the state of the Game.

public enum GameState { FIELD, BATTLEINIT, BATTLE}

I use an update function to check the state in the gamemanager.
If the state is BATTLEINIT, it will call a function. The function first checks whether the active scene is the battle scene, otherwise it returns.
When the function has properly executed it sets its state to Battle.
Update Method:

 private void Update()
    {
        switch (GameState)
        {
            case GameState.FIELD:
                break;
            case GameState.BATTLEINIT:
                SendToBattle();
                break;
            case GameState.BATTLE:
                break;
            default:
                break;
        }
    }
private void SendToBattle()
    {
        var currentScene = SceneManager.GetActiveScene();
        if (currentScene != SceneManager.GetSceneByName("Battle Scene"))
            return;
      
        BattleSystem battleSystem = GameObject.FindGameObjectWithTag("Battle System").GetComponent<BattleSystem>();
        Debug.Log("Executing");
}

It’s definitely not the best way to do it as SendToBattle will get called many times… But it’s the best solution I came up with. Found a lot of ‘answers’ including using OnEnable and OnDisable, which doesn’t work. This does.

I suspect you’ve used the OnCollisionEnter’s script’s StartCoroutine instead of the GameManager’s.

i.e. the difference between

void OnCollisionEnter(Collision collision)
{
    // This starts the coroutine on the current script
    StartCoroutine(GameManager.Instance.SendToBattle());
    // This starts the coroutine on the game manager
    GameManager.Instance.StartCoroutine(GameManager.Instance.SendToBattle());
}

In doesn’t matter which class the coroutine method is defined on, it only matters which MonoBehaviour’s StartCoroutine is called. If you start the coroutine on a script that is then unloaded during scene load, the coroutine will be stopped silently.

3 Likes

Thanks, I will try that out as well! I find it quite weird behaviour though, that apparently the program remembers where something started. I thought that as soon as it was fired it just keeps to itself. Thank you!