docs: sync gameobject tag layer docs
This commit is contained in:
48
docs/api/XCEngine/Components/GameObject/CompareTag.md
Normal file
48
docs/api/XCEngine/Components/GameObject/CompareTag.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# GameObject::CompareTag
|
||||
|
||||
判断当前对象的 tag 是否与给定字符串精确匹配。
|
||||
|
||||
```cpp
|
||||
bool CompareTag(const std::string& tag) const;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
|
||||
当前实现等价于:
|
||||
|
||||
```cpp
|
||||
return m_tag == tag;
|
||||
```
|
||||
|
||||
因此它的语义非常明确:
|
||||
|
||||
- 比较的是真实 tag 字段
|
||||
- 比较方式是精确字符串比较
|
||||
- 大小写敏感
|
||||
- 不做模糊匹配,也不看对象名称
|
||||
|
||||
由于 [SetTag](SetTag.md) 会把空字符串规范化成 `"Untagged"`,在引擎正常路径下,`CompareTag("")` 通常会返回 `false`。
|
||||
|
||||
## 参数
|
||||
|
||||
- `tag` - 要比较的目标 tag。
|
||||
|
||||
## 返回值
|
||||
|
||||
- `true` - 当前对象 tag 与参数完全相同。
|
||||
- `false` - 其余情况。
|
||||
|
||||
## 为什么推荐用它而不是手写字符串比较
|
||||
|
||||
虽然当前实现只是 `m_tag == tag`,但对上层代码来说,`CompareTag()` 仍然更稳妥:
|
||||
|
||||
- 语义更接近 Unity,脚本和原生更一致
|
||||
- 未来如果引擎在这里加入规范化、诊断或更快查询路径,调用点不需要改
|
||||
- API 意图更清楚,读文档的人一眼能看出你在做“tag 分类判断”
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [GameObject](GameObject.md)
|
||||
- [GetTag](GetTag.md)
|
||||
- [SetTag](SetTag.md)
|
||||
- [FindGameObjectsWithTag](FindGameObjectsWithTag.md)
|
||||
@@ -1,31 +1,62 @@
|
||||
# GameObject::Deserialize
|
||||
|
||||
公开方法,详见头文件声明。
|
||||
从一行分号分隔的 token 流恢复当前对象的基础状态。
|
||||
|
||||
```cpp
|
||||
void Deserialize(std::istream& is);
|
||||
```
|
||||
|
||||
该方法声明于 `XCEngine/Components/GameObject.h`,当前页面用于固定 `GameObject` 类目录下的方法级 canonical 路径。
|
||||
## 行为说明
|
||||
|
||||
**参数:**
|
||||
- `is` - 参数语义详见头文件声明。
|
||||
当前实现会持续读取 token,直到遇到:
|
||||
|
||||
**返回:** `void` - 无返回值。
|
||||
- `EOF`
|
||||
- 换行
|
||||
- 回车
|
||||
|
||||
**示例:**
|
||||
它能识别的 key 包括:
|
||||
|
||||
```cpp
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
- `name`
|
||||
- `tag`
|
||||
- `active`
|
||||
- `layer`
|
||||
- `id`
|
||||
- `uuid`
|
||||
- `transform`
|
||||
|
||||
void Example() {
|
||||
XCEngine::Components::GameObject object;
|
||||
// 根据上下文补齐参数后调用 GameObject::Deserialize(...)。
|
||||
(void)object;
|
||||
}
|
||||
```
|
||||
未知 key 会被跳过,因此它对“多写了一些暂时不认识的字段”具备有限容错能力。
|
||||
|
||||
## Tag 与 Layer 恢复语义
|
||||
|
||||
- `tag` 最终走 [SetTag](SetTag.md),因此空字符串会回退到 `"Untagged"`
|
||||
- `layer` 最终走 [SetLayer](SetLayer.md),因此上界仍会被限制到 `31`
|
||||
|
||||
按当前引擎自己生成的数据格式,这可以稳定 round-trip tag 与 `0..31` 范围内的 layer。
|
||||
|
||||
## 当前边界
|
||||
|
||||
- 不会创建普通组件
|
||||
- 不会恢复父子关系
|
||||
- 不会设置所属 `Scene`
|
||||
- 不会触发 `Awake()`、`Start()` 或创建事件
|
||||
- 只恢复这一个对象的基础状态
|
||||
|
||||
完整场景恢复应使用 [Scene::DeserializeFromString](../../Scene/Scene/DeserializeFromString.md)。
|
||||
|
||||
## 参数
|
||||
|
||||
- `is` - 输入流。
|
||||
|
||||
## 返回值
|
||||
|
||||
- 无。
|
||||
|
||||
## 设计提示
|
||||
|
||||
这个接口更像“对象基础状态的底层反序列化原语”,而不是面向最终用户的完整场景加载入口。商业引擎里把这类低层接口和场景容器级加载分开,是为了避免单对象 API 意外承担过多树结构与资源恢复责任。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类总览](GameObject.md)
|
||||
- [返回模块目录](../Components.md)
|
||||
- [GameObject](GameObject.md)
|
||||
- [Serialize](Serialize.md)
|
||||
- [Scene::DeserializeFromString](../../Scene/Scene/DeserializeFromString.md)
|
||||
|
||||
@@ -1,31 +1,47 @@
|
||||
# GameObject::FindGameObjectsWithTag
|
||||
|
||||
查找并返回匹配对象。
|
||||
按 tag 返回当前全局 registry 中的全部匹配对象。
|
||||
|
||||
```cpp
|
||||
static std::vector<GameObject*> FindGameObjectsWithTag(const std::string& tag);
|
||||
```
|
||||
|
||||
该方法声明于 `XCEngine/Components/GameObject.h`,当前页面用于固定 `GameObject` 类目录下的方法级 canonical 路径。
|
||||
## 行为说明
|
||||
|
||||
**参数:**
|
||||
- `tag` - 参数语义详见头文件声明。
|
||||
|
||||
**返回:** `std::vector<GameObject*>` - 返回值语义详见头文件声明。
|
||||
|
||||
**示例:**
|
||||
当前实现会遍历 `GameObject::GetGlobalRegistry()`,并对每个对象执行:
|
||||
|
||||
```cpp
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
|
||||
void Example() {
|
||||
XCEngine::Components::GameObject object;
|
||||
// 根据上下文补齐参数后调用 GameObject::FindGameObjectsWithTag(...)。
|
||||
(void)object;
|
||||
}
|
||||
pair.second->CompareTag(tag)
|
||||
```
|
||||
|
||||
所有匹配对象都会被追加到结果数组中。
|
||||
|
||||
这意味着:
|
||||
|
||||
- 比较的是真实 tag 字段,不是对象名称
|
||||
- 查询范围是全局 registry,而不是“当前某个 Scene 的子树”
|
||||
- 未加入场景的独立 `GameObject` 默认不会出现在结果里
|
||||
- 由 `Scene::DeserializeFromString()` 恢复出来并重新注册的对象也会被查到
|
||||
|
||||
由于 registry 底层是 `std::unordered_map`,返回顺序当前不构成稳定契约。如果你需要“当前场景树里的第一个匹配对象”,应优先使用 [Scene::FindGameObjectWithTag](../../Scene/Scene/FindGameObjectWithTag.md)。
|
||||
|
||||
## 参数
|
||||
|
||||
- `tag` - 要精确匹配的 tag 字符串。
|
||||
|
||||
## 返回值
|
||||
|
||||
- `std::vector<GameObject*>` - 当前所有匹配对象;找不到时返回空数组。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 做全局调试扫描
|
||||
- 脚本或工具侧收集某类对象
|
||||
- 不要求结果顺序稳定,只关心“有哪些对象匹配”
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类总览](GameObject.md)
|
||||
- [返回模块目录](../Components.md)
|
||||
- [GameObject](GameObject.md)
|
||||
- [GetTag](GetTag.md)
|
||||
- [CompareTag](CompareTag.md)
|
||||
- [Scene::FindGameObjectWithTag](../../Scene/Scene/FindGameObjectWithTag.md)
|
||||
|
||||
@@ -6,168 +6,222 @@
|
||||
|
||||
**头文件**: `XCEngine/Components/GameObject.h`
|
||||
|
||||
**描述**: 场景对象树的基础节点,负责对象身份、父子层级、激活状态、组件容器和生命周期分发。
|
||||
**描述**: 当前对象树模型的核心节点,负责身份、层级、激活状态、轻量 `tag/layer` 元数据、组件容器与生命周期分发。
|
||||
|
||||
## 角色概述
|
||||
## 设计定位
|
||||
|
||||
`GameObject` 是当前引擎对象模型的中心类型。你可以把它理解成一个“可挂组件的层级节点”:
|
||||
`GameObject` 是当前引擎对象模型的中心类型。它的设计明显借鉴了 Unity 的 `GameObject + Transform + Component` 体系,但刻意保持为更轻的实现:
|
||||
|
||||
- 它自身保存 `name`、`id`、`uuid`、active 状态、父子关系和所属 `Scene`。
|
||||
- 它始终自带一个 `TransformComponent`。
|
||||
- 它通过模板 `AddComponent<T>()`/`GetComponent<T>()` 组织附加组件。
|
||||
- 它负责把生命周期回调分发给已挂接组件。
|
||||
- 用对象树而不是 ECS 组织运行时对象,便于编辑器层级、脚本调用和场景保存。
|
||||
- 把 `Transform` 作为基础设施内建到每个对象里,避免“对象没有空间变换”的特例分支。
|
||||
- 提供熟悉的 `tag` / `layer` 心智模型,但暂时不引入完整的项目级 TagManager、LayerMask 配置器或查询索引。
|
||||
|
||||
这个设计明显接近 Unity 的 `GameObject` 模式,优点是对象树和组件树可以直接映射到编辑器层级与脚本使用习惯;代价是很多行为依赖“对象是否由 `Scene` 托管”,而不是只看对象本身。
|
||||
这种设计的好处是学习成本低、编辑器友好、序列化边界清晰;代价则是很多查询仍是线性扫描,生命周期也更依赖 `Scene -> GameObject -> Component` 的调用链。
|
||||
|
||||
## 内建 Transform 语义
|
||||
## 当前真实数据模型
|
||||
|
||||
`GameObject` 构造函数会直接 `new TransformComponent()`,并把该组件绑定为内建 `m_transform`。这带来几个重要结果:
|
||||
`GameObject` 当前同时持有以下几类状态:
|
||||
|
||||
- 每个 `GameObject` 天生就有 `TransformComponent`,不需要也不应该自己创建。
|
||||
- `AddComponent<TransformComponent>()` 不会生成第二个 `Transform`,而是直接返回内建的那个实例。
|
||||
- `RemoveComponent()` 明确禁止移除 `Transform`。
|
||||
- 身份字段:`m_id`、`m_uuid`、`m_name`
|
||||
- 元数据字段:`m_tag`、`m_layer`
|
||||
- 层级字段:`m_parent`、`m_children`
|
||||
- 激活字段:`m_activeSelf`、`m_started`
|
||||
- 所有权字段:`m_scene`、`m_transform`、`m_components`
|
||||
|
||||
如果你把这个模块当成 Unity 风格对象树来理解,这一行为是合理的;但它与“所有组件都等价可增删”的纯组合系统不同,文档中必须明确区分。
|
||||
其中最容易和旧文档混淆的是 `tag` / `layer`:它们现在已经是独立字段,不再是名字别名或“预留接口”。
|
||||
|
||||
## 场景托管与独立对象
|
||||
## Tag 与 Layer 语义
|
||||
|
||||
`GameObject` 有两种常见存在方式:
|
||||
### Tag
|
||||
|
||||
### 1. 独立构造
|
||||
- 底层字段是 `m_tag`
|
||||
- 默认值是 `"Untagged"`
|
||||
- [SetTag](SetTag.md) 传入空字符串时也会规范化回 `"Untagged"`
|
||||
- [CompareTag](CompareTag.md) 直接比较 `m_tag == tag`
|
||||
|
||||
直接调用 `GameObject go;` 或 `GameObject go("Name");`
|
||||
因此当前引擎里:
|
||||
|
||||
- 会创建对象和内建 `Transform`。
|
||||
- 不会自动注册到全局查找表。
|
||||
- `m_scene` 为空。
|
||||
- 不会自动调用 `Awake()`。
|
||||
- `tag` 不是 `name` 的别名
|
||||
- `CompareTag("Player")` 比较的是真实 tag 字段
|
||||
- [FindGameObjectsWithTag](FindGameObjectsWithTag.md) 和 [Scene::FindGameObjectWithTag](../../Scene/Scene/FindGameObjectWithTag.md) 都是真正按 tag 查询
|
||||
|
||||
这类对象适合单元测试或临时对象操作,但不要把它和“已经加入场景”的对象等同。
|
||||
### Layer
|
||||
|
||||
### 2. 由 `Scene::CreateGameObject()` 创建
|
||||
- 底层字段是 `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 再额外做上界限制”。
|
||||
|
||||
- `Scene` 会把对象放进场景拥有的 `unique_ptr` 容器。
|
||||
- 对象会注册到全局 registry。
|
||||
- `m_scene` 被设置为所属场景。
|
||||
- 如果指定父对象,会接入场景层级。
|
||||
- 创建结束后会立即调用 `Awake()`。
|
||||
## 为什么 Tag / Layer 做成轻量字段
|
||||
|
||||
因此,静态查找接口和完整生命周期语义,本质上都更偏向“场景托管对象”。
|
||||
当前实现没有引入完整 TagManager 的原因并不是“尚未支持 tag”,而是有意把它们先收敛为轻量对象元数据:
|
||||
|
||||
## 生命周期
|
||||
- 对编辑器和脚本层来说,独立字段已经足够支撑常见对象分类与查询。
|
||||
- 对场景序列化来说,独立字段更容易稳定 round-trip。
|
||||
- 对运行时复杂度来说,不必提前引入项目级表、重命名迁移、索引维护和配置资产。
|
||||
|
||||
`GameObject` 自己不派生自 `Component`,但它是组件生命周期的分发器。
|
||||
因此它更接近“商业引擎里可持续扩展的第一阶段实现”,而不是临时占位符。
|
||||
|
||||
- `Awake()` 会遍历普通组件并调用对应钩子。
|
||||
- `Start()` 只在对象处于 `active in hierarchy` 时执行;首次执行后会把 `m_started` 置为 true,之后不再重复调用。
|
||||
- `Update()`、`FixedUpdate()`、`LateUpdate()` 只在对象处于激活层级时向已启用组件分发,并递归处理子对象。
|
||||
- `OnDestroy()` 会把销毁消息发给普通组件。
|
||||
## 内建 Transform 与组件模型
|
||||
|
||||
注意两个当前实现特征:
|
||||
`GameObject` 构造时会立即创建一个 `TransformComponent` 并绑定到 `m_transform`。这带来三个明确语义:
|
||||
|
||||
- `TransformComponent` 不在 `m_components` 容器中,因此这些遍历不会覆盖 `Transform`。
|
||||
- `AddComponent<T>()` 只是创建并挂接组件,不会自动调用 `Awake()`、`Start()` 或 `OnEnable()`。
|
||||
- 如果 `GameObject` 已经进入过 `Start()` 阶段,后续再动态添加组件时,新的组件也不会在未来被自动补发 `Start()`,因为 `m_started` 已经锁定为 true。
|
||||
- 每个对象天然拥有 `Transform`
|
||||
- `AddComponent<TransformComponent>()` 返回已有实例,而不是再造一个
|
||||
- `RemoveComponent()` 不允许移除 `Transform`
|
||||
|
||||
这意味着运行时动态加组件时,当前行为比 Unity 更原始,使用者需要自己控制初始化时机。
|
||||
普通组件则保存在 `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` 同时维护:
|
||||
`GameObject` 维护两套不同层面的激活状态:
|
||||
|
||||
- `m_activeSelf`:对象自身开关,对应 `IsActive()`
|
||||
- 层级有效激活态:父链都激活时才为 true,对应 `IsActiveInHierarchy()`
|
||||
- [IsActive](IsActive.md) 对应 `m_activeSelf`
|
||||
- [IsActiveInHierarchy](IsActiveInHierarchy.md) 要求对象自己激活,且所有父节点也激活
|
||||
|
||||
`SetActive()` 和 `SetParent()` 都会计算层级激活态是否发生变化;如果发生变化,会:
|
||||
[SetActive](SetActive.md) 与 [SetParent](SetParent.md) 都会重新计算“层级有效激活态”。一旦有效激活态发生变化,当前实现会:
|
||||
|
||||
- 给当前对象上已启用组件发送 `OnEnable()` 或 `OnDisable()`
|
||||
- 递归向子对象传播
|
||||
- 对已启用的普通组件发送 `OnEnable()` 或 `OnDisable()`
|
||||
- 把变化递归传播给子对象
|
||||
|
||||
这一行为已经由 `tests/Components/test_game_object.cpp` 和 `tests/Components/test_component.cpp` 覆盖。
|
||||
这和商业引擎里把“对象自己是否开启”和“它是否真的在运行层级里生效”拆开的做法一致。
|
||||
|
||||
## 查找与全局注册表
|
||||
## 查找语义与全局 Registry
|
||||
|
||||
静态接口:
|
||||
静态查找接口:
|
||||
|
||||
- `Find(const std::string&)`
|
||||
- `FindObjectsOfType()`
|
||||
- `FindGameObjectsWithTag(const std::string&)`
|
||||
- [Find](Find.md) 按对象名遍历全局 registry
|
||||
- [FindObjectsOfType](FindObjectsOfType.md) 返回当前 registry 全部对象
|
||||
- [FindGameObjectsWithTag](FindGameObjectsWithTag.md) 通过 `CompareTag()` 过滤 registry
|
||||
|
||||
它们都依赖 `GameObject::GetGlobalRegistry()`。按当前实现,这个 registry 主要在以下路径填充:
|
||||
当前 registry 主要由两条路径填充:
|
||||
|
||||
- `Scene::CreateGameObject()`
|
||||
- `Scene::DeserializeFromString()`
|
||||
|
||||
普通构造出来但未加入场景的对象,不会自动出现在这些查找结果里。
|
||||
|
||||
另外,`FindGameObjectsWithTag()` 当前实际上比较的是对象名称,而不是独立的 tag 字段。这是一个重要的实现限制,文档和上层调用都不应假定已经存在真正的 tag 系统。
|
||||
因此未加入场景的独立对象默认不会出现在这些查询结果中。另一方面,场景级 [Scene::FindGameObjectWithTag](../../Scene/Scene/FindGameObjectWithTag.md) 则是沿场景根对象做深度优先遍历,更适合表达“当前场景树里的第一个匹配对象”。
|
||||
|
||||
## 所有权与销毁
|
||||
|
||||
### 对象所有权
|
||||
- 场景托管对象由 `Scene` 以 `std::unique_ptr<GameObject>` 持有
|
||||
- 普通组件由 `GameObject` 以 `std::unique_ptr<Component>` 持有
|
||||
- `TransformComponent` 由 `m_transform` 单独持有并在析构时删除
|
||||
|
||||
- 场景托管对象由 `Scene` 通过 `std::unique_ptr<GameObject>` 持有。
|
||||
- 普通组件由 `GameObject` 通过 `std::unique_ptr<Component>` 持有。
|
||||
- `TransformComponent` 单独由裸指针 `m_transform` 持有,并在析构中 `delete`。
|
||||
[Destroy](Destroy.md) 当前有两种路径:
|
||||
|
||||
### `Destroy()` 的当前行为
|
||||
- 如果对象属于某个场景,则委托给 `Scene::DestroyGameObject(this)`
|
||||
- 如果对象不属于场景,只调用 `OnDestroy()`,不会释放对象自身内存
|
||||
|
||||
- 如果对象属于某个 `Scene`,`Destroy()` 会委托给 `Scene::DestroyGameObject(this)`。
|
||||
- 如果对象不属于场景,`Destroy()` 只会调用 `OnDestroy()`,不会 `delete this`。
|
||||
|
||||
这和很多用户的直觉并不完全一致。对独立对象来说,`Destroy()` 更像“发送销毁事件”,不是“释放对象内存”。
|
||||
|
||||
另外,析构函数本身不会主动调用 `OnDestroy()`;它只会删除内建 `Transform` 并清空组件容器。所以不要依赖析构语义去替代显式销毁回调。
|
||||
因此“独立对象调用 `Destroy()`”不等于“对象被 delete”。真正释放内存的仍然是拥有它的那一层所有者。
|
||||
|
||||
## 序列化边界
|
||||
|
||||
`GameObject::Serialize()` 当前只写入:
|
||||
[Serialize](Serialize.md) 只负责单对象基础状态:
|
||||
|
||||
- `name`
|
||||
- `tag`
|
||||
- `active`
|
||||
- `layer`
|
||||
- `id`
|
||||
- `uuid`
|
||||
- `transform`
|
||||
|
||||
它不会负责写出普通组件列表,也不会负责写出父子关系。完整场景序列化是在 `Scene::SerializeToString()` 中递归完成的,那里才会:
|
||||
它不会保存:
|
||||
|
||||
- 输出 `parent`
|
||||
- 遍历普通组件并写入 `component=<type>;payload`
|
||||
- 递归所有子对象
|
||||
- 普通组件列表
|
||||
- 父子层级
|
||||
- 所属场景
|
||||
|
||||
因此,单独调用 `GameObject::Serialize()` 更适合基础对象状态转储,而不是完整 prefab/scene 快照。
|
||||
完整对象树持久化由 [Scene::SerializeToString](../../Scene/Scene/SerializeToString.md) 负责;对应地,[Deserialize](Deserialize.md) 也只恢复单对象基础字段,不会重建组件、父子关系或 `Scene` 归属。
|
||||
|
||||
还要注意一个当前版本的生命周期差异:`Scene::DeserializeFromString()` 在重建对象和组件后,并不会自动调用 `Awake()`。也就是说,场景加载恢复出的对象和通过 `Scene::CreateGameObject()` 新建出来的对象,在初始化阶段语义上并不完全一致。
|
||||
## 托管脚本桥接
|
||||
|
||||
## 线程语义
|
||||
Mono 运行时当前已经把这些元数据直接暴露给 C#:
|
||||
|
||||
- 当前实现没有内部加锁。
|
||||
- 父子层级、组件容器和全局 registry 都不是线程安全容器。
|
||||
- 默认应按主线程场景更新路径使用。
|
||||
- `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 系统,`FindGameObjectsWithTag()` 只是按名称匹配。
|
||||
- `AddComponent<T>()` 不会自动驱动新组件进入完整生命周期。
|
||||
- 已经 `Start()` 过的对象在运行时新增组件后,新组件不会被自动 `Start()`。
|
||||
- 静态查找接口只对已注册对象生效,独立构造对象默认不可见。
|
||||
- `Destroy()` 对独立对象不会释放自身内存。
|
||||
- `Transform` 不会被普通组件遍历逻辑自动包含在生命周期分发中。
|
||||
- 通过 `Scene::DeserializeFromString()` 恢复的对象当前不会自动补发 `Awake()`。
|
||||
- tag / layer 仍是轻量字段,没有项目级定义表、mask 工具或索引查询
|
||||
- 全局静态查找只对已注册对象成立
|
||||
- 运行时新增组件不会自动补发初始化生命周期
|
||||
- `Destroy()` 对独立对象不会释放自身内存
|
||||
- `Transform` 不参与普通组件生命周期遍历
|
||||
- `Scene::DeserializeFromString()` 恢复出的对象不会自动补发 `Awake()`
|
||||
|
||||
## 推荐使用方式
|
||||
|
||||
1. 运行时对象优先通过 `Scene::CreateGameObject()` 创建,而不是直接构造裸对象。
|
||||
2. 操作父子关系优先使用 `GameObject::SetParent()`,而不是只改 `TransformComponent::SetParent()`。
|
||||
3. 需要保存完整对象树时,优先走 `Scene::SerializeToString()` / `Scene::DeserializeFromString()`。
|
||||
4. 运行时动态挂组件时,不要假定引擎已经自动补发 `Awake`/`Start`。
|
||||
5. 场景反序列化后如果某些组件依赖 `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)
|
||||
@@ -198,4 +252,3 @@
|
||||
- [Component](../Component/Component.md)
|
||||
- [TransformComponent](../TransformComponent/TransformComponent.md)
|
||||
- [Scene](../../Scene/Scene/Scene.md)
|
||||
- [API 总索引](../../../main.md)
|
||||
|
||||
35
docs/api/XCEngine/Components/GameObject/GetLayer.md
Normal file
35
docs/api/XCEngine/Components/GameObject/GetLayer.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# GameObject::GetLayer
|
||||
|
||||
返回当前对象的 layer 值。
|
||||
|
||||
```cpp
|
||||
uint8_t GetLayer() const;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
|
||||
这个接口直接返回底层 `m_layer` 的当前值。
|
||||
|
||||
按当前实现:
|
||||
|
||||
- 默认值是 `0`
|
||||
- 原生 [SetLayer](SetLayer.md) 会把上界限制到 `31`
|
||||
- 托管 `GameObject.layer` / `Component.layer` 会先把 `int` 显式限制到 `[0, 31]`
|
||||
- `GameObject::Serialize()` 与 `Scene::SerializeToString()` 都会保存这个字段
|
||||
|
||||
因此在引擎正常产生的数据路径里,这个值会稳定落在 `0..31`。
|
||||
|
||||
## 返回值
|
||||
|
||||
- `uint8_t` - 当前对象的 layer。
|
||||
|
||||
## 常见用途
|
||||
|
||||
- 渲染层过滤
|
||||
- 轻量对象分组
|
||||
- 向脚本层暴露与 Unity 类似的 `layer` 属性
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [GameObject](GameObject.md)
|
||||
- [SetLayer](SetLayer.md)
|
||||
40
docs/api/XCEngine/Components/GameObject/GetTag.md
Normal file
40
docs/api/XCEngine/Components/GameObject/GetTag.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# GameObject::GetTag
|
||||
|
||||
返回当前对象的 tag 字符串。
|
||||
|
||||
```cpp
|
||||
const std::string& GetTag() const;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
|
||||
这个接口直接返回底层 `m_tag` 的只读引用,不做复制,也不做格式转换。
|
||||
|
||||
按当前实现:
|
||||
|
||||
- 默认构造出的对象 tag 是 `"Untagged"`
|
||||
- [SetTag](SetTag.md) 传入空字符串时也会回退到 `"Untagged"`
|
||||
- 场景反序列化恢复 tag 时同样走 `SetTag()` 逻辑
|
||||
|
||||
因此,只要对象来自引擎正常构造、反序列化或 setter 路径,这里通常不会返回空字符串。
|
||||
|
||||
## 返回值
|
||||
|
||||
- `const std::string&` - 当前对象 tag 的只读引用。
|
||||
|
||||
## 使用建议
|
||||
|
||||
- 需要判断对象分类时,优先用 [CompareTag](CompareTag.md),不要到处手写 `GetTag() == "Player"`。
|
||||
- 需要显示当前值、做日志输出或写入编辑器面板时,再直接读取 `GetTag()`。
|
||||
|
||||
## 线程语义
|
||||
|
||||
- 返回的是对象内部字符串引用
|
||||
- 在对象销毁前、且没有并发修改该对象时读取才安全
|
||||
- 当前实现没有为 tag 读写提供内部锁
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [GameObject](GameObject.md)
|
||||
- [SetTag](SetTag.md)
|
||||
- [CompareTag](CompareTag.md)
|
||||
@@ -1,31 +1,61 @@
|
||||
# GameObject::Serialize
|
||||
|
||||
公开方法,详见头文件声明。
|
||||
把当前对象的基础状态写成一行分号分隔的 token 流。
|
||||
|
||||
```cpp
|
||||
void Serialize(std::ostream& os) const;
|
||||
```
|
||||
|
||||
该方法声明于 `XCEngine/Components/GameObject.h`,当前页面用于固定 `GameObject` 类目录下的方法级 canonical 路径。
|
||||
## 行为说明
|
||||
|
||||
**参数:**
|
||||
- `os` - 参数语义详见头文件声明。
|
||||
当前实现按固定顺序写出:
|
||||
|
||||
**返回:** `void` - 无返回值。
|
||||
|
||||
**示例:**
|
||||
|
||||
```cpp
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
|
||||
void Example() {
|
||||
XCEngine::Components::GameObject object;
|
||||
// 根据上下文补齐参数后调用 GameObject::Serialize(...)。
|
||||
(void)object;
|
||||
}
|
||||
```text
|
||||
name=...;
|
||||
tag=...;
|
||||
active=...;
|
||||
layer=...;
|
||||
id=...;
|
||||
uuid=...;
|
||||
transform=...;
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `tag` 是真实对象 tag,而不是名字别名
|
||||
- `layer` 是当前对象 layer
|
||||
- `transform` 继续委托给 `TransformComponent::Serialize()`
|
||||
|
||||
`tests/Components/test_game_object.cpp` 里的 `Layer_GetSetAndSerializeRoundTrip` 已验证这一行 token 流会保留 tag 和 layer 的 round-trip 结果。
|
||||
|
||||
## 当前边界
|
||||
|
||||
- 这里只写单个 `GameObject` 的基础状态
|
||||
- 不写普通组件列表
|
||||
- 不写父子关系
|
||||
- 不写所属场景
|
||||
|
||||
如果你要保存完整对象树,应使用 [Scene::SerializeToString](../../Scene/Scene/SerializeToString.md)。
|
||||
|
||||
另外,这条紧凑 token 流当前不会像 `Scene::SerializeToString()` 那样对 `name` / `tag` 做换行和反斜杠转义;它更适合引擎内部受控 round-trip,而不是通用交换格式。
|
||||
|
||||
## 参数
|
||||
|
||||
- `os` - 输出流。
|
||||
|
||||
## 返回值
|
||||
|
||||
- 无。
|
||||
|
||||
## 设计提示
|
||||
|
||||
把“单对象基础状态”和“完整场景树”拆成两层序列化接口,是商业引擎里常见的职责分离方式:
|
||||
|
||||
- `GameObject::Serialize()` 负责描述“我自己是谁”
|
||||
- `Scene::SerializeToString()` 负责描述“整棵树如何保存”
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类总览](GameObject.md)
|
||||
- [返回模块目录](../Components.md)
|
||||
- [GameObject](GameObject.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
- [Scene::SerializeToString](../../Scene/Scene/SerializeToString.md)
|
||||
|
||||
52
docs/api/XCEngine/Components/GameObject/SetLayer.md
Normal file
52
docs/api/XCEngine/Components/GameObject/SetLayer.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# GameObject::SetLayer
|
||||
|
||||
设置当前对象的 layer。
|
||||
|
||||
```cpp
|
||||
void SetLayer(uint8_t layer);
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
|
||||
当前 native 实现等价于:
|
||||
|
||||
```cpp
|
||||
m_layer = std::min<uint8_t>(layer, 31u);
|
||||
```
|
||||
|
||||
因此对 C++ 调用者来说,精确语义是:
|
||||
|
||||
- 参数类型本身就是 `uint8_t`
|
||||
- `0..31` 会原样保留
|
||||
- 大于 `31` 的值会被写成 `31`
|
||||
|
||||
## 原生与托管的差异
|
||||
|
||||
很多文档会把它简写成“clamp 到 `[0, 31]`”。这对托管调用链是正确的,但对原生头文件还不够精确:
|
||||
|
||||
- 原生公开 API 没有负数输入通道,因为参数已经是 `uint8_t`
|
||||
- 托管 `GameObject.layer` / `Component.layer` 先在 internal call 里把 `int32` 显式限制到 `[0, 31]`
|
||||
- 然后托管层再调用这个 native setter
|
||||
|
||||
所以:
|
||||
|
||||
- C++ 视角:这是“`uint8_t` + 上界 clamp”
|
||||
- C# 视角:这是完整的 `[0, 31]` clamp
|
||||
|
||||
## 参数
|
||||
|
||||
- `layer` - 目标 layer 值。
|
||||
|
||||
## 返回值
|
||||
|
||||
- 无。
|
||||
|
||||
## 设计提示
|
||||
|
||||
把 layer 收敛为 `0..31` 的轻量整数,可以让渲染过滤、对象分类、脚本暴露与场景序列化共享同一套最小稳定模型,而不必提前引入更重的 LayerMask 资产系统。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [GameObject](GameObject.md)
|
||||
- [GetLayer](GetLayer.md)
|
||||
- [Serialize](Serialize.md)
|
||||
53
docs/api/XCEngine/Components/GameObject/SetTag.md
Normal file
53
docs/api/XCEngine/Components/GameObject/SetTag.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# GameObject::SetTag
|
||||
|
||||
设置当前对象的 tag。
|
||||
|
||||
```cpp
|
||||
void SetTag(const std::string& tag);
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
|
||||
当前实现等价于:
|
||||
|
||||
```cpp
|
||||
m_tag = tag.empty() ? std::string("Untagged") : tag;
|
||||
```
|
||||
|
||||
这意味着:
|
||||
|
||||
- 传入空字符串会被规范化为 `"Untagged"`
|
||||
- 非空字符串会原样写入
|
||||
- 不会自动改对象名字
|
||||
- 不会检查这个 tag 是否已在项目里“预先声明”
|
||||
- 不会维护任何全局 tag 注册表
|
||||
|
||||
因此它已经是真实 tag 写入接口,但仍是轻量 Unity 风格实现,而不是完整 TagManager。
|
||||
|
||||
## 参数
|
||||
|
||||
- `tag` - 新的 tag;空字符串会回退到 `"Untagged"`。
|
||||
|
||||
## 设计提示
|
||||
|
||||
把空 tag 统一回落到 `"Untagged"`,能让引擎在编辑器、序列化和脚本桥接层保持一个稳定的“未分类对象”约定,而不是把 `""`、`null`、`Untagged` 混成多种语义。
|
||||
|
||||
## 与托管脚本的关系
|
||||
|
||||
托管 `GameObject.Tag` / `GameObject.tag` / `Component.Tag` / `Component.tag` 最终都会走到这个 native setter。
|
||||
|
||||
- C# 侧把 `null` 先归一化成 `string.Empty`
|
||||
- native 再把空字符串统一回落到 `"Untagged"`
|
||||
|
||||
所以原生和托管看到的是同一套 tag 规范化结果。
|
||||
|
||||
## 返回值
|
||||
|
||||
- 无。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [GameObject](GameObject.md)
|
||||
- [GetTag](GetTag.md)
|
||||
- [CompareTag](CompareTag.md)
|
||||
- [Scene::FindGameObjectWithTag](../../Scene/Scene/FindGameObjectWithTag.md)
|
||||
Reference in New Issue
Block a user