7.6 KiB
Scene
命名空间: XCEngine::Components
类型: class
头文件: XCEngine/Scene/Scene.h
描述: 当前对象树的场景级容器,负责 GameObject 所有权、根节点入口、场景更新与完整树结构的保存/加载。
设计定位
商业级游戏引擎通常不会让“对象本身”同时承担对象集合管理、生命周期入口和场景存档边界。Scene 在这里承担的就是这层容器职责:
- 统一持有对象所有权
- 提供根对象遍历入口
- 驱动场景级
Update / FixedUpdate / LateUpdate - 作为整棵对象树的序列化和反序列化边界
这样拆分的好处是:GameObject 可以专注于对象自身与子树,Scene 则负责“这一整片运行时世界如何被管理和保存”。
当前真实职责
Scene 内部当前维护四组关键状态:
m_gameObjects- 以对象 ID 为键保存
std::unique_ptr<GameObject> - 是场景内对象的唯一所有权容器
- 以对象 ID 为键保存
m_rootGameObjects- 保存所有根对象的 ID
- 决定场景遍历和序列化的根入口顺序
m_gameObjectIDs- 保存当前场景内对象 ID 集合
- 用于快速判断成员关系
- 事件入口
这种“对象表 + 根列表”的拆法很典型,因为“按 ID 找对象”和“从根入口递归整棵树”是两种不同的使用需求。
创建与销毁路径
CreateGameObject
CreateGameObject 是当前标准运行时创建入口。它会完成:
- 分配并持有新的
GameObject - 把对象注册到全局
GameObject::GetGlobalRegistry() - 写入当前场景指针
- 挂到根节点列表或父对象之下
- 立刻调用
Awake() - 触发
OnGameObjectCreated
这条路径产生的对象最符合“真实运行时场景对象”的语义。
DestroyGameObject
- 先递归销毁子对象
- 再把对象从父节点子列表或根列表里移除
- 触发
OnGameObjectDestroyed - 调用对象的
OnDestroy() - 从场景容器和全局 registry 中删除
事件与 OnDestroy() 的顺序很重要:当前实现是先发场景销毁事件,再让对象给普通组件分发 OnDestroy()。
Destructor
Destructor 当前不会重放上述显式销毁链。它只会:
- 从全局 registry 擦除属于本场景的对象
- 清空内部
unique_ptr容器
所以 ~Scene() 不是“完整生命周期清理器”,而只是容器析构。
场景更新语义
Update、FixedUpdate、LateUpdate 当前都从根对象开始遍历。
按当前实现:
Update()会先调用每个激活根对象的Start(),再调用Update(deltaTime)FixedUpdate()与LateUpdate()也只会继续驱动IsActiveInHierarchy()为真的对象Scene::m_active当前不会作为这些接口的门控条件
因此更准确的理解方式是:
- 对象级是否真正参与运行,看
GameObject::IsActiveInHierarchy() Scene::IsActive()目前更像一个可保存的场景状态位,而不是运行时强门控
Tag / Layer 与场景查询
FindGameObjectWithTag 当前已经是真正的 tag 查询:
- 从根对象开始
- 对每个对象调用
CompareTag(tag) - 按深度优先顺序返回第一个匹配项
场景保存与恢复也已经覆盖 tag / layer:
- SerializeToString 会写出每个对象的
tag和layer - DeserializeFromString 会通过
SetTag()/SetLayer()恢复它们
这意味着场景级 tag 查询、原生 GameObject 字段,以及托管 GameObject.tag / layer 当前共享同一份底层状态。tests/Scripting/test_mono_script_runtime.cpp 中的 GameObjectTagAndLayerApiExposeUnityStylePropertiesAndCompareTag 就验证了脚本侧写回 tag 后,原生场景查询会立即看到结果。
序列化与反序列化模型
SerializeToString
SerializeToString 输出完整场景文本,包含:
- 场景名与场景 active 位
- 每个对象的
id、uuid、name、tag、active、layer、parent、transform - 每个普通组件的
component=<type>;payload
对象按“根列表顺序 + 子树深度优先顺序”输出。
DeserializeFromString
DeserializeFromString 采用两阶段重建:
- 先解析所有对象块与组件 payload
- 再创建全部对象、恢复字段、挂组件、连接父子关系
这条路径当前不会经过 CreateGameObject(),因此不会自动触发:
OnGameObjectCreatedAwake()Start()
这是当前场景创建和场景加载之间最重要的行为差异之一。
当前实现限制
m_active当前不会阻止场景更新- 场景析构不会为所有对象补发显式销毁链
- 反序列化不会触发创建事件,也不会补发
Awake()/Start() - 组件恢复依赖
ComponentFactoryRegistry - Save / Load 的错误处理目前比较轻,不提供事务式恢复
- SetName 不会同步更新
SceneManager里可能存在的管理 key
推荐使用方式
- 运行时对象创建优先使用 CreateGameObject。
- 需要当前场景内“第一个匹配对象”时,用 FindGameObjectWithTag;需要全局扫描时,再考虑
GameObject::FindGameObjectsWithTag()。 - 需要保存完整对象树时,使用 SerializeToString / DeserializeFromString。
- 从文本反序列化恢复后,如果某些系统依赖
Awake()侧初始化,要显式设计补发或重建流程,不要假设引擎已经自动补齐。
公开方法
| 方法 | 说明 |
|---|---|
| Constructor | 构造空场景。 |
| Destructor | 销毁场景容器。 |
| GetName | 查询场景名称。 |
| SetName | 设置场景名称。 |
| IsActive | 查询场景 active 标志。 |
| SetActive | 设置场景 active 标志。 |
| CreateGameObject | 创建场景对象并注册到场景。 |
| DestroyGameObject | 从当前场景树中销毁对象。 |
| Find | 按名称查找对象。 |
| FindByID | 按 ID 查找对象。 |
| FindGameObjectWithTag | 按真实 tag 深度优先查找第一个匹配对象。 |
| FindObjectOfType | 查找第一个匹配组件。 |
| FindObjectsOfType | 查找所有匹配组件。 |
| GetRootGameObjects | 获取根对象列表。 |
| Update | 驱动每帧更新。 |
| FixedUpdate | 驱动固定步长更新。 |
| LateUpdate | 驱动晚更新。 |
| Save | 保存到文件。 |
| Load | 从文件加载。 |
| SerializeToString | 序列化完整场景文本。 |
| DeserializeFromString | 从完整场景文本重建场景。 |
| OnGameObjectCreated | 访问对象创建事件。 |
| OnGameObjectDestroyed | 访问对象销毁事件。 |