# GameObject **命名空间**: `XCEngine::Components` **类型**: `class` **头文件**: `XCEngine/Components/GameObject.h` **描述**: 当前对象树模型的核心节点,负责身份、层级、激活状态、轻量 `tag/layer` 元数据、组件容器与生命周期分发。 ## 设计定位 `GameObject` 是当前引擎对象模型的中心类型。它的设计明显借鉴了 Unity 的 `GameObject + Transform + Component` 体系,但刻意保持为更轻的实现: - 用对象树而不是 ECS 组织运行时对象,便于编辑器层级、脚本调用和场景保存。 - 把 `Transform` 作为基础设施内建到每个对象里,避免“对象没有空间变换”的特例分支。 - 提供熟悉的 `tag` / `layer` 心智模型,但暂时不引入完整的项目级 TagManager、LayerMask 配置器或查询索引。 这种设计的好处是学习成本低、编辑器友好、序列化边界清晰;代价则是很多查询仍是线性扫描,生命周期也更依赖 `Scene -> GameObject -> Component` 的调用链。 ## 当前真实数据模型 `GameObject` 当前同时持有以下几类状态: - 身份字段:`m_id`、`m_uuid`、`m_name` - 元数据字段:`m_tag`、`m_layer` - 层级字段:`m_parent`、`m_children` - 激活字段:`m_activeSelf`、`m_started` - 所有权字段:`m_scene`、`m_transform`、`m_components` 其中最容易和旧文档混淆的是 `tag` / `layer`:它们现在已经是独立字段,不再是名字别名或“预留接口”。 ## Tag 与 Layer 语义 ### Tag - 底层字段是 `m_tag` - 默认值是 `"Untagged"` - [SetTag](SetTag.md) 传入空字符串时也会规范化回 `"Untagged"` - [CompareTag](CompareTag.md) 直接比较 `m_tag == tag` 因此当前引擎里: - `tag` 不是 `name` 的别名 - `CompareTag("Player")` 比较的是真实 tag 字段 - [FindGameObjectsWithTag](FindGameObjectsWithTag.md) 和 [Scene::FindGameObjectWithTag](../../Scene/Scene/FindGameObjectWithTag.md) 都是真正按 tag 查询 ### Layer - 底层字段是 `m_layer` - 默认值是 `0` - 原生 [SetLayer](SetLayer.md) 会把上界限制到 `31` - 托管 `GameObject.layer` / `Component.layer` 先把 `int` 显式限制到 `[0, 31]`,再回落到 native setter 这意味着在“引擎正常产生的数据路径”里,layer 会稳定落在 `0..31`。文档里常说的“clamp 到 `[0, 31]`”,对托管调用链是精确描述;对原生 C++ API 来说,更精确的说法是“参数本身是 `uint8_t`,setter 再额外做上界限制”。 ## 为什么 Tag / Layer 做成轻量字段 当前实现没有引入完整 TagManager 的原因并不是“尚未支持 tag”,而是有意把它们先收敛为轻量对象元数据: - 对编辑器和脚本层来说,独立字段已经足够支撑常见对象分类与查询。 - 对场景序列化来说,独立字段更容易稳定 round-trip。 - 对运行时复杂度来说,不必提前引入项目级表、重命名迁移、索引维护和配置资产。 因此它更接近“商业引擎里可持续扩展的第一阶段实现”,而不是临时占位符。 ## 内建 Transform 与组件模型 `GameObject` 构造时会立即创建一个 `TransformComponent` 并绑定到 `m_transform`。这带来三个明确语义: - 每个对象天然拥有 `Transform` - `AddComponent()` 返回已有实例,而不是再造一个 - `RemoveComponent()` 不允许移除 `Transform` 普通组件则保存在 `m_components` 里,所有权由 `std::unique_ptr` 持有。也正因为 `Transform` 不在 `m_components` 中,很多生命周期与序列化逻辑都只覆盖“普通组件”,不会自动覆盖内建 `Transform`。 ## 创建路径与对象注册 ### 直接构造 `GameObject go;` 或 `GameObject go("Name");` 会得到一个可用对象,但它: - 拥有内建 `Transform` - `m_scene == nullptr` - 不会自动注册到全局 registry - 不会自动触发 `Awake()` 这条路径适合测试、临时对象或完全手工控制生命周期的代码。 ### 由 `Scene::CreateGameObject()` 创建 这是当前推荐的运行时路径。`Scene` 会额外完成: - 接管对象所有权 - 把对象放入 `GameObject::GetGlobalRegistry()` - 设置所属 `Scene` - 接入根节点或父子层级 - 创建后立即调用 `Awake()` ### 由 `Scene::DeserializeFromString()` 恢复 反序列化也会重建对象并注册到场景和全局 registry,但它不会经过 `CreateGameObject()`,因此不会自动触发创建事件,也不会补发 `Awake()` / `Start()`。 ## 生命周期语义 `GameObject` 自己不是组件,但它是普通组件生命周期的分发者。 - [Awake](Awake.md) 遍历普通组件并调用 `Awake()` - [Start](Start.md) 只在 `active in hierarchy` 时执行,且每个对象最多执行一次 - [Update](Update.md)、[FixedUpdate](FixedUpdate.md)、[LateUpdate](LateUpdate.md) 只对激活层级中的已启用组件递归分发 - [OnDestroy](OnDestroy.md) 会把销毁消息发给普通组件 需要特别注意两条现实边界: - `TransformComponent` 不参与普通组件生命周期遍历 - 运行时 `AddComponent()` 只负责挂接,不会自动补发 `Awake()`、`Start()` 或 `OnEnable()` 所以如果对象已经进入过 `Start()`,你之后再动态添加组件,新组件不会被引擎自动“追上进度”。 ## 激活状态与层级传播 `GameObject` 维护两套不同层面的激活状态: - [IsActive](IsActive.md) 对应 `m_activeSelf` - [IsActiveInHierarchy](IsActiveInHierarchy.md) 要求对象自己激活,且所有父节点也激活 [SetActive](SetActive.md) 与 [SetParent](SetParent.md) 都会重新计算“层级有效激活态”。一旦有效激活态发生变化,当前实现会: - 对已启用的普通组件发送 `OnEnable()` 或 `OnDisable()` - 把变化递归传播给子对象 这和商业引擎里把“对象自己是否开启”和“它是否真的在运行层级里生效”拆开的做法一致。 ## 查找语义与全局 Registry 静态查找接口: - [Find](Find.md) 按对象名遍历全局 registry - [FindObjectsOfType](FindObjectsOfType.md) 返回当前 registry 全部对象 - [FindGameObjectsWithTag](FindGameObjectsWithTag.md) 通过 `CompareTag()` 过滤 registry 当前 registry 主要由两条路径填充: - `Scene::CreateGameObject()` - `Scene::DeserializeFromString()` 因此未加入场景的独立对象默认不会出现在这些查询结果中。另一方面,场景级 [Scene::FindGameObjectWithTag](../../Scene/Scene/FindGameObjectWithTag.md) 则是沿场景根对象做深度优先遍历,更适合表达“当前场景树里的第一个匹配对象”。 ## 所有权与销毁 - 场景托管对象由 `Scene` 以 `std::unique_ptr` 持有 - 普通组件由 `GameObject` 以 `std::unique_ptr` 持有 - `TransformComponent` 由 `m_transform` 单独持有并在析构时删除 [Destroy](Destroy.md) 当前有两种路径: - 如果对象属于某个场景,则委托给 `Scene::DestroyGameObject(this)` - 如果对象不属于场景,只调用 `OnDestroy()`,不会释放对象自身内存 因此“独立对象调用 `Destroy()`”不等于“对象被 delete”。真正释放内存的仍然是拥有它的那一层所有者。 ## 序列化边界 [Serialize](Serialize.md) 只负责单对象基础状态: - `name` - `tag` - `active` - `layer` - `id` - `uuid` - `transform` 它不会保存: - 普通组件列表 - 父子层级 - 所属场景 完整对象树持久化由 [Scene::SerializeToString](../../Scene/Scene/SerializeToString.md) 负责;对应地,[Deserialize](Deserialize.md) 也只恢复单对象基础字段,不会重建组件、父子关系或 `Scene` 归属。 ## 托管脚本桥接 Mono 运行时当前已经把这些元数据直接暴露给 C#: - `GameObject.Tag` / `GameObject.tag` - `GameObject.Layer` / `GameObject.layer` - `Component.Tag` / `Component.tag` - `Component.Layer` / `Component.layer` - `CompareTag(...)` 这些接口不是副本,而是直接读写同一份原生字段。`tests/scripting/test_mono_script_runtime.cpp` 中的 `GameObjectTagAndLayerApiExposeUnityStylePropertiesAndCompareTag` 明确验证了: - 托管脚本能读到原生已有的 tag / layer - 托管脚本写回 `"Player"` 和 `31` 后,原生对象会立即更新 - 场景级 `FindGameObjectWithTag("Player")` 会立刻看到更新结果 ## 当前实现限制 - tag / layer 仍是轻量字段,没有项目级定义表、mask 工具或索引查询 - 全局静态查找只对已注册对象成立 - 运行时新增组件不会自动补发初始化生命周期 - `Destroy()` 对独立对象不会释放自身内存 - `Transform` 不参与普通组件生命周期遍历 - `Scene::DeserializeFromString()` 恢复出的对象不会自动补发 `Awake()` ## 推荐使用方式 1. 运行时对象优先通过 `Scene::CreateGameObject()` 创建。 2. 需要对象分类时使用真实的 [SetTag](SetTag.md) / [CompareTag](CompareTag.md),不要再把 tag 当作名字别名。 3. 需要层过滤或脚本暴露时使用 [SetLayer](SetLayer.md),并按 `0..31` 的轻量层模型组织约定。 4. 需要完整持久化对象树时使用 [Scene::SerializeToString](../../Scene/Scene/SerializeToString.md) / [Scene::DeserializeFromString](../../Scene/Scene/DeserializeFromString.md)。 5. 运行时动态挂组件时,不要假设引擎会自动补发 `Awake()` / `Start()`。 ## 相关方法 - [Constructor](Constructor.md) - [GetName](GetName.md) - [SetName](SetName.md) - [GetTag](GetTag.md) - [SetTag](SetTag.md) - [CompareTag](CompareTag.md) - [GetLayer](GetLayer.md) - [SetLayer](SetLayer.md) - [AddComponent](AddComponent.md) - [GetComponent](GetComponent.md) - [GetComponents](GetComponents.md) - [GetComponentInChildren](GetComponentInChildren.md) - [GetComponentInParent](GetComponentInParent.md) - [GetComponentsInChildren](GetComponentsInChildren.md) - [GetTransform](GetTransform.md) - [SetParent](SetParent.md) - [DetachFromParent](DetachFromParent.md) - [DetachChildren](DetachChildren.md) - [SetActive](SetActive.md) - [IsActive](IsActive.md) - [IsActiveInHierarchy](IsActiveInHierarchy.md) - [Find](Find.md) - [FindObjectsOfType](FindObjectsOfType.md) - [FindGameObjectsWithTag](FindGameObjectsWithTag.md) - [Destroy](Destroy.md) - [Serialize](Serialize.md) - [Deserialize](Deserialize.md) ## 相关指南 - [GameObject-Component Lifecycle And Serialization](../../../_guides/Components/GameObject-Component-Lifecycle-And-Serialization.md) ## 相关文档 - [当前模块](../Components.md) - [Component](../Component/Component.md) - [TransformComponent](../TransformComponent/TransformComponent.md) - [Scene](../../Scene/Scene/Scene.md)