- Home /
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:
SceneManager.activeSceneChanged
SceneManager.sceneLoaded
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?
Have you tried to register to those delegates to see when they trigger?
Answer by JacksonHLBC · Aug 04, 2021 at 12:33 AM
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
sceneLoaded -- The documentation is clear that sceneLoaded event triggers after Awake + OnEnable, and before Start.
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.
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 :( (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:
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.
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.
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).