该系列笔记基于Unity3D 5.x的版本学习,部分API使用和4.x不一致。

目前在Unity3D中,除了新的UGUI部分控件外,所有的物体(GameObject)都必带有Transform组件,而Transform组件主要是控制物体在3D空间中的位置、旋转以及缩放。

学习和掌握物体的变换是Unity3D开发者必备的基础知识。

基础变换

最基础的变换就是通过脚本直接对物体的位置旋转缩放等进行变换。

匀速移动

我们下面实现一个匀速移动物体的效果,我们在场景中添加一个Cube物体,把下面的脚本绑定到摄像机上并把Cube拖拽赋予transfrom属性。



1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7 
 8     void Start()
 9     {
10     }
11     
12     void Update()
13     {
14         myTransform.position = new Vector3(myTransform.position.x, myTransform.position.y + 1.0f * Time.deltaTime, myTransform.position.z);
15     }
16 }



运行游戏,我们会发现Cube会匀速上升。我们回到编辑场景,对Cube进行任意的旋转后运行游戏该Cube仍然是向上上升,这是因为位置和旋转是相互独立的,我们直接操作位置的话程序是不会理会旋转属性的,更换为localPosition效果也是一致的。

根据物体方向匀速移动

我们发现如果使用上面的方法来按照物体面向的方向移动物体是不容易的,我们需要根据物体的朝向计算出x、y、z这3个分量的数值再应用回物体中才行,这需要扎实的3维运算功底,不过好在Unity已经给我们提供了大量的属性及方法,方便我们直接调用来达到我们需要的效果。

本地坐标系变量

  • transform.right:物体本地坐标的x轴正方向朝向,1米的单位。
  • transform.up:物体本地坐标的y轴正方向朝向,1米的单位。
  • transform.forward:物体本地坐标的z轴正方向朝向,1米的单位。

由于我们知道了物体本地坐标的信息,所以可以方便的通过这个来按照物体的朝向移动物体了,比如,下面的代码会朝着物体的y轴正方向每秒1米的速度匀速移动:



1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7 
 8     void Start()
 9     {
10     }
11     
12     void Update()
13     {
14         Vector3 pos = myTransform.position;
15         pos.x += myTransform.up.x * 1.0f * Time.deltaTime;
16         pos.y += myTransform.up.y * 1.0f * Time.deltaTime;
17         pos.z += myTransform.up.z * 1.0f * Time.deltaTime;
18         myTransform.position = pos;
19     }
20 }



坐标系转换

由于坐标系存在本地坐标系和世界坐标系两种,那么就需要有方法可以对这两种坐标系进行转换。

  • transform.localToWorldMatrix:本地坐标转世界坐标的矩阵信息。
  • transform.worldToLocalMatrix:世界坐标转本地坐标的矩阵信息。
  • transform.TransformDirection:将方向从本地坐标转换为世界坐标,不受缩放影响。
  • transform.InverseTransformDirection:将方向从世界坐标转换为本地坐标,不受缩放影响。
  • transform.TransformPoint:将位置从本地坐标转换为世界坐标,受缩放影响。
  • transform.InverseTransformPoint:将位置从世界坐标转换为本地坐标,受缩放影响。
  • transform.TransformVector:将坐标点从本地坐标转换为世界坐标,不受位置影响但受缩放影响。
  • transform.InverseTransformVector:将坐标点从世界坐标转换为本地坐标,不受位置影响但受缩放影响。

TransformPoint和TransformVector的区别

下面我们看看这两个方法的区别,首先,我们添加一个空物体到舞台并设置该物体的坐标为(1,1,1),然后把Cube对象拖入该空物体成为其子项设定其坐标为(2,2,2),修改脚本如下:



1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7 
 8     void Start()
 9     {
10         Vector3 pos = myTransform.TransformPoint(new Vector3(1, 1, 1));
11         Debug.Log(pos);
12         //(4.0, 4.0, 4.0)
13 
14         Vector3 vec = myTransform.TransformVector(new Vector3(1, 1, 1));
15         Debug.Log(vec);
16         //(1.0, 1.0, 1.0)
17     }
18     
19     void Update()
20     {
21     }
22 }



接下来我们把空物体的尺寸缩小一半看看结果会如何:

unity动画移动位置 unity移动场景_世界坐标

结论

TransformPoint转变会受物体的位置和缩放影响转换,而TransformVector仅受物体的缩放影响转换。

Demo01

这里做了一个示例,具体的功能是按下指定的键抓取到场景中的小盒子,使其始终位于屏幕前方,按下另一个键将这个小盒子抛出。

下面我们看核心的实现。



