Unity3D之空間轉(zhuǎn)換學(xué)習(xí)筆記(一):場(chǎng)景物體變換

該系列筆記基于Unity3D 5.x的版本學(xué)習(xí),部分API使用和4.x不一致。

目前在Unity3D中,除了新的UGUI部分控件外,所有的物體(GameObject)都必帶有Transform組件,而Transform組件主要是控制物體在3D空間中的位置、旋轉(zhuǎn)以及縮放。

學(xué)習(xí)和掌握物體的變換是Unity3D開(kāi)發(fā)者必備的基礎(chǔ)知識(shí)。

基礎(chǔ)變換

最基礎(chǔ)的變換就是通過(guò)腳本直接對(duì)物體的位置旋轉(zhuǎn)縮放等進(jìn)行變換。

勻速移動(dòng)

我們下面實(shí)現(xiàn)一個(gè)勻速移動(dòng)物體的效果,我們?cè)趫?chǎng)景中添加一個(gè)Cube物體,把下面的腳本綁定到攝像機(jī)上并把Cube拖拽賦予transfrom屬性。

using UnityEngine;

using System.Collections;

public class Demo01Script : MonoBehaviour

{

????public Transform myTransform;

????void Start()

????{

????}

????void Update()

????{

????????myTransform.position = new Vector3(myTransform.position.x, myTransform.position.y + 1.0f * Time.deltaTime, myTransform.position.z);

????}

}


運(yùn)行游戲,我們會(huì)發(fā)現(xiàn)Cube會(huì)勻速上升。我們回到編輯場(chǎng)景,對(duì)Cube進(jìn)行任意的旋轉(zhuǎn)后運(yùn)行游戲該Cube仍然是向上上升,這是因?yàn)槲恢煤托D(zhuǎn)是相互獨(dú)立的,我們直接操作位置的話程序是不會(huì)理會(huì)旋轉(zhuǎn)屬性的,更換為localPosition效果也是一致的。

根據(jù)物體方向勻速移動(dòng)

我們發(fā)現(xiàn)如果使用上面的方法來(lái)按照物體面向的方向移動(dòng)物體是不容易的,我們需要根據(jù)物體的朝向計(jì)算出x、y、z這3個(gè)分量的數(shù)值再應(yīng)用回物體中才行,這需要扎實(shí)的3維運(yùn)算功底,不過(guò)好在Unity已經(jīng)給我們提供了大量的屬性及方法,方便我們直接調(diào)用來(lái)達(dá)到我們需要的效果。

本地坐標(biāo)系變量

transform.right:物體本地坐標(biāo)的x軸正方向朝向,1米的單位。

transform.up:物體本地坐標(biāo)的y軸正方向朝向,1米的單位。

transform.forward:物體本地坐標(biāo)的z軸正方向朝向,1米的單位。

由于我們知道了物體本地坐標(biāo)的信息,所以可以方便的通過(guò)這個(gè)來(lái)按照物體的朝向移動(dòng)物體了,比如,下面的代碼會(huì)朝著物體的y軸正方向每秒1米的速度勻速移動(dòng):

using UnityEngine;

using System.Collections;

public class Demo01Script : MonoBehaviour

{

????public Transform myTransform;

????void Start()

????{

????}

????void Update()

????{

????????Vector3 pos = myTransform.position;

????????pos.x += myTransform.up.x * 1.0f * Time.deltaTime;

????????pos.y += myTransform.up.y * 1.0f * Time.deltaTime;

????????pos.z += myTransform.up.z * 1.0f * Time.deltaTime;

????????myTransform.position = pos;

????}

}

坐標(biāo)系轉(zhuǎn)換

由于坐標(biāo)系存在本地坐標(biāo)系和世界坐標(biāo)系兩種,那么就需要有方法可以對(duì)這兩種坐標(biāo)系進(jìn)行轉(zhuǎn)換。

transform.localToWorldMatrix:本地坐標(biāo)轉(zhuǎn)世界坐標(biāo)的矩陣信息。

transform.worldToLocalMatrix:世界坐標(biāo)轉(zhuǎn)本地坐標(biāo)的矩陣信息。

transform.TransformDirection:將方向從本地坐標(biāo)轉(zhuǎn)換為世界坐標(biāo),不受縮放影響。

transform.InverseTransformDirection:將方向從世界坐標(biāo)轉(zhuǎn)換為本地坐標(biāo),不受縮放影響。

