該系列筆記基于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
天道酬勤,功不唐捐!