1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform cube;
 7 
 8     void Start()
 9     {
10     }
11     
12     void Update()
13     {
14         //抓取小盒子
15         if (Input.GetKey(KeyCode.Q))
16         {
17             //设定小盒子的位置到屏幕前方
18             cube.transform.position = transform.TransformPoint(new Vector3(0, 0, 2));
19             //将小盒子设定为读取对象的子对象, 保证跟随运动
20             cube.transform.SetParent(transform);
21             //去掉物理交互
22             cube.GetComponent<Rigidbody>().isKinematic = true;
23         }
24         //扔出小盒子
25         if (Input.GetKey(KeyCode.E))
26         {
27             if (cube.transform.parent == transform)
28             {
29                 //使用掉物理交互
30                 cube.GetComponent<Rigidbody>().isKinematic = false;
31                 //解除所有子物件的绑定关系
32                 transform.DetachChildren();
33                 //获取方向
34                 Vector3 cameraDirect = transform.TransformDirection(0, 0, 5);
35                 //添加缓冲的力
36                 cube.GetComponent<Rigidbody>().AddForce(cameraDirect, ForceMode.Impulse);
37             }
38         }
39     }
40 }



我们先将摄像机前的一个点转换为世界坐标赋予给小盒子的世界坐标使其位于摄像机之前,抛出时把摄像机向前方向的一个向量转换为世界方向赋予小盒子抛出。

位移

Unity3D里提供了方便控制物体位移的属性及方法。

本地和世界坐标

transform.position:设置和获取物件的世界坐标。

transform.localPosition:设置和获取物件的本地坐标,相对于父级的坐标。

注意,在Inspector面板中的Transform里显示的是本地坐标。

Translate

Transform的Translate方法可以更加方便的对物体的位移进行操作,该方法有四个重载:



1 public function Translate(translation: Vector3, relativeTo: Space = Space.Self): void;
2 public function Translate(x: float, y: float, z: float, relativeTo: Space = Space.Self): void;



相对于本地坐标系或世界坐标系对物体进行位移操作。



1 public function Translate(translation: Vector3, relativeTo: Transform): void;
2 public function Translate(x: float, y: float, z: float, relativeTo: Transform): void;



相对于指定物体的坐标进行位移操作。

注意:如果是相对于本地坐标系,则如果向上移动就是朝向物体本身的上方移动,如果是相对于世界坐标系则是向世界的上方向移动,如果是相对于其他物体则是向这指定的物体的上方向移动。

AnimationCurve

AnimationCurve可以用来定义自定义的动画轨迹,我们通过在脚本中声明一个该类型的对象,就可以在编辑器窗口对其进行编辑,然后使我们的物体按照编辑的轨迹进行移动等操作。

比如我们想要得到一个物体在X轴匀速移动,Y轴进行上下循环移动的时候,可以使用下面的脚本:



1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class Demo02Script : MonoBehaviour
 5 {
 6     public AnimationCurve myAnimationCurve;
 7 
 8     public Transform myTransform;
 9 
10     void Start()
11     {
12     }
13     
14     void Update()
15     {
16         myTransform.position = new Vector3(
17             myTransform.position.x + 1 * Time.deltaTime,
18             myAnimationCurve.Evaluate(Time.time * 0.5f) * 2,
19             myTransform.position.z);
20     }
21 }



编辑器编辑的曲线如下:

unity动画移动位置 unity移动场景_unity动画移动位置_02

Demo02

在游戏中都会有一个最基本的需求,就是移动到指定的点,下面我们来实现一下这个基本的功能,脚本如下:



1 using System;
 2 using UnityEngine;
 3 using System.Collections;
 4 
 5 public class Demo02Script : MonoBehaviour
 6 {
 7     public Transform myTransform;
 8     public Transform myTarget;
 9 
10     private bool _isArrived = true;
11     private Vector3 _origin;
12     private Vector3 _target;
13     private float _speed;
14     private Action _onArrived;
15     private float _allTime;
16     private float _time;
17 
18     void Start()
19     {
20         MoveTo(myTarget.position, 1, () => Debug.Log("I am arrived!"));
21     }
22     
23     void Update()
24     {
25         if (!_isArrived)
26         {
27             _time += Time.deltaTime;
28             //判断是否抵达终点
29             if (_time >= _allTime)
30             {
31                 //校正位置
32                 myTransform.position = _target;
33                 //标记到达和调用回调
34                 _isArrived = true;
35                 if (_onArrived != null)
36                 {
37                     _onArrived();
38                 }
39             }
40             else
41             {
42                 //这里使用Lerp方法进行差值运算也可以得到相同的效果, 但是我们作为学习还是自己实现
43                 //myTransform.position = Vector3.Lerp(_origin, _target, _time / _allTime);
44                 
45                 //获取方向的单位向量
46                 Vector3 dirction = _target - _origin;
47                 dirction.Normalize();
48                 //朝方向运动
49                 myTransform.Translate(dirction * Time.deltaTime);
50             }
51         }
52     }
53 
54     /// <summary>
55     /// 移动到指定点.
56     /// </summary>
57     /// <param name="targetPosition">目标点.</param>
58     /// <param name="speed">移动速度, 米/秒.</param>
59     /// <param name="onArrived">到达后调用的方法.</param>
60     private void MoveTo(Vector3 targetPosition, float speed, Action onArrived)
61     {
62         _isArrived = false;
63         _origin = myTransform.position;
64         _target = targetPosition;
65         _speed = speed;
66         _onArrived = onArrived;
67 
68         //计算总共需要花费的时间
69         _allTime = Vector3.Distance(myTransform.position, _target) / _speed;
70         //重置使用的时间
71         _time = 0;
72     }
73 }



