使用SceneManager.sceneLoaded时如何区分Additive与Single
解读
国内面试中,这道题表面问“事件参数”,实则考察候选人对Unity场景加载模式、事件订阅时机与运行时状态判断的综合理解。很多候选人只回答“看LoadSceneMode”,却忽略了事件注册时场景尚未完全激活、异步加载允许中途切换模式等真实项目坑点,导致回答被直接打断。面试官期待你给出可落地的运行时判别方案,并能主动提到Addressables或热更场景下的特殊处理,体现线上项目经验。
知识点
- SceneManager.sceneLoaded 原型:
public static event Action<Scene, LoadSceneMode> sceneLoaded;
第二个参数LoadSceneMode即本次加载所用的模式,Single会卸载其余场景,Additive则保留。 - 事件触发时机:新场景Awake之后、Start之前;此时主线程已能安全访问
Scene.isLoaded、Scene.path。 - 陷阱:
- 若在异步加载(
LoadSceneAsync)过程中取消(allowSceneActivation=false)再重新以另一模式加载,事件参数仍反映最后一次调用的模式,需自行缓存。 - DontDestroyOnLoad物体上的脚本也会收到事件,需过滤
scene.buildIndex为-1的情况。
- 若在异步加载(
- 线上项目常配合自定义场景包装器(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内部状态与业务预期不一致。
拓展思考
- Addressables.LoadSceneAsync返回的
AsyncOperationHandle<SceneInstance>同样带LoadSceneMode,但事件参数仍走SceneManager.sceneLoaded;此时建议统一封装SceneLoadService,在业务层屏蔽Unity原生事件,方便埋点统计与异常重试。 - 在大型MMO中,主城使用Additive加载分线副本,需保证光照贴图、NavMesh数据不冲突;可在
sceneLoaded里根据mode==Additive时调用Lightmapping.BakeMultipleScenes合并光照,并动态合并NavMeshSurface,实现无缝跨服。 - WebGL平台内存紧张,Single场景切换后必须立刻
Resources.UnloadUnusedAssets;而Additive卸载子场景时建议按场景名精确卸载对应AssetBundle,避免整包卸载导致Shader丢失的线上事故。