如果说前面几篇还偏“基础知识”,那从这一篇开始,矩阵就彻底进入 Unity 日常开发现场了。
我自己觉得,矩阵对客户端开发最先产生价值的地方,不是在 Shader,而是在你第一次认真排查父子层级、挂点和骨骼跟随的时候。
平时我们最常写的是:
transform.positiontransform.localPositiontransform.rotationtransform.localScale
这些字段看起来很直观,但真正把它们串起来的,是矩阵。
一、局部空间和世界空间的差别,本质上是参照系不同
这是所有问题的起点。
一个子物体的 localPosition = (0, 1, 0),意思不是“它在世界里 y=1”。
它的意思是:
相对于父节点的局部坐标系,它离父节点原点向上 1 个单位。
如果父节点旋转了、缩放了、平移了,这个子物体在世界里的最终位置就会一起变。
所以:
localPosition是局部描述position是世界描述
它们不是两个独立真相,只是同一个点在不同坐标系下的两种写法。
二、Transform 本质上就是一个 TRS 变换
Unity 里单个节点最核心的三组信息就是:
- Translation 位移
- Rotation 旋转
- Scale 缩放
把它们合起来,就是一个局部到父级空间的变换矩阵。
如果继续往上乘父节点、祖先节点的矩阵,就会得到最终的 localToWorldMatrix。
也就是说,一个物体的世界位置不是“单独算出来的”,而是整条父子层级矩阵连乘的结果。
三、为什么父节点一动,子节点世界坐标全变
因为子节点的局部坐标,不是直接挂在世界空间里,而是先挂在父节点坐标系里。
父节点的局部 x 轴、y 轴、z 轴一旦转了、缩了、挪了,子节点的解释方式也会跟着变。
这就是层级系统最核心的地方:
父节点定义了子节点的参考坐标系。
所以你看到的不是“子节点自己在乱动”,而是它所依附的坐标空间在变化。
四、TransformPoint 为什么这么常用
假设你有一个局部挂点:
1 | Vector3 localOffset = new Vector3(0.2f, 1.1f, 0f); |
你要得到这个挂点在世界里的实际位置,最稳的方式通常就是:
1 | Vector3 worldPos = transform.TransformPoint(localOffset); |
它本质上等价于:
用当前物体的局部到世界矩阵,把一个“点”乘到世界空间。
这也是为什么它会受平移、旋转、缩放共同影响。
五、TransformDirection 和 TransformVector 不要混用
这两个 API 很多人会随手用,但语义上还是有区别。
一般理解上:
TransformPoint处理位置点TransformDirection更强调方向TransformVector更强调长度也参与缩放
最常见的 bug 是:
你本来在处理“朝向”,却用成了点变换,结果把平移也算进去了。
或者你本来想保留纯方向,却无意间吃进了缩放影响。
所以一旦你在做射线、武器发射方向、特效朝向、骨骼挂点时出现偏差,先检查这里。
六、worldToLocalMatrix 是排查问题的利器
很多时候我们不是把局部点转世界,而是反过来:
我有一个世界坐标,想知道它相对于某个物体的局部坐标是多少。
例如:
- 判断敌人是否进入角色前方扇形区域
- 计算点击点在某个局部网格里的坐标
- 把世界里的命中点映射回模型局部空间
这个时候 InverseTransformPoint 或 worldToLocalMatrix 就很好用。
因为它做的正是逆变换。
例如做“目标是不是在角色前方”的判断时,我一般不会先上点乘,而是先转回局部空间:
1 | Vector3 localTarget = transform.InverseTransformPoint(target.position); |
这类写法的好处是语义很直接,后面要改成扇形范围、矩形攻击框或者棋盘局部坐标判断,也很容易往下扩。
七、一个父子层级的典型心智模型
可以把每个 Transform 理解成“在父坐标系里再搭一层新坐标系”。
比如:
- 角色是第一层
- 手臂骨骼是第二层
- 武器挂点是第三层
- 枪口火焰位置是第四层
最终枪口点位为什么能一直跟手臂、身体、朝向保持一致?
不是因为每一帧都在人工修正,而是因为这一连串矩阵关系天然保证了它们同步。
八、非等比缩放为什么在层级里很危险
如果父节点有非等比缩放,子节点的局部轴往往会变得不那么“干净”。
在工程里,这会引出很多连锁问题:
- 朝向判断怪怪的
- 圆形碰撞看起来被拉扁
- 特效跟随位置没错,但方向不对
- 法线或光照表现异常
所以很多团队会约束:
运行时骨架层、逻辑层尽量避免乱用非等比缩放。
因为这不是不能用,而是用了以后,你就必须更清楚矩阵里到底发生了什么。
九、Unity 中几个很值得背后的接口
我建议不要只背用法,而是连同语义一起记:
1 | transform.localToWorldMatrix |
这几组接口基本覆盖了大部分坐标转换场景。
十、一个很实用的调试建议
当你怀疑坐标不对时,不要只打印 position。
建议一起打印:
1 | Debug.Log(transform.localPosition); |
再配合 Scene 视图里的 Gizmos 画出局部轴方向,通常很快就能定位问题到底出在:
- 父节点层级
- 旋转顺序
- 缩放污染
- 局部/世界空间搞混
十一、总结
Transform 从表面上看只是几个字段,实际上它是 Unity 空间系统最核心的封装。
只要你把“每个节点都在定义一个局部坐标系”这个认知建立起来,很多层级、挂点、骨骼、朝向问题都会清楚很多。
下一篇继续往下走,专门讲 Matrix4x4.TRS 的顺序问题,以及项目里什么时候值得自己手组矩阵。