Files
XCEngine/docs/api/XCEngine/Components/GameObject/GameObject.md

255 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<TransformComponent>()` 返回已有实例,而不是再造一个
- `RemoveComponent()` 不允许移除 `Transform`
普通组件则保存在 `m_components` 里,所有权由 `std::unique_ptr<Component>` 持有。也正因为 `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<T>()` 只负责挂接,不会自动补发 `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>` 持有
- 普通组件由 `GameObject``std::unique_ptr<Component>` 持有
- `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)