transform.TransformPoint:將位置從本地坐標(biāo)轉(zhuǎn)換為世界坐標(biāo),受縮放影響。

transform.InverseTransformPoint:將位置從世界坐標(biāo)轉(zhuǎn)換為本地坐標(biāo),受縮放影響。

transform.TransformVector:將坐標(biāo)點(diǎn)從本地坐標(biāo)轉(zhuǎn)換為世界坐標(biāo),不受位置影響但受縮放影響。

transform.InverseTransformVector:將坐標(biāo)點(diǎn)從世界坐標(biāo)轉(zhuǎn)換為本地坐標(biāo),不受位置影響但受縮放影響。

TransformPoint和TransformVector的區(qū)別

下面我們看看這兩個(gè)方法的區(qū)別,首先,我們添加一個(gè)空物體到舞臺(tái)并設(shè)置該物體的坐標(biāo)為(1,1,1),然后把Cube對(duì)象拖入該空物體成為其子項(xiàng)設(shè)定其坐標(biāo)為(2,2,2),修改腳本如下:

using UnityEngine;

using System.Collections;

public class Demo01Script : MonoBehaviour

{

public Transform myTransform;

void Start()

{

????Vector3 pos = myTransform.TransformPoint(new Vector3(1, 1, 1));

????Debug.Log(pos);

????//(4.0, 4.0, 4.0)

????Vector3 vec = myTransform.TransformVector(new Vector3(1, 1, 1));

????Debug.Log(vec);

????//(1.0, 1.0, 1.0)

}

void Update()

{

}

}


接下來(lái)我們把空物體的尺寸縮小一半看看結(jié)果會(huì)如何:



結(jié)論

TransformPoint轉(zhuǎn)變會(huì)受物體的位置和縮放影響轉(zhuǎn)換,而TransformVector僅受物體的縮放影響轉(zhuǎn)換。

Demo01

這里做了一個(gè)示例,具體的功能是按下指定的鍵抓取到場(chǎng)景中的小盒子,使其始終位于屏幕前方,按下另一個(gè)鍵將這個(gè)小盒子拋出。

下面我們看核心的實(shí)現(xiàn)。

using UnityEngine;

using System.Collections;

public class Demo01Script : MonoBehaviour{?

?public Transform cube;?

?void Start() { }?

?void Update()?

?{

?//抓取小盒子 if (Input.GetKey(KeyCode.Q))

?{?

?//設(shè)定小盒子的位置到屏幕前方?

?cube.transform.position = transform.TransformPoint(new Vector3(0, 0, 2));?

?//將小盒子設(shè)定為讀取對(duì)象的子對(duì)象, 保證跟隨運(yùn)動(dòng)

?cube.transform.SetParent(transform);?

?//去掉物理交互?

?cube.GetComponent().isKinematic = true;?

?}?

?//扔出小盒子

?if (Input.GetKey(KeyCode.E))?

?{

?if (cube.transform.parent == transform)?

?{

?//使用掉物理交互?

?cube.GetComponent().isKinematic = false;

?//解除所有子物件的綁定關(guān)系

?transform.DetachChildren();?

?//獲取方向?

?Vector3 cameraDirect = transform.TransformDirection(0, 0, 5);?

?//添加緩沖的力?

?cube.GetComponent().AddForce(cameraDirect, ForceMode.Impulse);

}

}

}

}

我們先將攝像機(jī)前的一個(gè)點(diǎn)轉(zhuǎn)換為世界坐標(biāo)賦予給小盒子的世界坐標(biāo)使其位于攝像機(jī)之前,拋出時(shí)把攝像機(jī)向前方向的一個(gè)向量轉(zhuǎn)換為世界方向賦予小盒子拋出。

位移

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

本地和世界坐標(biāo)

transform.position:設(shè)置和獲取物件的世界坐標(biāo)。

transform.localPosition:設(shè)置和獲取物件的本地坐標(biāo),相對(duì)于父級(jí)的坐標(biāo)。

注意,在Inspector面板中的Transform里顯示的是本地坐標(biāo)。

Translate

Transform的Translate方法可以更加方便的對(duì)物體的位移進(jìn)行操作,該方法有四個(gè)重載:

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

相對(duì)于本地坐標(biāo)系或世界坐標(biāo)系對(duì)物體進(jìn)行位移操作。

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

相對(duì)于指定物體的坐標(biāo)進(jìn)行位移操作。

注意:如果是相對(duì)于本地坐標(biāo)系,則如果向上移動(dòng)就是朝向物體本身的上方移動(dòng),如果是相對(duì)于世界坐標(biāo)系則是向世界的上方向移動(dòng),如果是相對(duì)于其他物體則是向這指定的物體的上方向移動(dòng)。

AnimationCurve

AnimationCurve可以用來(lái)定義自定義的動(dòng)畫(huà)軌跡,我們通過(guò)在腳本中聲明一個(gè)該類型的對(duì)象,就可以在編輯器窗口對(duì)其進(jìn)行編輯,然后使我們的物體按照編輯的軌跡進(jìn)行移動(dòng)等操作。

比如我們想要得到一個(gè)物體在X軸勻速移動(dòng),Y軸進(jìn)行上下循環(huán)移動(dòng)的時(shí)候,可以使用下面的腳本:

using UnityEngine;

using System.Collections;

public class Demo02Script : MonoBehaviour

{

public AnimationCurve myAnimationCurve;

public Transform myTransform;

void Start()

{

}

void Update()

{

myTransform.position = new Vector3(

myTransform.position.x + 1 * Time.deltaTime,

myAnimationCurve.Evaluate(Time.time * 0.5f) * 2,

myTransform.position.z);

}

}

編輯器編輯的曲線如下:



Demo02

在游戲中都會(huì)有一個(gè)最基本的需求,就是移動(dòng)到指定的點(diǎn),下面我們來(lái)實(shí)現(xiàn)一下這個(gè)基本的功能,腳本如下:

using System;

using UnityEngine;

using System.Collections;

public class Demo02Script : MonoBehaviour

{

?public Transform myTransform;

?public Transform myTarget;?

?private bool _isArrived = true;?

?private Vector3 _origin;?

?private Vector3 _target;

?private float _speed;?

?private Action _onArrived;?

?private float _allTime;

?private float _time;

?void Start()

?{?

?MoveTo(myTarget.position, 1, () => Debug.Log("I am arrived!"));

?} void Update() { if (!_isArrived) { _time += Time.deltaTime; //判斷是否抵達(dá)終點(diǎn) if (_time >= _allTime) { //校正位置 myTransform.position = _target; //標(biāo)記到達(dá)和調(diào)用回調(diào) _isArrived = true; if (_onArrived != null) { _onArrived(); } } else { //這里使用Lerp方法進(jìn)行差值運(yùn)算也可以得到相同的效果, 但是我們作為學(xué)習(xí)還是自己實(shí)現(xiàn) //myTransform.position = Vector3.Lerp(_origin, _target, _time / _allTime); //獲取方向的單位向量 Vector3 dirction = _target - _origin; dirction.Normalize(); //朝方向運(yùn)動(dòng) myTransform.Translate(dirction * Time.deltaTime); } } } ////// 移動(dòng)到指定點(diǎn).

//////目標(biāo)點(diǎn).? ? ///移動(dòng)速度, 米/秒.? ? ///到達(dá)后調(diào)用的方法.? ? private void MoveTo(Vector3 targetPosition, float speed, Action onArrived)? ? {? ? ? ? _isArrived = false;? ? ? ? _origin = myTransform.position;? ? ? ? _target = targetPosition;? ? ? ? _speed = speed;? ? ? ? _onArrived = onArrived;? ? ? ? //計(jì)算總共需要花費(fèi)的時(shí)間? ? ? ? _allTime = Vector3.Distance(myTransform.position, _target) / _speed;? ? ? ? //重置使用的時(shí)間? ? ? ? _time = 0;? ? }}


運(yùn)行后小盒子會(huì)想指定的物體進(jìn)行勻速移動(dòng),到達(dá)后會(huì)輸出“I am arrived!”的字符串。

旋轉(zhuǎn)之歐拉角

歐拉角是由3個(gè)軸的旋轉(zhuǎn)角度組成的旋轉(zhuǎn)數(shù)據(jù),比如我們?cè)贗nspector界面的Transform中看到的就是物體本地坐標(biāo)系的歐拉角:



歐拉角每個(gè)軸數(shù)字都在0-360之間,表示其旋轉(zhuǎn)的角度。

Rotate

官方提供的旋轉(zhuǎn)方法,其一共有三個(gè)重載方法:

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

指定在本地坐標(biāo)系或世界坐標(biāo)系下旋轉(zhuǎn)到指定的角度。

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

指定在本地坐標(biāo)系或世界坐標(biāo)系下基于軸axis進(jìn)行旋轉(zhuǎn),旋轉(zhuǎn)到angle角度。

RotateAround

我們先看看其參數(shù):

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

表示我們的物體圍繞指定的點(diǎn)point在軸axis下旋轉(zhuǎn)angle的角度。

LookAt

可以使物體面向指定的點(diǎn),我們看看其參數(shù):

1publicvoidLookAt(Transform target, Vector3 worldUp =Vector3.up);2publicvoidLookAt(Vector3 worldPosition, Vector3 worldUp = Vector3.up);

即使我們的物體面向指定的物體或點(diǎn)。

旋轉(zhuǎn)之四元數(shù)

歐拉角理解和使用都相當(dāng)?shù)姆奖?,但是在?shí)際進(jìn)行旋轉(zhuǎn)時(shí)存在萬(wàn)向鎖的問(wèn)題,所以引入了比較抽象的四元數(shù)的概念,當(dāng)然我們?cè)赨nity中只要直接使用即可,是非常方便的。

