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

6.0 KiB
Raw Blame History

TransformComponent

命名空间: XCEngine::Components

类型: class

头文件: XCEngine/Components/TransformComponent.h

描述: GameObject 的内建变换组件,负责局部/世界空间变换、层级矩阵计算和常见的空间操作。

角色概述

TransformComponent 是当前对象树里最基础、也是最特殊的组件。它既继承自 Component,又不属于普通“可随意添加和删除”的组件集合:

  • 每个 GameObject 构造时都会创建一个 TransformComponent
  • 普通组件通过 Component::transform() 获取它。
  • 场景层级和空间层级的大部分语义都围绕它展开。

如果把整个 Components 模块类比成 Unity 风格对象系统,那么 TransformComponent 对应的就是最接近 Unity Transform 的那一层基础设施。

局部与世界变换

类型内部同时维护:

  • 局部数据:m_localPositionm_localRotationm_localScale
  • 派生缓存:m_localToWorldMatrixm_worldPositionm_worldRotationm_worldScale
  • 脏标记:m_dirty

当前实现使用懒更新模式:

  • 修改局部位置、旋转或缩放时,调用 SetDirty()
  • 真正读取世界矩阵、世界位置、世界旋转或世界缩放时,才通过 UpdateWorldTransform() 重新计算

这是一种常见的商业引擎做法,优点是多个 setter 可以先累积,再在真正需要世界空间数据时统一计算;代价是层级脏标记传播一定要正确,否则很容易出现缓存过期问题。

层级关系

TransformComponent 自己维护一套父子变换树:

  • GetParent()
  • SetParent(TransformComponent*, bool worldPositionStays = true)
  • GetChild()
  • GetChildCount()
  • DetachChildren()

但要注意,变换树和 GameObject 的对象树虽然通常同步,却不是完全同一个概念。

推荐做法

当你操作真实场景对象关系时,优先使用 GameObject::SetParent()

原因是 GameObject::SetParent() 会同时处理:

  • GameObject 子列表
  • 场景根节点列表
  • 激活层级变化传播
  • 对应 Transform 的父子关系

而直接调用 TransformComponent::SetParent() 只会改变变换树,不会完整维护场景对象树的其他语义。

空间操作

TransformComponent 提供了当前最常用的一组空间 API

  • 世界/局部位置、旋转、缩放读写
  • GetForward()GetRight()GetUp()
  • LookAt()
  • Rotate()Translate()
  • TransformPoint() / InverseTransformPoint()
  • TransformDirection() / InverseTransformDirection()

从实现看,这些方法都是直接基于 QuaternionMatrix4x4 和当前缓存矩阵完成的轻量封装,没有额外的物理、动画或约束系统参与。

查找语义

Find(const std::string& name) 的行为容易被误解。按当前实现,它不是“从当前节点的子树开始找”,而是:

  1. 通过宿主 GameObject 拿到所属 Scene
  2. 从场景所有 root game objects 开始递归搜索
  3. 找到第一个名称匹配的 GameObject 后返回其 Transform

因此,它更像一个场景级按名称查找的快捷入口,而不是严格的局部子树查询 API。

序列化语义

Serialize() / Deserialize() 当前只处理局部变换:

  • position
  • rotation
  • scale

不会直接写出父子关系,也不会直接写出世界矩阵缓存。世界空间数据会在反序列化后通过 SetDirty() 延迟重建。

这和商业引擎常见做法一致:持久化局部值,把世界值当运行时派生结果。

线程语义

  • 当前实现没有内部加锁。
  • m_dirty、父子列表和缓存矩阵都按单线程访问模型设计。
  • 默认应在主线程或明确受控的场景更新线程上读写。

当前实现限制

  • SetDirty() 会递归标记所有子节点,这是正确方向,但如果层级很深,批量修改会带来明显传播成本。
  • SetSiblingIndex()SetAsFirstSibling()SetAsLastSibling() 当前只修改 m_siblingIndex 数值,没有真正重排父节点 m_children 顺序。
  • DetachChildren() 只改 TransformComponent 自己的父子指针,不会同步处理 GameObject 层级与 Scene 根节点列表。
  • Find() 是场景级名称搜索,不是局部子树搜索。

这些限制非常值得在编辑器或脚本层显式规避。

推荐使用方式

  1. 日常层级重组优先用 GameObject::SetParent()
  2. TransformComponent 视为内建基础设施,不要尝试把它当普通组件那样增删。
  3. 需要保存/加载时只关心局部值,不要手动持久化世界矩阵。
  4. 使用 Find() 时明确它是场景级名称匹配,不要把它当路径查询接口。

相关方法

相关指南

相关文档