Unity MyFramework 用法说明(二十八):使用 SceneSystem 管理 Unity 场景资源
2026/8/5 19:16:24 网站建设 项目流程

前面介绍的GameSceneSceneProcedure负责管理游戏的业务流程,但它们并不直接加载 Unity 的.unity场景文件。

真正负责 Unity 场景注册、异步加载、显示隐藏和资源卸载的是SceneSystem

项目地址:

https://github.com/ZHOURUIH/MyFramework

一、GameScene 和 SceneSystem 的区别

这两个系统的名字相似,但职责完全不同。

GameScene 管理游戏业务流程 SceneProcedure 管理一个业务流程中的不同阶段 SceneSystem 管理真正的 Unity Scene 资源 SceneInstance 封装一个已经加载的 Unity Scene

例如进入战斗时,业务流程可能是:

MainSceneLoading ↓ MainSceneGaming ↓ MainSceneExit

这是GameScene管理的内容。

而下面这些操作属于SceneSystem

加载 Battle.unity 显示或隐藏 Battle_Root 将 Battle 设置为 Active Scene 卸载 Battle.unity 释放场景目录中的资源

二者通常配合使用:

SceneProcedure 决定什么时候进入战斗 ↓ SceneSystem 负责加载战斗场景资源

二、创建 SceneInstance

每个需要由SceneSystem管理的 Unity 场景,都可以创建一个对应的SceneInstance

例如战斗场景:

