# Scene Lifecycle And Serialization ## 为什么场景系统通常这样分层 商业级游戏引擎很少把“场景”“对象”“组件”揉成一个类。更常见的拆法是: - 场景负责对象集合、根节点入口和存档边界。 - 对象负责层级关系与生命周期转发。 - 组件负责真正的可组合行为。 `XCEngine` 当前的 `Scene` / `GameObject` / `Component` 关系基本就是这个思路,所以它看上去会比较像 Unity 风格的设计,而不是传统单一 Entity 容器。 ## `Scene` 真正负责什么 当前 `Scene` 最核心的职责有三件事: 1. 持有 `GameObject` 的唯一所有权。 2. 从根对象入口驱动 `Update` / `FixedUpdate` / `LateUpdate`。 3. 作为保存和加载的边界,把一整棵对象树序列化出去或重新建回来。 这样做的好处是,场景可以被理解成“一个可整体保存、整体切换的运行时世界片段”。 ## 为什么要有根对象列表 场景里既有对象树,又单独保存 `m_rootGameObjects`,这不是重复设计,而是为了让两件事都变简单: - 查“从哪里开始遍历场景”时,不用扫描全体对象找没有父节点的项。 - 对象挂接和脱离父节点时,只要同步更新根列表即可。 这是很多商用引擎场景树都会采用的做法,因为“根入口”和“局部子树”是两种不同维度的查询。 ## 当前 active 状态有两层,但还没有完全打通 理解这块很重要: - `SceneManager::GetActiveScene()` 是“哪一个场景被管理器认为是当前活动场景”。 - `Scene::IsActive()` 是 `Scene` 自己保存的一个布尔标志。 - `GameObject::IsActiveInHierarchy()` 才是对象更新真正参与的门槛。 在当前实现里,这三者并没有完全联动: - `Scene::SetActive(false)` 不会阻止 `Scene::Update()`。 - `SceneManager::SetActiveScene()` 也不会自动改 `Scene::IsActive()`。 这意味着现在更稳妥的理解方式是: - `SceneManager` 的 active scene 更接近“当前选中的场景引用”。 - `Scene::m_active` 更接近“一个可保存的场景状态位”。 ## 序列化为什么用自定义文本格式 当前 `Scene::SerializeToString()` 没有走 JSON,而是用了非常直接的文本块格式。这样做的现实好处是: - 实现简单。 - 组件可以继续用自己的 `Serialize(std::ostream&)` 输出。 - 调试时直接打开文本就能看见对象、父子关系和组件条目。 但代价也很明确: - 格式是引擎私有的,没有版本协商。 - 解析鲁棒性有限。 - 组件恢复依赖 `ComponentFactoryRegistry`,组件名或注册表变化都会影响加载结果。 ## 当前最需要小心的几个事实 - 场景析构不会自动为所有对象走显式销毁流程,所以不要把 `~Scene()` 当成完整生命周期清理器。 - `FindGameObjectWithTag()` 现在其实是在按名字查。 - `LoadSceneAsync()` 现在不是异步,更多只是一个名字上预留好的 API。 - `LoadScene()` 后的管理器 key 来自文件名,而不是场景内部保存的 `scene=` 名称。 ## 实际使用建议 如果你现在基于这套系统开发,比较稳妥的做法通常是: - 需要 `OnDestroy` 或场景事件时,显式调用对象销毁入口,不要只依赖场景析构。 - 需要稳定查场景时,优先用 `CreateScene()` 的名称或加载文件的文件名 stem,不要假设一定等于场景内部名字。 - 不要把 `LoadSceneAsync()` 当成真正的后台加载接口。 - 不要把 `FindGameObjectWithTag()` 当成完整 tag 系统。 ## 相关 API - [Scene Module](../../XCEngine/Scene/Scene.md) - [Scene](../../XCEngine/Scene/Scene/Scene.md) - [SceneManager](../../XCEngine/Scene/SceneManager/SceneManager.md) - [GameObject](../../XCEngine/Components/GameObject/GameObject.md)