欢迎来到我的博客小站。  交流请加我微信好友: studyjava。  也欢迎关注同名公众号:Java学习之道

学Unity的猫之Transform的魔力(七)

  |   0 评论   |   0 浏览

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类

我:“TransformUnity中非常非常重要的一个类,所有的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显示的既是局部坐标也是世界坐标。”

皮皮:“那RotationScale也是同理吗?”

我:“是的,你已经学会触类旁通了呀,不错不错。”

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是世界坐标系中的缩放,类似于localPositionposition的关系。不过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://www.mmzsblog.cn/articles/2021/01/06/1609938866509.html
-----------------------------
如未加特殊说明,此网站文章均为原创。
网站转载须在文章起始位置标注作者及原文连接,否则保留追究法律责任的权利。
公众号转载请联系网站首页的微信号申请白名单!

个人微信公众号 ↓↓↓                 

微信搜一搜爱上游戏开发