运行后小盒子会想指定的物体进行匀速移动,到达后会输出“I am arrived!”的字符串。

旋转之欧拉角

欧拉角是由3个轴的旋转角度组成的旋转数据,比如我们在Inspector界面的Transform中看到的就是物体本地坐标系的欧拉角:

unity动画移动位置 unity移动场景_世界坐标_03

欧拉角每个轴数字都在0-360之间,表示其旋转的角度。

Rotate

官方提供的旋转方法,其一共有三个重载方法:



1 public function Rotate(eulerAngles: Vector3, relativeTo: Space = Space.Self): void;
2 public function Rotate(xAngle: float, yAngle: float, zAngle: float, relativeTo: Space = Space.Self): void;



指定在本地坐标系或世界坐标系下旋转到指定的角度。



public function Rotate(axis: Vector3, angle: float, relativeTo: Space = Space.Self): void;



指定在本地坐标系或世界坐标系下基于轴axis进行旋转,旋转到angle角度。

RotateAround

我们先看看其参数:



public function RotateAround(point: Vector3, axis: Vector3, angle: float): void;



表示我们的物体围绕指定的点point在轴axis下旋转angle的角度。

LookAt

可以使物体面向指定的点,我们看看其参数:



1 public void LookAt(Transform target, Vector3 worldUp = Vector3.up);
2 public void LookAt(Vector3 worldPosition, Vector3 worldUp = Vector3.up);



即使我们的物体面向指定的物体或点。

旋转之四元数

欧拉角理解和使用都相当的方便,但是在实际进行旋转时存在万向锁的问题,所以引入了比较抽象的四元数的概念,当然我们在Unity中只要直接使用即可,是非常方便的。

这里提供一个视频,可以让大家直观的了解什么是万向锁:http://v.youku.com/v_show/id_XNzkyOTIyMTI=.html

Quaternion

在Transform中,eulerAngles属性是使用欧拉角来表示旋转,而rotation属性则是使用四元数来表示旋转。

四元数提供了许多的静态方法来使我们完成特定需求的效果,点击这里可查看帮助。

Demo03

如果我们想要实现一个效果,物体匀速旋转到指定角度时,使用欧拉角对每个轴进行变换是相当复杂的,同时如果两个轴重合了就会出现万向锁的问题,无法解决,而使用四元数则可以避免这些问题,下面是实现的脚本:



1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class Demo03Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7     public Transform myTarget;
 8 
 9     void Start()
10     {
11     }
12     
13     void Update()
14     {
15         RotateToTarget();
16     }
17 
18     private void RotateToTarget()
19     {
20         //获取目标方向的单位向量
21         Vector3 dicetion = (myTarget.position - myTransform.position).normalized;
22         //获取目标方向的四元数对象
23         Quaternion targetDicetion = Quaternion.LookRotation(dicetion);
24         //按照每秒 45 度的速度旋转面向目标对象
25         myTransform.rotation = Quaternion.RotateTowards(myTransform.rotation, targetDicetion, 45 * Time.deltaTime);
26     }
27 }



这样我们就可以使我们的物体匀速的转向指定的目标对象了。

缩放与位置关系

缩放

缩放比较简单,没有提供更多的方法。

  • Transform.lossyScale:只读,获取本物体相对于世界坐标的缩放大小。
  • Transform.localScale:设置或获取本物体相对于父级IDE缩放大小。

位置关系

在Unity3D中,所有3D对象是按照树形结构进行组合的,而操作物体之间的位置关系的所有API都存放在Transform对象中,下面我们看看常用的属性及方法。

属性

  • Transform.parent:设置和获取父级对象。
  • Transform.root:获取层次最高的对象。
  • Transform.childCount:获取子级对象的数量。

方法

  • Transform.Find:根据名字寻找子项。
  • Transform.IsChildOf:判断是否为指定Transform对象的子项。
  • Transform.DetachChildren:解除所有子项。
  • Transform.GetChild:根据索引获取子项。
  • Transform.GetSiblingIndex:获取同一级别的物体的索引。
  • Transform.SetAsFirstSibling:设置为同一级别的物体为第一个索引。
  • Transform.SetAsLastSibling:设置为同一级别的物体为最后一个索引。
  • Transform.SetSiblingIndex:设置同一级别的物体的索引。

工程文件下载

http://pan.baidu.com/s/1sjQJ5j3