new SceneManager (Unity 5.4.2f2 or newer) events. When do they fire in the event execution order?

Hi,

I have several DontDestroyOnLoad objects moving among scenes, and I’d like to use the new SceneManager events to hook on scene laoded/unloaded/changed.

Yet, also for future reference, I’d like to know:

When do:

  1. SceneManager.activeSceneChanged
  2. SceneManager.sceneLoaded
  3. SceneManager.sceneUnloaded

happen in this (outdated, since it’s still using the now obsolete OnLevelWasLoaded) chart?

https://docs.unity3d.com/Manual/ExecutionOrder.html

Do the 2 and 3 get fired also when simply switching the active scene?

I know this is an old post, but I was interested and thought I’d share my investigation since this appeared high up in the google search algorithm.


TLDR

  • For Simple Scene Loading Mode:
    The events sceneLoaded and activeSceneChanged occur between Awake/OnEnable and Start, and activeSceneChanged comes before sceneLoaded.
  • For Additive Scene Loading Mode:
    sceneLoaded occurs between Awake/OnEnable and Start, and activeSceneChanged occurs later once SetActiveScene is called (since the Scene must be loaded before this is allowed).
  • For Unloading:
    sceneUnloaded occurs after OnDisable and OnDestroy, but before the next Scene has been loaded (See end of this post for consequences of the event occurring after disable/destroy). It is not called when the game (or play mode) is closed/last Scene unloaded.

What Unity says in the documentation

  1. sceneLoaded – The documentation is clear that sceneLoaded event triggers after Awake + OnEnable, and before Start.
  2. sceneUnloaded – This documentation is for the most part self-explanatory, the scene won’t unload until it has loaded, so it’s safe to assume it comes after Awake, OnEnable, Start, etc. It would also make sense for sceneUnloaded to be called after all disable/destroy calls are complete, though this isn’t clear.
  3. activeSceneChanged – This documentation is the least clear in my opinion as it adds the event on Start and the ChangeScene method calls SetActiveScene explicitly right after LoadScene. This implies that the activeSceneChanged event is something to trigger during the process of changing scenes. Additionally, the SetActiveScene call does not make sense here as it is meant for switching active status of two additive scenes (LoadScene by default loads a Simple Scene) and would fail either way since the scene hasn’t actually loaded yet.

What I tested locally

Note for these tests I ran on Unity 2019.4.21f1:
For my tests, I had buttons to switch between two identical scenes. Each Scene had a button to switch instantly (LoadScene(scene2Name)) and a button to switch gradually like a loading screen (LoadScene(scene2Name, LoadSceneMode.Additive) + SetActiveScene(scene2) + UnloadScene(scene1)). Additionally I had one script in each scene that printed debug lines for each of the following callbacks:
Awake, OnEnable, Start, OnDisable, OnDestroy, sceneLoaded, activeSceneChanged, sceneUnloaded

TLDR section has my results from this test.


Additional notes about sceneUnloaded

Because sceneUnloaded is triggered after OnDisable/OnDestroy this can disrupt how one typically subscribes to events. If you’re like me, your typical strategy looks like the following:

using UnityEngine;

public class MyScript : MonoBehaviour
{
    void OnEnable()
    {
        someEvent += OnEventTriggered;
    }

    void OnDisable()
    {
        // Allows one to stop the callback from happening by simply disabling the component.
        someEvent -= OnEventTriggered;
    }

    void OnEventTriggered()
    {
        // do stuff
    }
}

Unfortunately, because sceneUnloaded occurs after OnDisable (and similarly OnDestroy), this will unsubscribe before the event triggers and miss the event call. And if you never unsubscribe from the event, then the method will still get callbacks when the next scene is unloaded. Additionally, if you try to use resources local to the component, most will cause NullPointerExceptions since you’re accessing data from a destroyed gameobject :frowning: (I didn’t check the extent to which resources are inaccessible after OnDestroy, but it is safer to assume none of them are).


A couple ideas to work around this:

  1. Use a ScriptableObject instead of a MonoBehaviour: ScriptableObjects are scene agnostic and can be set to not unload when unused (omni-present callbacks). If you’re dealing with data that isn’t tied to a specific gameobject, then it’s likely better suited for a more global entity to manage.
  2. Interact with the sceneUnloaded event with additive scenes: This problem only exists if the object that is subscribed is the one that is unloaded. Instead you could write the logic into a separate scene that is loaded additively. This way the component will be around when the previous scene is unloaded.
  3. Unsubscribe as part of the callback itself: I feel this is best explained via code.

using UnityEngine;
using UnityEngine.SceneManagement;

public class MyScript : MonoBehaviour
{
    void Awake()
    {
        // do stuff
        SceneManager.sceneUnloaded += OnSceneUnloaded;
    }

    void OnSceneUnloaded(Scene scene)
    {
        // do stuff
        if (this == null)
            SceneManager.sceneUnloaded -= OnSceneUnloaded;
    }
}

Note: I use “this == null” so that this code is compatible with unloading additive scenes (don’t subscribe unless you are being unloaded).