Unity3D之空間轉換學習筆記(一):場景物體變換


該系列筆記基於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 }

接下來我們把空物體的尺寸縮小一半看看結果會如何:

結論

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 }

編輯器編輯的曲線如下:

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中看到的就是物體本地坐標系的歐拉角:

歐拉角每個軸數字都在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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM