Skip to main content
摘要:本文聚焦「Unity Transform 数学变换与最佳实践 (The Math of Transform)」,梳理核心概念、关键方法与落地实践。
Transform 是 Unity 中最基础也最重要的组件,它定义了物体在空间中的位置 (Position)旋转 (Rotation)缩放 (Scale)。深刻理解其背后的线性代数原理,对于编写高性能、无 Bug 的代码至关重要。

1. 坐标空间 (Coordinate Spaces)

Unity 中存在多个嵌套的坐标系,理解它们之间的转换是所有变换的基础。

1.1 常用空间

  • 模型空间 (Local/Object Space): 顶点相对于模型枢轴点 (Pivot) 的位置。
  • 世界空间 (World/Global Space): 相对于游戏世界原点 (0,0,0) 的位置。
  • 观察空间 (View/Camera Space): 相对于摄像机的位置。
  • 屏幕空间 (Screen Space): 像素坐标 (x, y),左下角为 (0,0)。
  • 视口空间 (Viewport Space): 归一化屏幕坐标 (0~1)。

1.2 变换矩阵 (Transformation Matrix)

一个物体从模型空间变换到世界空间,本质上是乘以一个 4×44 \times 4 矩阵 MLocalWorldM_{Local \to World} M=TRSM = T \cdot R \cdot S
  • 顺序至关重要: 先缩放 (SS),再旋转 (RR),最后平移 (TT)。
  • 矩阵乘法不满足交换律: RTTRR \cdot T \neq T \cdot R。如果顺序错了,物体会绕着世界原点旋转,而不是绕着自身旋转。

2. 旋转 (Rotation) —— 最大的坑

Unity 提供了三种方式来表示旋转,混用它们是 Bug 之源。

2.1 欧拉角 (Euler Angles) - transform.eulerAngles

  • 直观: (x, y, z) 分别代表绕 X, Y, Z 轴旋转的角度。
  • 优点: 人类易读,Inspector 面板里显示的就是这个。
  • 致命缺点: 万向节死锁 (Gimbal Lock)。当中间轴 (Y) 旋转 90 度时,X 轴和 Z 轴重合,失去一个自由度。
  • 最佳实践: 仅在 UI 显示或简单的初始化设置时使用。严禁在 Update 中对欧拉角进行累加计算(如 angles += speed * dt)。

2.2 四元数 (Quaternion) - transform.rotation

  • 原理: 复数扩展 (x,y,z,w)(x, y, z, w)
  • 优点: 无死锁,插值平滑 (Slerp),计算效率高。
  • 缺点: 人类无法直观理解数值含义。
  • 常用 API:
    • Quaternion.Identity: 无旋转。
    • Quaternion.Euler(x, y, z): 欧拉角 -> 四元数。
    • Quaternion.LookRotation(forward, up): 创建一个朝向 forward 的旋转。
    • Quaternion.Angle(q1, q2): 计算两个旋转间的夹角。

2.3 矩阵/向量法 (Vector Math)

  • forward/up/right: 直接操作轴向量。
  • 应用: transform.forward 本质上是 rotation * Vector3.forward

3. 空间变换 API 详解

3.1 点、向量与方向的区别

  • Point (点): 受位置、旋转、缩放影响。
    • TransformPoint(): Local -> World
    • InverseTransformPoint(): World -> Local
  • Direction (方向): 不受位置 (Translation) 影响,受旋转影响。通常用于法线、速度方向。
    • TransformDirection(): Local -> World
    • InverseTransformDirection(): World -> Local
  • Vector (向量): 受旋转和缩放影响,不受位置影响。
    • TransformVector(): Local -> World (带缩放)

3.3 特别篇:UI 坐标系转换 (The UI Coordinate Problem)

UI 系统 (RectTransform) 虽然继承自 Transform,但在坐标转换上有一个巨大的“断层”:渲染模式 (Render Mode)
  1. Screen Space - Overlay:
    • UI 直接绘制在屏幕最上层。
    • 没有世界坐标概念(或者说,世界坐标 = 屏幕像素坐标)。
    • position.x 就是屏幕上的像素 X。
    • 转换时不需要 Camera 参数 (传 null)。
  2. Screen Space - Camera / World Space:
    • UI 是 3D 世界中的实体板子,有确定的深度 (Z)。
    • 受透视 (Perspective) 影响:近大远小。
    • 转换时必须传入渲染该 Canvas 的 Camera,否则射线检测会偏离。
核心理论: 在处理 UI 交互(如鼠标点击、物体飞向 UI)时,永远不要试图直接“加减坐标”。必须寻找一个公共参考系——通常是屏幕空间 (Screen Space)
  • 3D 世界 -> 屏幕 <- UI 局部
  • UI A -> 屏幕 <- UI B

3.2 最佳实践案例

案例 A: 子弹发射位置

错误: bullet.position = transform.position + new Vector3(0, 0, 1); 问题: 只有当物体朝向世界 Z 轴且无缩放时才对。 正确: bullet.position = transform.TransformPoint(new Vector3(0, 0, 1)); 或者: bullet.position = transform.position + transform.forward * 1.0f;

案例 B: AI 相对坐标判断

判断 “敌人是否在我的右前方”。 方法: 将敌人坐标转换到我的局部空间。

案例 C: 相对方向的力 (Relative Force)

一个物体向前发射一个力(例如玩家冲刺,冲刺方向是角色面向的方向)。

案例 D: 旋转限制 (Rotation Constraint)

例如,摄像机绕着玩家旋转,但要保持摄像机 Y 轴始终指向世界 Y 轴(不倾斜)。

案例 E: 屏幕坐标到世界坐标 (Screen to World)

