11 KiB
11 KiB
Scene
模块概述
Scene 模块负责:
- 主场景加载
- Additive 子场景加载
- 延迟激活与解除挂起
- 子场景卸载
- 主场景状态追踪
- 场景作用域(Scene Scope)建立与重置
它不是 Unity 默认 SceneManager 的简单封装,而是把场景切换与框架服务作用域绑定在一起。
这意味着:
- 加载主场景时,会重置 Scene Scope
- 加载 Additive 子场景时,不会重置主场景状态
- 主场景和子场景在框架内是两套不同语义
快速开始
最少步骤
- 在场景中挂载
SceneComponent - 确保
ResourceComponent已可用 - 通过
GameApp.Scene.LoadSceneAsync(...)加载场景 - 如需卸载 Additive 场景,调用
UnloadAsync(...)
最小示例
using AlicizaX;
using UnityEngine.SceneManagement;
public sealed class SceneQuickStart
{
public async void Load()
{
await GameApp.Scene.LoadSceneAsync("Scene/Battle", LoadSceneMode.Single);
}
}
架构说明
SceneComponent
└─ SceneService
└─ SceneDomainStateService
├─ CurrentMainSceneName
├─ CurrentMainSceneHandle
├─ SubScene Map
└─ Handling Scene Set
关键协作关系
SceneComponent:注册SceneService并确保 Scene Scope 存在SceneService:处理加载、激活、卸载与状态切换SceneDomainStateService:记录当前主场景、子场景和处理中场景IResourceService:主场景切换完成后触发资源回收
主场景与子场景的区别
主场景(Main Scene)
- 通过
LoadSceneMode.Single加载 - 加载前会重置 Scene Scope
- 加载完成后更新
CurrentMainSceneName - 切换完成后会触发一次资源回收
子场景(Sub Scene / Additive)
- 通过
LoadSceneMode.Additive加载 - 记录在
_subScenes字典中 - 可通过
UnloadAsync(location)卸载
核心类与接口
ISceneService
公开能力:
CurrentMainSceneNameLoadSceneAsync(...)LoadScene(...)ActivateScene(string location)UnSuspend(string location)IsMainScene(string location)UnloadAsync(string location, Action<float> progressCallBack = null)Unload(string location, Action callBack = null, Action<float> progressCallBack = null)IsContainScene(string location)
ISceneStateService
偏状态查询接口,主要用于:
- 查询当前主场景
- 查询某个场景是否已记录在当前 Scene Scope 中
SceneDomainStateService
当前实现中负责维护:
CurrentMainSceneNameCurrentMainSceneHandle_subScenes_handlingScenes
用途:
- 避免同一场景重复并发加载/卸载
- 为
IsContainScene/IsMainScene提供判断基础
API 参考
一、场景加载
UniTask<Scene> LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, uint priority = 100, bool gcCollect = true, Action<float> progressCallBack = null)
- 必填参数:
location - 可选参数:
sceneMode - 可选参数:
suspendLoad - 可选参数:
priority - 可选参数:
gcCollect - 可选参数:
progressCallBack - 返回值:
UniTask<UnityEngine.SceneManagement.Scene>
参数说明:
location:场景资源定位地址sceneMode:Single或AdditivesuspendLoad:是否挂起加载后的激活priority:加载优先级gcCollect:主场景切换后是否执行资源回收progressCallBack:加载进度回调
行为说明:
Single:会重置当前 Scene ScopeAdditive:会作为子场景注册- 如果同一场景正在处理,当前实现会记录错误并返回默认值
void LoadScene(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, uint priority = 100, Action<Scene> callBack = null, bool gcCollect = true, Action<float> progressCallBack = null)
- 注意:这个方法不是同步加载
- 本质上仍是异步加载,只是使用回调而不是
await
推荐:
- 新代码优先使用
LoadSceneAsync
二、场景激活与挂起
bool ActivateScene(string location)
- 必填参数:
location - 返回值:
bool
说明:
- 对应场景已被挂起时,尝试激活它
- 可用于
suspendLoad = true的场景
bool UnSuspend(string location)
- 必填参数:
location - 返回值:
bool
说明:
- 解除场景挂起
- 语义接近
ActivateScene
三、场景查询
bool IsMainScene(string location)
- 必填参数:
location - 返回值:
bool
说明:
- 判断给定场景是否为当前主场景
- 内部结合
SceneDomainStateService与SceneManager.GetActiveScene()做判断
bool IsContainScene(string location)
- 必填参数:
location - 返回值:
bool
说明:
- 判断当前主场景或子场景列表中是否包含该场景
string CurrentMainSceneName
- 返回值:主场景名
四、场景卸载
UniTask<bool> UnloadAsync(string location, Action<float> progressCallBack = null)
- 必填参数:
location - 可选参数:
progressCallBack - 返回值:
UniTask<bool>
说明:
- 用于卸载 Additive 子场景
- 当前实现不用于直接卸载主场景
void Unload(string location, Action callBack = null, Action<float> progressCallBack = null)
- 必填参数:
location - 可选参数:
callBack - 可选参数:
progressCallBack - 返回值:无
说明:
- 回调式异步卸载
常见用法
1. 加载主场景
using AlicizaX;
using UnityEngine.SceneManagement;
public sealed class LoadMainSceneExample
{
public async void GoBattle()
{
await GameApp.Scene.LoadSceneAsync("Scene/Battle", LoadSceneMode.Single);
}
}
2. Additive 加载子场景
using AlicizaX;
using UnityEngine.SceneManagement;
public sealed class AdditiveSceneExample
{
public async void OpenSubScene()
{
await GameApp.Scene.LoadSceneAsync("Scene/PhotoRoom", LoadSceneMode.Additive);
}
}
3. 卸载 Additive 子场景
using AlicizaX;
public sealed class UnloadSubSceneExample
{
public async void CloseSubScene()
{
if (GameApp.Scene.IsContainScene("Scene/PhotoRoom"))
{
bool ok = await GameApp.Scene.UnloadAsync("Scene/PhotoRoom");
UnityEngine.Debug.Log($"Unload result: {ok}");
}
}
}
4. 带进度的场景加载
using AlicizaX;
using UnityEngine;
using UnityEngine.SceneManagement;
public sealed class SceneProgressExample
{
public async void LoadWithProgress()
{
await GameApp.Scene.LoadSceneAsync(
"Scene/Battle",
LoadSceneMode.Single,
suspendLoad: false,
priority: 100,
gcCollect: true,
progressCallBack: progress =>
{
Debug.Log($"Scene progress: {progress:P0}");
});
}
}
5. 挂起加载后手动激活
using AlicizaX;
using UnityEngine.SceneManagement;
public sealed class SuspendLoadExample
{
public async void LoadThenActivate()
{
await GameApp.Scene.LoadSceneAsync(
"Scene/Battle",
LoadSceneMode.Single,
suspendLoad: true);
GameApp.Scene.ActivateScene("Scene/Battle");
}
}
6. 使用回调式加载
using AlicizaX;
using UnityEngine;
using UnityEngine.SceneManagement;
public sealed class SceneCallbackExample
{
public void LoadLobby()
{
GameApp.Scene.LoadScene(
"Scene/Lobby",
LoadSceneMode.Single,
suspendLoad: false,
priority: 100,
callBack: scene =>
{
Debug.Log($"Loaded scene: {scene.name}");
},
gcCollect: true,
progressCallBack: progress =>
{
Debug.Log($"Loading: {progress:P0}");
});
}
}
7. 查询当前主场景
using AlicizaX;
using UnityEngine;
public sealed class SceneStateExample : MonoBehaviour
{
private void Update()
{
Debug.Log($"Main Scene: {GameApp.Scene.CurrentMainSceneName}");
}
}
运行行为细节
1. 主场景加载会重置 Scene Scope
这是本模块最重要的设计点之一。
当调用:
GameApp.Scene.LoadSceneAsync("Scene/Battle", LoadSceneMode.Single)
内部会:
Context.ResetScene()- 重新注册
SceneDomainStateService - 标记新主场景进入加载中
这意味着:
- 旧的 Scene Scope 服务会被重建
- 与旧主场景强绑定的场景级服务也应重新初始化
2. Additive 场景不会重置主场景作用域
使用 LoadSceneMode.Additive 时:
- 场景会被加入
_subScenes - 主场景状态保留
- 适合加载摄影间、剧情副场景、临时房间等
3. UnloadAsync 只对 Additive 子场景有效
当前实现中:
- 只有
_subScenes中登记的场景才会走卸载逻辑 - 主场景切换依赖新的
LoadScene(Single),而不是单独Unload主场景
4. 同一场景并发处理会被拦截
SceneDomainStateService 会使用 _handlingScenes 记录“正在加载/卸载”的场景。
效果:
- 避免同一路径重复加载或重复卸载
- 减少状态错乱
5. 主场景加载完成后会触发资源回收
当主场景切换完成后,会调用:
Context.Require<IResourceService>().ForceUnloadUnusedAssets(gcCollect);
因此:
- 场景切换是资源回收的重要时间点
gcCollect参数会影响切场景后的回收强度
6. LoadScene 方法名容易误导
虽然名字像“同步加载”,但当前实现中:
LoadScene(...)仍然是异步加载- 区别只是它通过回调返回结果,而不是
await
最佳实践
- 主场景切换统一交给流程层管理
- Additive 场景只用于临时叠加内容,不要滥用为主流程状态切换
- 若需要加载转场动画,可用
suspendLoad = true+ 手动激活 - 场景切换后如有场景级服务初始化,放在新的 Scene Scope 生命周期里完成
常见错误
1. 试图用 UnloadAsync 卸载主场景
现象:
- 返回
false或警告
正确方式:
- 通过加载新的
Single主场景来替换
2. 把 LoadScene(...) 当同步函数使用
现象:
- 加载还没完成就执行后续依赖逻辑
规避:
- 优先使用
LoadSceneAsync(...) - 或把后续逻辑写入回调中
3. 重复 Additive 加载同一场景
现象:
- 异步版可能直接抛异常
- 回调版会记录警告
规避:
- 在加载前先用
IsContainScene(location)做检查
性能注意事项
- 场景切换本身是重量级操作,不要把短生命周期面板式内容做成 Additive 场景
- 进度回调每帧执行,UI 刷新时应尽量轻量
- 主场景切换后伴随资源回收,切场景阶段要预估回收开销和 GC 波动