[Unity] 測試如何在場景切換過程安插工作 - Testing Unity SceneManager Events

最近與朋友討論了如何在 Unity 切換場景完的瞬間,立刻執行某些特定工作。嘗試了幾個方式後,發現基本上是不可能的。不過如果要在Awake 之後, Start 之前進行某些工作,則可以利用 Unity SceneManager 類別底下預設的幾個事件 (Event) 來達成。

不過 Unity 官方文件對於 SceneManager 預設事件的說明可以說是沒有,所以還是得自行測試過才能確定功能。

正好前陣子測試了 MonoBehaviour 時,在轉換場景的部分沒有測試完全,便趁這機會與 Unity SceneManager 一並做些測試,確定在切換場景的過程中所有流程 (Flow),未來依據不同需求便可以在不同階段安插希望執行的工作了。

本次測試用腳本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour {
private void Start() {
GameObject.DontDestroyOnLoad(this.gameObject);
SceneManager.sceneLoaded += SceneLoaded;
SceneManager.sceneUnloaded += SceneUnloaded;
SceneManager.activeSceneChanged += ActiveSceneChanged;
}
private void SceneLoaded (Scene scene, LoadSceneMode mode) {
Debug.Log("SceneLoaded");
}
private void SceneUnloaded(Scene scene) {
Debug.Log("SceneUnoaded");
}
private void ActiveSceneChanged(Scene sceneA, Scene sceneB) {
Debug.Log("ActiveSceneChanged");
}
public void ReloadScene() {
Debug.Log("=== ReloadScene Start ==="); StartCoroutine(StartLoadScene("main"));
}
private IEnumerator StartLoadScene(string sSceneName) {
AsyncOperation op = SceneManager.LoadSceneAsync(sSceneName, LoadSceneMode.Single);
while (!op.isDone) {
yield return null;
}
Debug.Log("=== ReloadScene End ===");
}
}

除了上方的主要腳本,另外場景中還準備了在上週直播時所使用的 TestUnit,用於確認 MonoBehaviour 的事件執行狀況,同時有一個按鈕用於切換動作。

完整的測試專案可以在我的 github 找到,連結如下:

測試結果

scenemanager

假設我們是從場景A切換至場景B,則可以紀錄整個流程為:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=== ReloadScene Start ===
# Coroutine 的起點
# 場景A 的 OnDisable
# 場景A 的 OnDistroy SceneUnoaded
# SceneManager 事件
# 場景B 的 Awake
# 場景B 的 OnEnable SceneLoaded
# SceneManager 事件 ActiveSceneChanged
# SceneManager 事件
# 場景B 的 Start
# 場景B 的 Update
=== ReloadScene End ===
# Coroutine 的終點
# 場景B 的 LateUpdate \[/code\]

從結果可以看出,SceneManager 各個事件的執行時機,以及 MonoBehaviour 的相關事件何時執行。

其中 SceneManager.activeSceneChanged 可能會在切換場景以外的功能觸發,所以建議場景切換時,只要使用 SceneManager.sceneLoaded 以及 SceneManager.sceneUnloaded 就好。

另外可以發現,因為 Coroutine 的運算時間是在 Update 之後,所以收尾的動作會在新場景的物件之 Start 之後執行,並不會在場景切換後立刻執行,這點於開發中相對重要。

後話

這次文章算是把直播所提出的 MonoBehaviour 議題做了一個收尾,也是我第一次使用 SceneManager.sceneLoaded 等事件。

透過這次測試,可以整理出一個簡易的場景切換工具,方便在各個時間點插入工作,用於精確進行新場景的初始化,以及 Loading 畫面的處理等,頗有收穫。