例如,点击屏幕发射射线或生成物体。

案例 F: 绕点旋转 (Rotate Around Point)

让一个物体(如卫星、僚机)绕着另一个点(如行星、玩家)旋转。

案例 G: 平滑 LookAt (Smooth LookAt)

让物体平滑地转向目标,而不是瞬时旋转。这对于摄像机跟随、炮塔转动等场景非常重要。

案例 H: 平滑移动 (Smooth Movement)

让物体平滑地移动到目标位置。

案例 I: 3D物体飞向UI (World Object to UI Fly Effect) - 进阶版

经典需求:怪物掉落金币(世界坐标),金币拾取后飞向 UI 上的金币栏(屏幕坐标)。 初级陷阱: 直接用 position 赋值,在不同分辨率或 UI 锚点设置下会偏移。 核心原理: 使用 RectTransformUtility 将屏幕坐标转换为局部 UI 坐标
总结: 解决 UI 坐标乱飞的终极法宝是 “屏幕坐标 (Screen Point)” 作为中转站,配合 RectTransformUtility.ScreenPointToLocalPointInRectangle
💡 深入学习 UI 数学: 关于 Anchors、Pivot、SizeDelta 的深层原理及更多 UI 适配技巧,请参阅专门文档: Unity RectTransform 深度解析 (The Math of UI)

4. 核心向量数学与几何直觉 (Vector Math Intuition)

在 Gameplay 编程中,理解向量的点乘和叉乘比记住公式更重要。它们是战斗逻辑(如视野、判定)的数学基石。

4.1 点积 (Dot Product) - Vector3.Dot(A, B)

  • 数学定义: ABcosθ|A||B|\cos\theta
  • 几何意义: 衡量两个向量的方向相似程度,或者向量 A 在向量 B 上的投影长度
  • 应用场景:
    1. 视野检测 (FOV): 判断敌人是否在玩家前方夹角内。
    2. 背刺判定 (Backstab): 判断攻击是否来自敌人背后。
      • 如果 Dot(enemy.forward, player.forward) > 0.8,说明两人朝向基本一致,是背后攻击。
    3. 光照计算: 漫反射计算中,光线方向与法线的点积决定亮度。

4.2 叉积 (Cross Product) - Vector3.Cross(A, B)

  • 数学定义: 生成一个同时垂直于 A 和 B 的新向量(法向量)。遵守右手定则。
  • 几何意义: 确定两个向量构成的平面及其法线
  • 应用场景:
    1. 左右判断: 判断敌人在我的左边还是右边。
    2. 构建坐标系: 已知 Forward 和 Up,求 Right。
      • Right = Cross(Up, Forward) (注意顺序影响方向)

5. 矩阵的“基向量”视角 (Basis Vectors)

不要把变换矩阵看作一堆枯燥的数字。4x4 矩阵的前三列,实际上就是该物体局部坐标轴在世界空间中的表示。 [RxUxFxTxRyUyFyTyRzUzFzTz0001]\begin{bmatrix} \color{red}{R_x} & \color{green}{U_x} & \color{blue}{F_x} & T_x \\ \color{red}{R_y} & \color{green}{U_y} & \color{blue}{F_y} & T_y \\ \color{red}{R_z} & \color{green}{U_z} & \color{blue}{F_z} & T_z \\ 0 & 0 & 0 & 1 \end{bmatrix}
  • 第一列 (Red): 物体的 transform.right (局部 X 轴)
  • 第二列 (Green): 物体的 transform.up (局部 Y 轴)
  • 第三列 (Blue): 物体的 transform.forward (局部 Z 轴)
  • 第四列: 物体的 transform.position (位移)
深刻理解: 旋转一个物体,本质上就是定义这三个基向量(Right, Up, Forward)指向哪里。

6. 物理与变换的冲突 (Physics vs Transform)

这是一个极易被忽视的理论陷阱。
  • 现象: 直接修改带 RigidbodyCollider 物体的 transform.position
  • 理论后果 (Teleportation):
    • 物理引擎认为物体是瞬移过去的,速度为 0。
    • 穿墙 (Tunneling): 这一帧在墙前,下一帧在墙后,中间没有检测到碰撞。
    • 破坏插值: 导致刚体运动卡顿或抖动。
  • 正确做法:
    • 瞬移: 使用 rigidbody.position = newPos (类似 transform 但通知物理引擎)。
    • 移动: 使用 rigidbody.MovePosition(newPos) (平滑移动,会与沿途物体碰撞)。
    • 施力: 使用 rigidbody.AddForce()

7. 层级关系 (Hierarchy) 与性能

7.1 肮脏标记 (Dirty Flag)

Unity 的 Transform 系统使用“肮脏标记”模式。
  • 当你修改父物体的 Transform 时,所有子物体并不会立即重新计算世界坐标。
  • 它们会被标记为 Dirty
  • 只有当你下次访问子物体的 .position.rotation 时,才会触发递归计算 (Recursion)。

7.2 性能陷阱

  • 深度层级: 层级越深,计算开销越大。
  • 频繁读写: 在一帧内反复读取 position 会强制重算。
    • Bad: for(i) { x += transform.position.x; }
    • Good: Vector3 pos = transform.position; for(i) { x += pos.x; }
  • 缩放 (Scale): 尽量保持 Scale 为 (1,1,1)。非统一缩放 (Non-uniform scale) 会导致物理引擎计算复杂化,并破坏批处理 (Batching)。

7.3 transform.hasChanged

  • 用途: 极其高效地检查物体自上一帧以来是否移动过。
  • 场景: 只有当物体移动时,才更新空间索引 (Grid/QuadTree)。

8. 数学变换速查表 (Cheat Sheet)