# GameObject **命名空间**: `XCEngine::Components` **类型**: `class` **头文件**: `XCEngine/Components/GameObject.h` **描述**: 场景对象树的基础节点,负责对象身份、父子层级、激活状态、组件容器和生命周期分发。 ## 角色概述 `GameObject` 是当前引擎对象模型的中心类型。你可以把它理解成一个“可挂组件的层级节点”: - 它自身保存 `name`、`id`、`uuid`、active 状态、父子关系和所属 `Scene`。 - 它始终自带一个 `TransformComponent`。 - 它通过模板 `AddComponent()`/`GetComponent()` 组织附加组件。 - 它负责把生命周期回调分发给已挂接组件。 这个设计明显接近 Unity 的 `GameObject` 模式,优点是对象树和组件树可以直接映射到编辑器层级与脚本使用习惯;代价是很多行为依赖“对象是否由 `Scene` 托管”,而不是只看对象本身。 ## 内建 Transform 语义 `GameObject` 构造函数会直接 `new TransformComponent()`,并把该组件绑定为内建 `m_transform`。这带来几个重要结果: - 每个 `GameObject` 天生就有 `TransformComponent`,不需要也不应该自己创建。 - `AddComponent()` 不会生成第二个 `Transform`,而是直接返回内建的那个实例。 - `RemoveComponent()` 明确禁止移除 `Transform`。 如果你把这个模块当成 Unity 风格对象树来理解,这一行为是合理的;但它与“所有组件都等价可增删”的纯组合系统不同,文档中必须明确区分。 ## 场景托管与独立对象 `GameObject` 有两种常见存在方式: ### 1. 独立构造 直接调用 `GameObject go;` 或 `GameObject go("Name");` - 会创建对象和内建 `Transform`。 - 不会自动注册到全局查找表。 - `m_scene` 为空。 - 不会自动调用 `Awake()`。 这类对象适合单元测试或临时对象操作,但不要把它和“已经加入场景”的对象等同。 ### 2. 由 `Scene::CreateGameObject()` 创建 这是当前运行时的推荐路径。 - `Scene` 会把对象放进场景拥有的 `unique_ptr` 容器。 - 对象会注册到全局 registry。 - `m_scene` 被设置为所属场景。 - 如果指定父对象,会接入场景层级。 - 创建结束后会立即调用 `Awake()`。 因此,静态查找接口和完整生命周期语义,本质上都更偏向“场景托管对象”。 ## 生命周期 `GameObject` 自己不派生自 `Component`,但它是组件生命周期的分发器。 - `Awake()` 会遍历普通组件并调用对应钩子。 - `Start()` 只在对象处于 `active in hierarchy` 时执行;首次执行后会把 `m_started` 置为 true,之后不再重复调用。 - `Update()`、`FixedUpdate()`、`LateUpdate()` 只在对象处于激活层级时向已启用组件分发,并递归处理子对象。 - `OnDestroy()` 会把销毁消息发给普通组件。 注意两个当前实现特征: - `TransformComponent` 不在 `m_components` 容器中,因此这些遍历不会覆盖 `Transform`。 - `AddComponent()` 只是创建并挂接组件,不会自动调用 `Awake()`、`Start()` 或 `OnEnable()`。 - 如果 `GameObject` 已经进入过 `Start()` 阶段,后续再动态添加组件时,新的组件也不会在未来被自动补发 `Start()`,因为 `m_started` 已经锁定为 true。 这意味着运行时动态加组件时,当前行为比 Unity 更原始,使用者需要自己控制初始化时机。 ## 激活状态与层级传播 `GameObject` 同时维护: - `m_activeSelf`:对象自身开关,对应 `IsActive()` - 层级有效激活态:父链都激活时才为 true,对应 `IsActiveInHierarchy()` `SetActive()` 和 `SetParent()` 都会计算层级激活态是否发生变化;如果发生变化,会: - 给当前对象上已启用组件发送 `OnEnable()` 或 `OnDisable()` - 递归向子对象传播 这一行为已经由 `tests/Components/test_game_object.cpp` 和 `tests/Components/test_component.cpp` 覆盖。 ## 查找与全局注册表 静态接口: - `Find(const std::string&)` - `FindObjectsOfType()` - `FindGameObjectsWithTag(const std::string&)` 它们都依赖 `GameObject::GetGlobalRegistry()`。按当前实现,这个 registry 主要在以下路径填充: - `Scene::CreateGameObject()` - `Scene::DeserializeFromString()` 普通构造出来但未加入场景的对象,不会自动出现在这些查找结果里。 另外,`FindGameObjectsWithTag()` 当前实际上比较的是对象名称,而不是独立的 tag 字段。这是一个重要的实现限制,文档和上层调用都不应假定已经存在真正的 tag 系统。 ## 所有权与销毁 ### 对象所有权 - 场景托管对象由 `Scene` 通过 `std::unique_ptr` 持有。 - 普通组件由 `GameObject` 通过 `std::unique_ptr` 持有。 - `TransformComponent` 单独由裸指针 `m_transform` 持有,并在析构中 `delete`。 ### `Destroy()` 的当前行为 - 如果对象属于某个 `Scene`,`Destroy()` 会委托给 `Scene::DestroyGameObject(this)`。 - 如果对象不属于场景,`Destroy()` 只会调用 `OnDestroy()`,不会 `delete this`。 这和很多用户的直觉并不完全一致。对独立对象来说,`Destroy()` 更像“发送销毁事件”,不是“释放对象内存”。 另外,析构函数本身不会主动调用 `OnDestroy()`;它只会删除内建 `Transform` 并清空组件容器。所以不要依赖析构语义去替代显式销毁回调。 ## 序列化边界 `GameObject::Serialize()` 当前只写入: - `name` - `active` - `id` - `uuid` - `transform` 它不会负责写出普通组件列表,也不会负责写出父子关系。完整场景序列化是在 `Scene::SerializeToString()` 中递归完成的,那里才会: - 输出 `parent` - 遍历普通组件并写入 `component=;payload` - 递归所有子对象 因此,单独调用 `GameObject::Serialize()` 更适合基础对象状态转储,而不是完整 prefab/scene 快照。 还要注意一个当前版本的生命周期差异:`Scene::DeserializeFromString()` 在重建对象和组件后,并不会自动调用 `Awake()`。也就是说,场景加载恢复出的对象和通过 `Scene::CreateGameObject()` 新建出来的对象,在初始化阶段语义上并不完全一致。 ## 线程语义 - 当前实现没有内部加锁。 - 父子层级、组件容器和全局 registry 都不是线程安全容器。 - 默认应按主线程场景更新路径使用。 ## 当前实现限制 - 没有真正的 tag 系统,`FindGameObjectsWithTag()` 只是按名称匹配。 - `AddComponent()` 不会自动驱动新组件进入完整生命周期。 - 已经 `Start()` 过的对象在运行时新增组件后,新组件不会被自动 `Start()`。 - 静态查找接口只对已注册对象生效,独立构造对象默认不可见。 - `Destroy()` 对独立对象不会释放自身内存。 - `Transform` 不会被普通组件遍历逻辑自动包含在生命周期分发中。 - 通过 `Scene::DeserializeFromString()` 恢复的对象当前不会自动补发 `Awake()`。 ## 推荐使用方式 1. 运行时对象优先通过 `Scene::CreateGameObject()` 创建,而不是直接构造裸对象。 2. 操作父子关系优先使用 `GameObject::SetParent()`,而不是只改 `TransformComponent::SetParent()`。 3. 需要保存完整对象树时,优先走 `Scene::SerializeToString()` / `Scene::DeserializeFromString()`。 4. 运行时动态挂组件时,不要假定引擎已经自动补发 `Awake`/`Start`。 5. 场景反序列化后如果某些组件依赖 `Awake()` 完成初始化,需要显式检查当前加载路径是否已补齐这一步。 ## 相关方法 - [Constructor](Constructor.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) - [API 总索引](../../../main.md)