Unity多场景加载:如何让Level_01的Start()在激活场景设置后执行?
首先,咱们来拆解一下你遇到的问题根源:当你用LoadSceneAsync以Additive模式加载场景时,当AsyncOperation.isDone变为true的那一刻,Unity已经完成了场景的加载,会立即调用该场景内所有启用的MonoBehaviour的Start()方法——这一步发生在你调用SceneManager.UnloadSceneAsync和SceneManager.SetActiveScene之前,所以你的Level_01的Start()会在激活场景切换前就执行,导致后续的Update里的日志先打出StartScene,下一帧才切换到Level_01。
下面给你几个可行的解决方案,按推荐程度排序:
方案1:利用AsyncOperation.allowSceneActivation控制场景激活时机
这是最贴合Unity加载流程的方案,通过暂停场景的自动激活,先完成旧场景卸载和激活场景设置,再允许新场景激活,确保Start()在场景激活后执行。
修改你的SceneLoader脚本中的协程:
IEnumerator LoadSceneAsync(string sceneName) { Scene currentScene = SceneManager.GetActiveScene(); // 加载新场景,但禁止自动激活 AsyncOperation sceneLoadingOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); sceneLoadingOperation.allowSceneActivation = false; // 等待加载进度到0.9(allowSceneActivation为false时,进度最高到0.9) while (sceneLoadingOperation.progress < 0.9f) { yield return null; } // 先卸载当前场景,确保资源清理完成 yield return SceneManager.UnloadSceneAsync(currentScene); // 现在允许新场景激活 sceneLoadingOperation.allowSceneActivation = true; // 等待场景完全激活 while (!sceneLoadingOperation.isDone) { yield return null; } // 确保新场景被设为激活场景(这一步其实在allowSceneActivation设为true后会自动完成,但保险起见可以加上) SceneManager.SetActiveScene(SceneManager.GetSceneByName(sceneName)); }
这样调整后,Level_01的Start()会在场景被正式激活后才触发,完全符合你的预期。
方案2:监听场景激活事件替代Start()
如果不想修改加载流程,可以在Level_01的脚本里放弃依赖Start(),转而监听Unity的场景激活事件,确保初始化逻辑只在场景激活后执行。
修改Level_01的脚本:
private void OnEnable() { // 注册场景激活事件 SceneManager.sceneActivated += OnSceneActivated; } private void OnDisable() { // 取消注册,避免内存泄漏 SceneManager.sceneActivated -= OnSceneActivated; } private void OnSceneActivated(Scene activatedScene, LoadSceneMode loadMode) { // 只处理当前场景激活的情况 if (activatedScene.name == "Level_01") { Debug.Log("Level_01 start called"); // 这里放置原来Start()中的初始化逻辑 } }
这种方式的好处是不需要改动加载管理器的代码,适合场景较多、不想统一修改加载流程的项目。
方案3:手动触发初始化方法
另一种思路是完全绕过Start(),在加载流程完成、场景激活后,手动调用场景内脚本的初始化方法。
首先给Level_01的脚本添加一个公开的初始化方法:
public void Init() { Debug.Log("Level_01 start called"); // 原来Start()中的逻辑放到这里 }
然后修改SceneLoader的协程,在设置激活场景后手动调用:
IEnumerator LoadSceneAsync(string sceneName) { Scene currentScene = SceneManager.GetActiveScene(); AsyncOperation sceneLoadingOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); while(!sceneLoadingOperation.isDone) { yield return null; } yield return SceneManager.UnloadSceneAsync(currentScene); Scene newScene = SceneManager.GetSceneByName(sceneName); SceneManager.SetActiveScene(newScene); // 遍历新场景的所有根对象,找到需要初始化的脚本并调用方法 foreach (GameObject rootObj in newScene.GetRootGameObjects()) { Level_01_Script[] levelScripts = rootObj.GetComponentsInChildren<Level_01_Script>(true); foreach (Level_01_Script script in levelScripts) { script.Init(); } } }
这个方案适合需要精确控制初始化时机的场景,比如需要传递参数给场景内的脚本时。
内容的提问来源于stack exchange,提问作者Thrindil

