《学Unity的猫》——第七章:Transform的魔力,超越光速的移动

本文介绍了Unity中的Transform类,包括如何实现超过光速的移动,详细讲解了Transform的属性和函数,如设置坐标、旋转、缩放、朝向和父节点等,并通过实例演示了移动和旋转物体的方法。文章还探讨了世界坐标与局部坐标的区别,以及Transform在游戏对象定位和变换中的重要作用。
摘要由CSDN通过智能技术生成

简介:我是一名Unity游戏开发工程师,皮皮是我养的猫,会讲人话,它接到了喵星的特殊任务:学习编程,学习Unity游戏开发。
于是,发生了一系列有趣的故事。
在这里插入图片描述

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测量运行时间
            Stopwatch sw = new Stopwatch();
            // 时间测量开始
            sw.Start();
            // 执行设置坐标
            SetPos();
            // 时间测量结束
            sw.Stop();
			// 输出日志
            UnityEngine.Debug.Log("总耗时: " + sw.ElapsedMilliseconds);
        }
    }

    /// <summary>
    /// 设置坐标
    /// </summary>
    void SetPos()
    {
        // 临时缓存transform对象
        Transform selfTransform = transform;
        // 循环执行10000次,看总耗时,在计算单次的运行时间
        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即可超过光速啦,如下:

// 设置初始坐标为Vector3.zero,即(0, 0, 0)
transform.position = Vector3.zero;
//从坐标(0, 0, 0)移动到(241, 0, 0),根据上面的计算,这个移动已经超过光速了
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的属性
属性数据类型描述
positionVector3在世界空间中的坐标
localPositionVector3相对于父节点的局部坐标,如果没有父节点,则localPosition等于position
eulerAnglesVector3世界坐标系中的旋转(欧拉角)
localEulerAnglesVector3相对于父节点的局部旋转(欧拉角),如果没有父节点,则localEulerAngles等于eulerAngles
rotationQuaternion世界坐标系中的旋转(四元数)
localRotationQuaternion相对于父节点的旋转(四元数),如果没有父节点,则localRotation等于rotation
rightVector3局部坐标系的x轴方向向量
upVector3局部坐标系的y轴方向向量
forwardVector3局部坐标系的z轴方向向量
localScaleVector3相对于父节点的缩放比例
parentTransform父节点的Transform组件
rootTransform根节点的Transform组件
childCountint子节点数量
lossyScaleVector3全局缩放比例(只读)
worldToLocalMatrixMatrix4x4矩阵变换的点从世界坐标转为自身坐标(只读)
localToWorldMatrixMatrix4x4矩阵变换的点从自身坐标转为世界坐标(只读)

皮皮:“这么多我该怎么记呀?”
我:“多写多练,用多了你就记住啦。我给你示范几个例子吧。”

7.3.1 设置坐标

设置世界坐标:position

// 设置世界坐标
transform.position = new Vector3(100, 0, 0);

设置局部坐标:localPosition

// 设置局部坐标
transform.localPosition = new Vector3(100, 0, 0);

皮皮:“什么是世界坐标和局部坐标呀?”
我:“世界坐标就是以世界坐标系为参考的坐标,局部坐标(或叫本地坐标)就是以本地坐标系为参考的坐标。三维坐标系由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 设置父节点
// 创建父游戏对象 parentGo
GameObject parentGo = new GameObject("parentGo");
// 创建子游戏对象 childGo
GameObject childGo = new GameObject("childGo");
// 设置 childGo 的父对象为 parentGo
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);

注: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);

皮皮:“不用猜,参考系,以它的坐标系为参考。”
我:“厉害哦,变通能力越来越强了。”

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()
    {
        // 缓存transform
        m_selfTrans = transform;
    }

    void Update()
    {
        // 围绕中心点Vector3.zero, 绕着轴Vector3.up旋转,旋转角度1度
        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()
    {
        // 缓存自身的transform
        m_selfTrans = transform;
    }

    void Start()
    {
        // 查找a节点
        var a = m_selfTrans.Find("a");
        // 查找c节点
        var c = m_selfTrans.Find("a/b/c");
        //查找e节点
        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;
// 设置父节点也可以用SetParent方法
// b.transform.SetParent(a.transform, false);

if(b.transform.IsChildOf(a.transform))
{
    Debug.Log("a 是 b 的子节点");
}

《学Unity的猫》——第八章:Unity预设文件,无限纸团喷射机

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值