学Unity的猫之Transform的魔力(七)
7.1 超过光速的移动
我:“皮皮,你知道真空光速是多少吗?”
皮皮:“你以为我是百科全书呀?不过我知道,目前所知的物理定律里,光速是无法超越的,连我们猫族也无法超越这个速度。”
我:“真空中的光速是299792458
米/秒,大约300000
米/毫秒。在Unity
中,你信不信我可以超越光速?”
皮皮:“真的假的?”
我打开Unity
,说:“真的,不信我可以证明给你看。”
首先,创建一个Cube
。
接着,创建一个脚本
TransformTest.cs
。
代码如下:
using System.Diagnostics;
using UnityEngine;
public class TransformTest : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Stopwatch sw = new Stopwatch();
sw.Start();
SetPos();
sw.Stop();
UnityEngine.Debug.Log("总耗时: " + sw.ElapsedMilliseconds);
}
}
void SetPos()
{
Transform selfTransform = transform;
for (int i = 0; i < 100000; ++i)
{
selfTransform.position = Vector3.one * i;
}
}
}
注:上面代码中,我用到了
Stopwatch
这个类,用它可以精确测量函数的运行时间。
将TransformTest
脚本挂到Cube
上。
运行Unity
,按下空白键,可以在console
窗口中看到日志输出。
由此可得,执行10000
次的position
操作,耗时8
毫秒,折算一下,1
毫秒可以执行position
操作1250
次,而真空光速是约300000
米/毫秒,300000
除以1250
等于240
,也就是说,只要一次position
操作的距离超过240
即可超过光速啦,如下:
transform.position = Vector3.zero;
transform.position = new Vector3(241, 0, 0);
皮皮:“真是不可思议呀,电脑的运行速度这么快。”
我:“不过这是虚拟世界里的速度,真实的物理世界,光速还是无法超越的,另外需要注意一点,不同计算机的CPU
主频不同,运行速度不同,比如上面执行10000
次的position
操作耗时8
毫秒,可能别的性能差一点的电脑就要耗时14
毫秒,那么最后算出值就不同了。”
皮皮:“刚刚看代码,里面设置position
坐标要通过Transform
对象,这个Transform
是什么呀?”
7.2 初识Transform类
我:“Transform
是Unity
中非常非常重要的一个类,所有的GameObject
对象都有一个Transform
组件,你创建一个空物体的时候,就会看到它就已经自带了一个Transform
组件”
我指着Inspector
视图中的Transform
组件,“你猜猜这个Transform
组件到底是用来干嘛的?”
皮皮:“我看出来了,它是用来设置坐标、旋转角度和缩放的。”
我:“没错,一个GameObject
必然会有一个坐标、旋转角度和缩放,所以Transform
组件是必须的。不过呢,后面Unity
官方引入了ECS
框架,在ECS
框架中,一个实体Entity
可以没有Transform
。现在你只需记住,Transform
组件可以用来设置游戏对象的坐标、旋转角度和缩放。” 更深入一些,我们看一下Transform
常用的属性和函数吧。
7.3 Transform的属性
| 属性 | 数据类型 | 描述 |
| - | - | - |
| position | Vector3 | 在世界空间中的坐标 |
| localPosition | Vector3 | 相对于父节点的局部坐标,如果没有父节点,则localPosition等于position |
| eulerAngles | Vector3 | 世界坐标系中的旋转(欧拉角) |
| localEulerAngles | Vector3 | 相对于父节点的局部旋转(欧拉角),如果没有父节点,则localEulerAngles等于eulerAngles |
| rotation | Quaternion | 世界坐标系中的旋转(四元数) |
| localRotation | Quaternion | 相对于父节点的旋转(四元数),如果没有父节点,则localRotation等于rotation |
| right | Vector3 | 局部坐标系的x轴方向向量 |
| up | Vector3 | 局部坐标系的y轴方向向量 |
| forward | Vector3 | 局部坐标系的z轴方向向量 |
| localScale | Vector3 | 相对于父节点的缩放比例 |
| parent | Transform | 父节点的Transform组件 |
| root | Transform | 根节点的Transform组件 |
| childCount | int | 子节点数量 |
| lossyScale | Vector3 | 全局缩放比例(只读) |
| worldToLocalMatrix | Matrix4x4 | 矩阵变换的点从世界坐标转为自身坐标(只读) |
| localToWorldMatrix | Matrix4x4 | 矩阵变换的点从自身坐标转为世界坐标(只读) |
皮皮:“这么多我该怎么记呀?”
我:“多写多练,用多了你就记住啦。我给你示范几个例子吧。”
皮皮:“微信搜索公众号 [爱上游戏开发],回复 “资料”,免费领取 200G 学习资料!”
7.3.1 设置坐标
设置世界坐标:position
transform.position = new Vector3(100, 0, 0);
设置局部坐标:localPosition
transform.localPosition = new Vector3(100, 0, 0);12
皮皮:“什么是世界坐标和局部坐标呀?”
我:“世界坐标就是以世界坐标系为参考的坐标,局部坐标(或叫本地坐标)就是以本地坐标系为参考的坐标。三维坐标系由x、y、z
个轴组成,我们一般分为左手坐标系和右手坐标系,老皮,你看看Unity
采用的是左手坐标系还是右手坐标系呢?”
皮皮举起了它的爪子,分不清左右。
我:“哈哈哈,我直接告诉你吧,Unity
采用的是左手坐标系。不管是世界坐标系还是局部坐标系,它们都是左手坐标系。通过坐标系,我们就可以使用坐标值表示任意一个位置。世界有一个坐标系,游戏对象本身也有一个坐标系,游戏对象可以嵌套形成父子节点关系,当游戏对象有父节点的时候,相对父节点的坐标就是局部坐标localPosition
,它相对世界坐标系的坐标就是世界坐标position
。当游戏对象没有父节点的时候,或者可以理解为它此时的父节点就是世界,此时局部坐标localPosition
就会等于世界坐标position
。”
皮皮指着Inspector
视图问:“那Inspector
视图中的Position
到底是世界坐标还是局部坐标呀?”
我:“Inspector
视图中的Position
显示的是局部坐标,如果游戏对象没有父节点,那么局部坐标就会等于世界坐标,此时Position
显示的既是局部坐标也是世界坐标。”
皮皮:“那Rotation
和Scale
也是同理吗?”
我:“是的,你已经学会触类旁通了呀,不错不错。”
7.3.2 设置旋转角度
设置旋转角度有两种方式,一种是欧拉角,一种是四元数。设置局部欧拉角旋转:localEulerAngles
transform.localEulerAngles = new Vector3(100, 0, 0);
设置局部四元数旋转:localRotation
transform.localRotation = Quaternion.Euler(new Vector3(100, 0, 0));
皮皮:“为什么要弄两套旋转方法呢?”
我:“这里就要理解欧拉角的原理以及它的问题,它的主要问题会引发万向锁问题,还有,它做差值运算不合理。为了解决这些问题,人们发明了四元数来表示旋转。后面我再单独讲讲这部分的内容,这里你只需看懂如何通过代码设置旋转角度即可。”
7.3.3 设置缩放
设置缩放,为原始的2
倍
transform.localScale = Vector3.one * 2;
皮皮:“我看到缩放还有一个叫lossyScale
,它与localScale
有什么关系呢?”
我:“localScale
是本地坐标系中的缩放,lossyScale
是世界坐标系中的缩放,类似于localPosition
与position
的关系。不过lossyScale
是只读的,我们不能对它进行赋值,实际项目中lossyScale
比较少用到呢。”
7.3.4 设置朝向
设置物体的x
轴与向量(1, 1, 0)
朝向一致。
transform.right = new Vector3(1, 1, 0);
设置物体的y
轴与世界坐标系的y
轴朝向一致。
transform.up = Vector3.up;
设置物体的z
轴与主摄像机的z
轴反方向。
transform.forward = -Camera.main.transform.forward;
7.3.5 设置父节点
GameObject parentGo = new GameObject("parentGo");
GameObject childGo = new GameObject("childGo");
childGo.transform.parent = parentGo.transform;
注意,parent
是一个Transform
,不是GameObject
哦。
7.4 Transform的函数
| 函数 | 说明 |
| - | - |
| Translate | 用来移动物体的函数 |
| Rotate | 用来旋转物体的函数 |
| RotateAround | 让物体以某一点为轴心成圆周运动 |
| LookAt | 让物体的z轴看向目标物体 |
| TransformDirection | 从本地坐标到世界坐标变换方向 |
| InverseTransformDirection | 从世界坐标到本地坐标变换方向,与TransformDirection相反 |
| TransformPoint | 将基于当前游戏对象的局部坐标转化为基于世界坐标系的坐标 |
| InverseTransformPoint | 将基于世界坐标系的坐标转换为基于当前对象的局部坐标 |
| DetachChildren | 分离子物体,所有子物体解除父子关系 |
| Find | 通过名字查找子物体并返回它 |
| SetParent | 设置父节点 |
| IsChildOf | 判断自身是否是某个Transform的子节点 |
皮皮:“按照惯例,show me code
。”
7.4.1 移动物体
函数原型:
public void Translate(float x, float y, float z);
public void Translate(float x, float y, float z, [DefaultValue("Space.Self")] Space relativeTo);
public void Translate(Vector3 translation);
public void Translate(Vector3 translation, [DefaultValue("Space.Self")] Space relativeTo);
public void Translate(float x, float y, float z, Transform relativeTo);
public void Translate(Vector3 translation, Transform relativeTo);
示例:向本地坐标系的x
轴正方向移动1
米
m_selfTrans.Translate(1, 0, 0, Space.Self);1
注:Unity中,坐标轴上的1个单位长度的距离表示真实世界中的1米距离
皮皮:“举手提问,这个Translate
移动与直接设置localPosition
坐标有什么区别呢?”
我:“设置localPosition
是直接设置最终的目标位置,而Translate
方法相当于是一个增量操作,是基于当前坐标进行一个增量移动。”
皮皮:“提问,参数Space relativeTo
是什么意思呀?”
我:“这个是参考系,Space
是一个枚举,很好理解,Space.World
是以世界坐标系为参考,Space.Self
是以本地坐标系为参考。”
public enum Space
{
World = 0,
Self = 1
}
我:“皮皮,现在你猜猜,下面这个重载函数的第二个参数Transform relativeTo
是表示什么?”
public void Translate(Vector3 translation, Transform relativeTo);
皮皮:“不用猜,参考系,以它的坐标系为参考。”
我:“厉害哦,变通能力越来越强了。”
皮皮:“微信搜索公众号 [爱上游戏开发],回复 “资料”,免费领取 200G 学习资料!”
7.4.2 旋转物体
函数原型:
public void Rotate(float xAngle, float yAngle, float zAngle);
public void Rotate(Vector3 eulers, [DefaultValue("Space.Self")] Space relativeTo);
public void Rotate(Vector3 eulers);
public void Rotate(float xAngle, float yAngle, float zAngle, [DefaultValue("Space.Self")] Space relativeTo);
public void Rotate(Vector3 axis, float angle, [DefaultValue("Space.Self")] Space relativeTo);
public void Rotate(Vector3 axis, float angle);
示例:围绕本地坐标系的y
轴顺时针旋转1
度
transform.Rotate(0, 1, 0);
我:“老皮,考考你,这个Rotate
方法与直接设置localEulerAngles
有什么区别?”
皮皮:“你这么问,我就知道了,Rotate
方法是增量操作,上面讲Translate
的时候说过。”
我:“看来我不用多解释啦,聪明聪明。”
皮皮:“我看到还有一个RotateAround
方法,它与Rotate
有什么区别呢?” 函数原型:
public void RotateAround(Vector3 point, Vector3 axis, float angle);
public void RotateAround(Vector3 axis, float angle);
我:“你知道地球自转和公转吗?”
皮皮:“知道呀,我们古老的喵星也有自转和公转,在遥远的拉姆达星系,喵星围绕着巨大的鲁特恒星旋转,在大约4000千万年前… … ” 皮皮突然捂住自己的嘴,“糟了,泄露机密了。”
我:“哈哈哈,不要怕,我不会出卖你们的,喵星人早已经是人类的好朋友了,而且你也没说你们星系的具体坐标呢。”
皮皮:“打住,回归正题!”
我:“嘛,这个RotateAround
就是类似公转的效果。”RotateAround
也是一个增量操作,参数point
是围绕的坐标点,参数axis
是旋转的轴,angle
是旋转的角度。例:
using UnityEngine;
public class TransformTest : MonoBehaviour
{
private Transform m_selfTrans;
void Awake()
{
m_selfTrans = transform;
}
void Update()
{
m_selfTrans.RotateAround(Vector3.zero, Vector3.up, 1);
}
}
运行效果:
7.4.3 方向转换计算
本地方向向量 转 世界方向向量 函数原型:
public Vector3 TransformDirection(float x, float y, float z);
public Vector3 TransformDirection(Vector3 direction);
示例:计算本地坐标下的forward
向量(即本地坐标系的z
轴的正方向向量)在世界坐标系下的向量
Vector3 worldObjForward = transform.TransformDirection(transform.forward);
世界方向向量 转 本地方向向量 函数原型:
public Vector3 InverseTransformDirection(Vector3 direction);
public Vector3 InverseTransformDirection(float x, float y, float z);
示例:计算世界坐标系下的forward
向量在本地坐标系下的向量
Vector3 localForward = transform.InverseTransformDirection(Vector3.forward);
7.4.4 坐标转换计算
本地坐标 转 世界坐标 函数原型:
public Vector3 TransformPoint(float x, float y, float z);
public Vector3 TransformPoint(Vector3 position);
示例:计算局部坐标(10, 0, 0)
在世界坐标系下的坐标
Vector3 worldPos = transform.TransformPoint(10, 0, 0);
世界坐标 转 本地坐标 函数原型:
public Vector3 InverseTransformPoint(float x, float y, float z);
public Vector3 InverseTransformPoint(Vector3 position);
示例:计算世界坐标系下的(10, 0, 0)
在本地坐标系下的坐标
Vector3 localPos = transform.InverseTransformPoint(10, 0, 0);
7.4.5 查找子物体
函数原型:
public Transform Find(string n);
示例:创建节点,节点结构如下
root
节点挂TransformTest
脚本,脚本代码如下
using UnityEngine;
public class TransformTest : MonoBehaviour
{
Transform m_selfTrans;
void Awake()
{
m_selfTrans = transform;
}
void Start()
{
var a = m_selfTrans.Find("a");
var c = m_selfTrans.Find("a/b/c");
var d = m_selfTrans.Find("d");
}
}
7.4.6 判断是否是子节点
函数原型:
public bool IsChildOf([NotNull] Transform parent);
示例:
GameObject a = new GameObject("a");
GameObject b = new GameObject("b");
b.transform.parent = a.transform;
if(b.transform.IsChildOf(a.transform))
{
Debug.Log("a 是 b 的子节点");
}
标题:学Unity的猫之Transform的魔力(七)
作者:shirlnGame
地址:https://mmzsblog.cn/articles/2021/01/06/1609938866509.html
-----------------------------
如未加特殊说明,此网站文章均为原创。
网站转载须在文章起始位置标注作者及原文连接,否则保留追究法律责任的权利。
公众号转载请联系网站首页的微信号申请白名单!