public class BattleSceneInstance : SceneInstance { public override void init() { base.init(); // 获取场景中的固定节点 // 初始化场景逻辑 } public override void onShow() { // 场景被显示 } public override void onHide() { // 场景被隐藏 } public override void update(float elapsedTime) { // 只有场景处于显示状态时才会更新 } public override void lateUpdate(float elapsedTime) { } public override void destroy() { // 清理场景相关引用 base.destroy(); } }

SceneInstance主要保存:

Unity Scene 对象 场景根节点 场景名称 场景逻辑类型 加载状态 加载进度回调 加载完成回调 是否显示 是否设置为主场景

只有处于显示状态的场景,才会执行update()lateUpdate()

三、场景必须有固定根节点

SceneSystem加载完成后,会查找:

场景名_Root

假设场景文件名为:

Battle.unity

场景层级应该是:

Battle └── Battle_Root ├── Environment ├── Light ├── MonsterRoot ├── EffectRoot └── Other

框架通过Battle_Root控制整个场景的显示和隐藏。

内部加载完成后会执行:

scene.setRoot( findRootGameObject( scene.getName() + "_Root", true));

因此根节点名称必须正确,并且最好保证场景中的所有内容都放在这个根节点下。

调用:

mSceneSystem.hideScene("Battle");

本质上不会卸载场景,而是关闭:

Battle_Root

四、注册场景

场景使用前需要注册:

using static FrameBaseHotFix; public static class SceneRegister { public static BattleSceneInstance mBattleScene; public static void registeAll() { mSceneSystem.registeScene( typeof(BattleSceneInstance), "Scene/Battle/Battle.unity", (instance) => { mBattleScene = instance as BattleSceneInstance; }); } }

然后在GameHotFix.registerAll()中调用:

protected override void registerAll() { LayoutRegisterHotFix.registeAll(); SceneRegister.registeAll(); }

注册信息包括:

SceneInstance 类型 场景文件路径 场景实例变化回调

路径相对于:

Assets/GameResources

并且必须以.unity结尾:

Scene/Battle/Battle.unity

场景名称会自动从文件名中获得:

Battle.unity ↓ Battle

回调会在场景实例创建时传入对应的SceneInstance,卸载时传入null,因此可以用来保存和清空场景引用。

这个回调不表示 Unity 场景已经加载完成。真正的加载完成逻辑,应使用loadSceneAsync()的完成回调。

五、异步加载场景

SceneSystem只提供异步场景加载:

protected CustomAsyncOperation mLoadOperation; protected void loadBattleScene() { mLoadOperation = mSceneSystem.loadSceneAsync( "Battle", true, true, onBattleSceneLoaded, onBattleSceneLoading); } protected void onBattleSceneLoading(float progress) { mUILoading?.setLoadPercent(progress); } protected void onBattleSceneLoaded() { log("战斗场景加载完成"); }

参数含义为:

Battle 注册时生成的场景名称 true 加载完成后立即显示 true 加载完成后设置为 Unity Active Scene onBattleSceneLoaded 加载完成回调 onBattleSceneLoading 加载进度回调

返回的CustomAsyncOperation可以继续加入:

Waiting AsyncTaskGroup CustomMultiAsyncOperation

例如同时等待场景、角色和服务器数据:

Waiting waiting = mWaitingManager.createWaiting( onPrepareFinished); waiting.addAsyncOperation( mSceneSystem.loadSceneAsync( "Battle", true, true, null)); waiting.addCondition( () => mPlayerCreated); waiting.addCondition( () => mServerDataReady);

六、内部为什么统一使用 Additive

框架加载场景时固定使用:

SceneManager.LoadSceneAsync( scene.getName(), LoadSceneMode.Additive);

也就是所有场景都以叠加模式加载。

这样SceneSystem才能同时管理多个场景:

基础场景 战斗场景 角色展示场景 家园场景 预览场景

加载新场景时,不会由 Unity 自动销毁之前的场景,而是由框架决定:

哪些场景继续显示 哪些场景暂时隐藏 哪些场景真正卸载

SceneManager.LoadScene()虽然名字看起来是同步加载,但函数返回时场景不一定已经完全可用,因此框架不再提供表面上的同步接口。

七、场景加载的内部流程

loadSceneAsync()的主要流程是:

根据场景名查找注册信息 ↓ 创建对应的 SceneInstance ↓ 通过 ResourceManager 预加载场景 AssetBundle ↓ 使用 Additive 异步加载 Unity Scene ↓ 持续发送加载进度 ↓ 获取 Unity Scene 对象 ↓ 查找 场景名_Root ↓ 执行 SceneInstance.init() ↓ 根据参数显示或隐藏场景 ↓ 执行加载完成回调

核心逻辑可以简化为:

AsyncOperation operation = SceneManager.LoadSceneAsync( scene.getName(), LoadSceneMode.Additive); scene.setScene( SceneManager.GetSceneByName( scene.getName())); scene.setRoot( findRootGameObject( scene.getName() + "_Root", true)); scene.init();

SceneInstance.init()中还会扫描场景节点使用的 Shader,保证动态加载后的材质能够正确找到对应 Shader。

八、显示和隐藏场景

隐藏场景:

mSceneSystem.hideScene("Battle");

隐藏只会关闭根节点,并调用:

BattleSceneInstance.onHide();

场景仍然保留在内存中,之后可以快速重新显示。

显示场景:

mSceneSystem.showScene("Battle");

默认参数为:

showScene( "Battle", true, true);

表示:

隐藏其他已加载场景 显示 Battle 将 Battle 设置为主场景

需要同时显示多个场景时:

mSceneSystem.showScene( "CharacterPreview", false, false);

表示不隐藏其他场景,也不修改当前主场景。

单独设置主场景:

mSceneSystem.setMainScene( "Battle");

它会调用 Unity 的:

SceneManager.SetActiveScene(...);

九、隐藏和卸载不是一回事

隐藏场景:

mSceneSystem.hideScene("Battle");

结果是:

Scene 仍然已加载 资源仍然保留 Battle_Root 被关闭 停止 SceneInstance 更新 可以快速再次显示

卸载场景:

mSceneSystem.unloadScene("Battle");

结果是:

通知场景引用回调传入 null 销毁 SceneInstance 调用 SceneManager.UnloadSceneAsync 从已加载场景列表中移除 卸载场景目录中的资源

因此,短时间后还会重新使用的场景可以先隐藏。

确定离开并且短时间内不会再回来时,再真正卸载。

十、场景目录与资源卸载

unloadScene()默认参数为:

mSceneSystem.unloadScene( "Battle", true);

第二个参数表示是否卸载场景所在目录中的资源。

框架会调用:

mResourceManager.unloadPath( getFilePath(scenePath));

因此推荐按场景组织独占资源:

GameResources └── Scene └── Battle ├── Battle.unity ├── Model ├── Material ├── Texture └── Effect

这个目录中最好只放战斗场景独占资源。

多个场景共享的资源应放到公共目录,避免场景卸载时把资源目录一起清理。

不希望卸载目录资源时:

mSceneSystem.unloadScene( "Battle", false);

卸载除指定场景以外的其他场景:

mSceneSystem.unloadOtherScene( "Battle");

适合在进入新区域后清理旧场景。

十一、总结

MyFramework 中与场景相关的职责分为两层:

GameScene + SceneProcedure 管理游戏业务流程 SceneSystem + SceneInstance 管理 Unity 场景资源

SceneSystem的基本使用流程是:

1. 创建 SceneInstance 子类 2. 为 Unity 场景添加 场景名_Root 3. 使用 registeScene 注册场景 4. 使用 loadSceneAsync 异步加载 5. 使用 showScene 和 hideScene 控制显示 6. 使用 setMainScene 设置 Active Scene 7. 不再使用时调用 unloadScene

所有场景统一使用 Additive 模式加载,再由框架控制显示、更新和卸载。

这样业务流程只需要决定什么时候进入或离开场景,不需要在各个模块中重复处理 Unity Scene、根节点、加载进度和场景资源释放。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询