外观
Stage02|Engine Loop
Lesson014|Scene 是什么?加载和切换场景时发生了什么?
Tags: #Creator2.x #Stage02 #Scene #loadScene #LifecycleDifficulty: ⭐⭐⭐☆☆
一、本课目标
本课解决三个问题:
- Scene 和 Node 的关系是什么?
loadScene期间引擎大概做了什么?- 为什么切场景会触发大量生命周期、资源和内存变化?
完成本课后,你应该能够区分场景文件、运行时 Scene 对象和场景节点树,并能设计基本的场景切换流程。
二、Scene 不是一个普通的页面文件
编辑器中的 Scene 文件是序列化数据,运行时的 Scene 是引擎管理的对象:
text
Scene 文件
↓ 反序列化
运行时 Scene
↓
根节点和子节点树
↓
Node / ComponentScene 主要承载:
- 场景节点层级。
- 节点和组件的序列化属性。
- 当前场景的运行边界。
- Canvas、Camera、UI 和游戏对象。
Scene 不等于页面 DOM,也不等于一个独立线程。它最终仍然由 Director 推进。
三、当前 Scene 和 Scene 文件的区别
| 概念 | 含义 |
|---|---|
| Scene 资源文件 | 编辑器保存的序列化描述 |
| 运行时 Scene | 引擎反序列化后管理的对象 |
| Scene 根节点 | 节点树的顶层结构 |
| 常驻节点 | 跨场景保留的运行时对象 |
| 场景资源依赖 | Scene 使用的 Prefab、图片、脚本等 |
修改编辑器中的节点属性,最终会影响下次反序列化得到的运行时对象;运行时修改 Node 属性,通常不会自动写回 Scene 文件。
四、loadScene 的高层流程
ts
cc.director.loadScene('Battle');可以先按以下过程理解:
text
提出切换请求
↓
解析目标 Scene 资源
↓
加载 Scene 及依赖资源
↓
准备新 Scene 的序列化数据
↓
清理旧 Scene 中不再保留的对象
↓
创建新 Scene、Node 和 Component
↓
恢复常驻对象关系
↓
执行新场景生命周期
↓
设置为 Director 当前 Scene
↓
进入后续 mainLoop不同 Creator 2.x 版本可能在加载、清理和事件细节上有所不同,但“请求—加载—创建—激活—接管主循环”的骨架是稳定的。
五、场景加载为什么会卡
场景切换可能同时产生多个成本:
text
文件读取
资源解码
依赖加载
序列化解析
Node 创建
Component 创建
生命周期执行
纹理上传
渲染数据重建
旧对象清理所以“场景切换卡顿”不一定只是 loadScene API 本身慢,可能是:
- Scene 依赖图片过多。
- 大纹理首次解码或上传。
- 节点数量过多。
onLoad中做了大量同步工作。start中批量生成对象。- 旧场景监听器或资源没有释放。
排查时要把加载、创建、生命周期和首次渲染拆开测量。
六、场景生命周期和节点生命周期
新场景进入运行状态后,节点组件通常会经历:
text
创建对象
↓
onLoad
↓
onEnable
↓
start
↓
update / lateUpdate旧场景离开时,可能涉及:
text
失去激活状态
↓
onDisable
↓
销毁或等待引用释放
↓
onDestroy具体是否销毁,要看对象是否被常驻、外部是否仍有引用以及资源管理策略。
不要把 onLoad 当作“每次打开页面都必定执行”。如果对象没有被销毁而只是 active=false,再次启用通常不会重新走 onLoad。
七、常驻节点为什么存在
登录信息、全局音频、网络管理器等对象可能需要跨场景保存:
text
Login Scene
↓ 切换
Main Scene
↓
Battle Scene如果每次切场景都销毁网络连接和用户状态,会带来:
- 重复初始化。
- 状态丢失。
- 连接反复建立。
- 事件重复绑定。
常驻节点适合承载跨场景基础服务,但不代表所有对象都应该常驻。常驻对象过多会造成:
- 内存持续增长。
- 生命周期边界模糊。
- 场景之间隐藏依赖。
- 清理和测试困难。
原则是:只有真正跨场景共享且生命周期明确的对象才考虑常驻。
八、场景切换中的引用问题
旧场景对象可能被这些地方继续引用:
text
全局单例
事件总线
定时器
闭包回调
网络回调
Promise 回调
对象池即使 Scene 不再显示,这些引用也可能阻止对象和相关资源被回收。切场景时应检查:
text
onEnable 注册的事件是否在 onDisable 取消
schedule 是否在销毁前停止
异步回调是否检查对象有效性
全局管理器是否保留旧节点引用这就是“画面已经换了,但内存没有下降”的常见原因之一。
九、场景切换的工程写法
ts
loadBattle() {
this.loadingNode.active = true;
cc.director.loadScene('Battle', (error) => {
if (error) {
cc.error('load Battle failed', error);
return;
}
cc.log('Battle is ready');
});
}真实项目还应根据版本和平台补充:
- 加载进度。
- 重复点击保护。
- 失败重试或回退。
- 旧请求取消或过期检查。
- 加载界面本身的常驻策略。
不要让多个按钮同时发起不可控的场景切换请求。
十、常见误区
误区一:Scene 文件就是运行时 Scene
文件是序列化描述,运行时 Scene 是反序列化后的对象。
误区二:切场景只会替换一个根节点
它还涉及资源、对象创建、生命周期、常驻对象和渲染状态。
误区三:切场景后旧场景所有东西立即释放
外部引用、事件、定时器和资源缓存都可能让对象继续存活。
误区四:所有全局系统都应该做成常驻节点
常驻是生命周期设计,不是方便取引用的快捷方式。
误区五:在 onLoad 里做所有初始化最安全
大量同步工作会拉长场景进入时间。应区分基础初始化、依赖其他对象的初始化和可延迟工作。
十一、练习与答案
练习
- Scene 资源文件和运行时 Scene 有什么区别?
- 为什么切场景可能同时带来 IO、CPU、GPU 和内存成本?
- 什么对象适合做常驻节点?
- 场景已经切换,但旧场景内存没有下降,应检查哪些引用?
- 为什么
loadScene的回调比调用下一行更适合判断加载完成?
参考答案
- 资源文件是序列化描述,运行时 Scene 是引擎创建并管理的对象。
- 切换会读取和解码资源、反序列化、创建节点、执行生命周期、上传纹理并清理旧对象。
- 真正跨场景共享且生命周期明确的服务,如网络、音频或用户会话管理器。
- 检查全局引用、事件、定时器、闭包、异步回调和对象池。
- 场景切换通常包含异步加载和对象创建,调用下一行不代表新场景已完成初始化。
十二、Creator 2.4.x 可复现实验:切场景时间线
实验代码
在 A、B 两个场景根节点挂载同一个探针脚本:
ts
const { ccclass } = cc._decorator;
@ccclass
export default class SceneProbe extends cc.Component {
onLoad() { cc.log('[scene]', cc.director.getScene().name, 'onLoad'); }
onEnable() { cc.log('[scene]', cc.director.getScene().name, 'onEnable'); }
start() { cc.log('[scene]', cc.director.getScene().name, 'start'); }
onDestroy() { cc.log('[scene]', this.node.name, 'onDestroy'); }
}在 A 中调用:
ts
const t0 = Date.now();
cc.director.loadScene('SceneB', () => {
cc.log('[scene-switch-done]', Date.now() - t0, 'ms', cc.director.getScene().name);
});观察与诊断
预期能看到 A 的清理日志、B 的创建/激活日志以及最终回调。若切换卡顿,分别在请求前、加载回调、B 首个 start 和首帧 update 打时间戳;这样可以区分 IO、反序列化、生命周期和首次渲染成本。若旧场景内存不降,优先查常驻节点、全局事件、定时器、闭包、对象池和静态缓存。
十三、版本边界
Creator 2.4.x 中 cc.director.loadScene 的异步回调是判断切换完成的主要公开入口。场景资源的具体缓存、释放时机、加载事件和内部过渡实现可能随 2.4.x 小版本或平台改变;课程不依赖这些内部顺序。不要直接修改 .fire/.scene 文件来做实验,使用编辑器创建和 Build Settings 配置场景。
十四、应用题与推导答案
题目: B 场景已经显示,但内存比切换前高很多,如何判断是正常峰值还是泄漏?
答案: 先记录 A 常驻资源、B 加载峰值和清理后稳定值,连续往返 A/B 三次。若每次稳定值都接近同一基线,通常是加载峰值或缓存;若基线单调增长,再用引用链定位全局变量、事件监听、异步回调和对象池。destroy 节点只结束对象生命周期,不等于所有资源引用立即释放。
十五、本课总结
text
Scene 文件是序列化数据。
运行时 Scene 是当前节点树的容器。
Director 负责切换并推进当前 Scene。
场景切换同时影响资源、对象、生命周期和内存。
常驻节点是生命周期选择,不是万能单例。十六、下一课预告
text
Lesson015|Scheduler 和 ComponentScheduler 分别负责什么?下一课会把“每帧任务为什么能在正确时间执行”拆成 Scheduler 和 ComponentScheduler 两条线。