這里提供一個(gè)視頻,可以讓大家直觀的了解什么是萬(wàn)向鎖:http://v.youku.com/v_show/id_XNzkyOTIyMTI=.html

Quaternion

在Transform中,eulerAngles屬性是使用歐拉角來(lái)表示旋轉(zhuǎn),而rotation屬性則是使用四元數(shù)來(lái)表示旋轉(zhuǎn)。

四元數(shù)提供了許多的靜態(tài)方法來(lái)使我們完成特定需求的效果,點(diǎn)擊這里可查看幫助。

Demo03

如果我們想要實(shí)現(xiàn)一個(gè)效果,物體勻速旋轉(zhuǎn)到指定角度時(shí),使用歐拉角對(duì)每個(gè)軸進(jìn)行變換是相當(dāng)復(fù)雜的,同時(shí)如果兩個(gè)軸重合了就會(huì)出現(xiàn)萬(wàn)向鎖的問(wèn)題,無(wú)法解決,而使用四元數(shù)則可以避免這些問(wèn)題,下面是實(shí)現(xiàn)的腳本:


using UnityEngine;

using System.Collections;

public class Demo03Script : MonoBehaviour

{

public Transform myTransform;

public Transform myTarget;

void Start()

{

}

void Update()

{

RotateToTarget();

}

private void RotateToTarget()

{

//獲取目標(biāo)方向的單位向量

Vector3 dicetion = (myTarget.position - myTransform.position).normalized;

//獲取目標(biāo)方向的四元數(shù)對(duì)象

Quaternion targetDicetion = Quaternion.LookRotation(dicetion);

//按照每秒 45 度的速度旋轉(zhuǎn)面向目標(biāo)對(duì)象

myTransform.rotation = Quaternion.RotateTowards(myTransform.rotation, targetDicetion, 45 * Time.deltaTime);

}

}


這樣我們就可以使我們的物體勻速的轉(zhuǎn)向指定的目標(biāo)對(duì)象了。

縮放與位置關(guān)系

縮放

縮放比較簡(jiǎn)單,沒(méi)有提供更多的方法。

Transform.lossyScale:只讀,獲取本物體相對(duì)于世界坐標(biāo)的縮放大小。

Transform.localScale:設(shè)置或獲取本物體相對(duì)于父級(jí)IDE縮放大小。

位置關(guān)系

在Unity3D中,所有3D對(duì)象是按照樹(shù)形結(jié)構(gòu)進(jìn)行組合的,而操作物體之間的位置關(guān)系的所有API都存放在Transform對(duì)象中,下面我們看看常用的屬性及方法。

屬性

Transform.parent:設(shè)置和獲取父級(jí)對(duì)象。

Transform.root:獲取層次最高的對(duì)象。

Transform.childCount:獲取子級(jí)對(duì)象的數(shù)量。

方法

Transform.Find:根據(jù)名字尋找子項(xiàng)。

Transform.IsChildOf:判斷是否為指定Transform對(duì)象的子項(xiàng)。

Transform.DetachChildren:解除所有子項(xiàng)。

Transform.GetChild:根據(jù)索引獲取子項(xiàng)。

Transform.GetSiblingIndex:獲取同一級(jí)別的物體的索引。

Transform.SetAsFirstSibling:設(shè)置為同一級(jí)別的物體為第一個(gè)索引。

Transform.SetAsLastSibling:設(shè)置為同一級(jí)別的物體為最后一個(gè)索引。

Transform.SetSiblingIndex:設(shè)置同一級(jí)別的物體的索引。

工程文件下載

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

天道酬勤,功不唐捐!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容