使用SceneManager.sceneLoaded时如何区分Additive与Single

解读

国内面试中,这道题表面问“事件参数”,实则考察候选人对Unity场景加载模式事件订阅时机运行时状态判断的综合理解。很多候选人只回答“看LoadSceneMode”,却忽略了事件注册时场景尚未完全激活异步加载允许中途切换模式等真实项目坑点,导致回答被直接打断。面试官期待你给出可落地的运行时判别方案,并能主动提到Addressables或热更场景下的特殊处理,体现线上项目经验

知识点

  1. SceneManager.sceneLoaded 原型:
    public static event Action<Scene, LoadSceneMode> sceneLoaded;
    第二个参数LoadSceneMode即本次加载所用的模式,Single会卸载其余场景,Additive则保留。
  2. 事件触发时机:新场景Awake之后、Start之前;此时主线程已能安全访问Scene.isLoadedScene.path
  3. 陷阱
    • 若在异步加载LoadSceneAsync)过程中取消(allowSceneActivation=false)再重新以另一模式加载,事件参数仍反映最后一次调用的模式,需自行缓存。
    • DontDestroyOnLoad物体上的脚本也会收到事件,需过滤scene.buildIndex为-1的情况。
  4. 线上项目常配合自定义场景包装器(SceneWrapper)在事件里记录mode,供热更脚本Lua层快速判断,避免二次GC。

答案

sceneLoaded回调里直接读取第二个参数即可:

SceneManager.sceneLoaded += (scene, mode) =>
{
    if (mode == LoadSceneMode.Single)
    {
        // 单场景:清理旧UI、释放旧AssetBundle
        UIManager.Instance.ClearAllCanvas();
        AssetManager.Instance.UnloadUnusedBundle();
    }
    else
    {
        // 叠加场景:仅注册新UI层级,不清理全局对象
        UIManager.Instance.RegisterCanvas(scene);
    }
};

若项目使用异步中途可能切换模式的加载策略,需在调用LoadSceneAsync自行缓存LoadSceneMode到自定义LoadingContext,事件触发时优先使用缓存值,避免Unity内部状态与业务预期不一致。

拓展思考

  1. Addressables.LoadSceneAsync返回的AsyncOperationHandle<SceneInstance>同样带LoadSceneMode,但事件参数仍走SceneManager.sceneLoaded;此时建议统一封装SceneLoadService,在业务层屏蔽Unity原生事件,方便埋点统计异常重试
  2. 大型MMO中,主城使用Additive加载分线副本,需保证光照贴图、NavMesh数据不冲突;可在sceneLoaded里根据mode==Additive时调用Lightmapping.BakeMultipleScenes合并光照,并动态合并NavMeshSurface,实现无缝跨服。
  3. WebGL平台内存紧张,Single场景切换后必须立刻Resources.UnloadUnusedAssets;而Additive卸载子场景时建议按场景名精确卸载对应AssetBundle,避免整包卸载导致Shader丢失的线上事故。