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

作者:四五二十
大家好。今天帶來的是一個講座性質(zhì)的內(nèi)容。這里的概念搞清楚且能純熟運(yùn)用了,對于游戲開發(fā)那是相當(dāng)?shù)挠杏谩?/p>

首先來介紹一下我們經(jīng)常會用到的幾種坐標(biāo)系:
一.世界坐標(biāo)系
游戲場景中所有的物體都統(tǒng)一遵守的坐標(biāo)系統(tǒng),它標(biāo)注了每個物體在世界中的唯一位置方向信息。

如上圖所示,我們用一個平面世界坐標(biāo)系來表示紅點(diǎn)和藍(lán)點(diǎn)的位置,往后無論增加多少個點(diǎn),都可以在該坐標(biāo)系中找到一個數(shù)值來表示位置,只要被標(biāo)注位置的物體不動,位置數(shù)值永遠(yuǎn)不變。
所以世界坐標(biāo)系又叫絕對坐標(biāo)系,我們在Unity中使用的世界坐標(biāo)不過是多了一根Z軸的空間坐標(biāo)系罷了,原理是一樣的。
二.本地坐標(biāo)系
本地坐標(biāo)系則是以物體自身位置作為原點(diǎn),表示物體間相對位置和方向,并且會根據(jù)物體自身旋轉(zhuǎn)而旋轉(zhuǎn):

我們在Inspector看到的坐標(biāo)系其實(shí)就是本地坐標(biāo)系,包括旋轉(zhuǎn)、縮放都屬于本地:

當(dāng)一個物體有父物體時,無論父物體的位置旋轉(zhuǎn)縮放如何變化,作為子物體它的Transform數(shù)值始終保持不變,說明它相對于自己的父物體位置始終沒有變化,而當(dāng)一個物體沒有父物體時,可以把“世界” 就看成它的父物體,它在世界中的一切位置變化都可以看做是和世界的相對位置變化,由于世界坐標(biāo)系和“世界的本地坐標(biāo)系”是重合的,所以該物體的本地坐標(biāo)和世界坐標(biāo)也是一樣的。
由于物體各自坐標(biāo)系的不同參考標(biāo)準(zhǔn),同一個物體,會在不同本地坐標(biāo)系中顯示出不同的位置信息,產(chǎn)生各種各樣的歧義:

熊大將美女在本地坐標(biāo)系的方向位置告訴熊二,熊二如果也往左手邊這個方向看是肯定看不見美女的,所以本地坐標(biāo)系如果使用不當(dāng)很容易帶來混亂。而如果熊大能將美女的世界坐標(biāo)告訴熊二,熊二就不會感到迷惑了。
三.屏幕坐標(biāo)系
在Unity的Game視窗中,以屏幕左下角為原點(diǎn)(0,0),像素為單位,坐標(biāo)軸往屏幕右上方進(jìn)行延伸,X軸和Y軸數(shù)值不能超過屏幕的最大寬度和最大高度。由于原點(diǎn)位置是唯一的,也可以理解成是一個平面世界坐標(biāo)系,作用是表示物體在屏幕中的位置。

四.視口坐標(biāo)系
視口就是當(dāng)你在Unity 的Scene面板中看到攝像機(jī)的那個白色矩形框:

視口坐標(biāo)系同屏幕坐標(biāo)系相似,也是以左下角為原點(diǎn)的平面坐標(biāo)系,不同的是視口坐標(biāo)系的數(shù)值上限不受屏幕大小影響,右上角的坐標(biāo)永遠(yuǎn)是(1,1),數(shù)值就是物體屏幕坐標(biāo)與屏幕寬高的比例:( X軸/寬度, Y軸/高度),作用是表示物體在攝像機(jī)中的位置。

對這幾種坐標(biāo)系有一個基本的了解后,我們就來看坐標(biāo)系之間的轉(zhuǎn)換:
一.本地坐標(biāo)<==>世界坐標(biāo):
●本地轉(zhuǎn)世界:transform.TransformPoint
用一個案例來表示:
1.在場景中有物體A和B,且B是A的子物體:

2.在Inspector面板將A的坐標(biāo)設(shè)為(0,0,5),B的坐標(biāo)設(shè)為(0,0,2),那么這時B的世界坐標(biāo)就應(yīng)該是(0,0,7)。
3.用代碼來驗(yàn)證我們的推斷:
public Transform p; //A物體
public Transform s; //B物體
void Update()
{
??? if (Input.GetKeyDown(KeyCode.A)) //按下A鍵
??? {??????????
??????? print("子物體的本地坐標(biāo): " + s.localPosition);
??????? print("子物體的世界坐標(biāo): " + s.position);
??????? print("本地轉(zhuǎn)世界: " + p.TransformPoint(s.localPosition));
??? }
}
?
4.運(yùn)行游戲,點(diǎn)擊A鍵:

●本地轉(zhuǎn)世界:transform.InverseTransformPoint
將剛才的代碼稍稍改一下:
if (Input.GetKeyDown(KeyCode.A)) //按下A鍵
{??????????
??? print("子物體的本地坐標(biāo): " + s.localPosition);
??? print("子物體的世界坐標(biāo): " + s.position);
??? print("世界轉(zhuǎn)本地: " + p.InverseTransformPoint(s.position));
}
?

二.世界坐標(biāo)<==>屏幕坐標(biāo):
●世界轉(zhuǎn)屏幕:Camera.main.WorldToScreenPoint
屏幕坐標(biāo)是一個二維坐標(biāo)(X,Y),所有被攝像機(jī)照的的三維物體都可以在這個二維屏幕上找到自己的點(diǎn):

在游戲游戲中我們可以看到這種現(xiàn)象:

顯示在人物身上的名字信息,會始終跟著人物移動,并且無論人物離攝像機(jī)多遠(yuǎn)多近,字體大小始終一樣(通過兩幅圖主角血條和頂上的時間顯示可以看出兩個屏幕是一樣大?。鋵?shí)就是將人物的世界坐標(biāo)轉(zhuǎn)成屏幕坐標(biāo),再將這個坐標(biāo)賦給顯示名字的UI,接下來我們就通過一個實(shí)例來完成:
1.在場景中創(chuàng)建一個Cube,再用UGUI創(chuàng)建一個Text,內(nèi)容改為“小兵”:

2.創(chuàng)建腳本:
public Transform cube; //將Cube拖進(jìn)去
public Text text; //將Text拖進(jìn)去
void Update()
{
??? //獲取Cube頂上的位置的世界坐標(biāo)
??? Vector3 pos = cube.position + Vector3.up;
??? //將該世界坐標(biāo)轉(zhuǎn)為屏幕坐標(biāo)賦給Text
??? text.transform.position = Camera.main.WorldToScreenPoint(pos);
}
?
3.運(yùn)行游戲:

●屏幕轉(zhuǎn)世界:Camera.main.ScreenToWoldPoint
和世界轉(zhuǎn)屏幕相反,屏幕轉(zhuǎn)世界則是,將屏幕坐標(biāo)的XY軸信息轉(zhuǎn)換后再增加一個Z軸信息,賦給三維世界的物體:

鼠標(biāo)坐標(biāo)也是屏幕坐標(biāo),我們就來做一個三維物體跟隨鼠標(biāo)移動的案例:
1.創(chuàng)建一個Cube,讓攝像機(jī)正對著它:

2.創(chuàng)建腳本:
public Transform cube; //方塊
public Transform mainCamera; //主攝像機(jī)
void Update()
{
??? if (Input.GetMouseButton(0)) //按住鼠標(biāo)左鍵
??? {
??????? //獲取鼠標(biāo)屏幕坐標(biāo)(Z軸為0)??
??????? Vector3 mPos = Input.mousePosition;
??????? //以攝像機(jī)z軸為法線創(chuàng)建攝像機(jī)XY軸組成的平面?
??????? Plane pla = new Plane(mainCamera.forward, mainCamera.position);
??????? //獲取該物體到平面的距離(z軸垂直距離)???????
??????? float dis = pla.GetDistanceToPoint(cube.position);
??????? //將屏幕坐標(biāo)轉(zhuǎn)為世界坐標(biāo)
??????? cube.position = Camera.main.ScreenToWorldPoint(new Vector3(mPos.x, mPos.y, dis));
??? }
}
?
3.運(yùn)行程序:

四.屏幕坐標(biāo)<==>視口坐標(biāo):
●屏幕轉(zhuǎn)視口:Camera.main.ScreenToViewportPoint
上面我們說到視口坐標(biāo)實(shí)際就是屏幕坐標(biāo)與屏幕的寬高比,我們通過一個案例來進(jìn)行驗(yàn)證:
1.場景中用UGUI新建兩個Text,分別用于顯示視口坐標(biāo)與比例:

2.我們還是使用鼠標(biāo)的屏幕坐標(biāo)來進(jìn)行轉(zhuǎn)換,同時將鼠標(biāo)在攝像機(jī)的位置用比例表示出來,創(chuàng)建腳本:
? ? public Text text; //顯示視口坐標(biāo)
??? public Text text1; //顯示比例
??? void Update()
??? {
??????? //獲取鼠標(biāo)坐標(biāo)的屏幕坐標(biāo)
??????? Vector2 mouPos = Input.mousePosition;
??????? //將屏幕坐標(biāo)轉(zhuǎn)為視口坐標(biāo)
??????? Vector2 viewPos = Camera.main.ScreenToViewportPoint(mouPos);
??????? //顯示視口坐標(biāo)(保留小數(shù)點(diǎn)后兩位)
??????? text.text = "視口: " + viewPos.ToString("0.00");
?
??????? //獲取屏幕坐標(biāo)與屏幕的寬高比
??????? float x = mouPos.x / Screen.width;
??????? float y = mouPos.y / Screen.height;
??????? Vector2 viewpos1 = new Vector2(x, y);
??????? //顯示比例
??????? text1.text = "比值: " + viewpos1.ToString("0.00");
??? }
?
3.運(yùn)行程序:

●視口轉(zhuǎn)屏幕:Camera.main. ViewportPointToScreenPoint
public Text text; //顯示視口坐標(biāo)
public Text text1; //顯示比例
void Update()
{
??? //獲取鼠標(biāo)坐標(biāo)的屏幕坐標(biāo)
??? Vector2 mouPos = Input.mousePosition;
??? //直接顯示屏幕坐標(biāo)
??? text.text = "直接顯示屏幕坐標(biāo): " + mouPos.ToString();
?
??? //獲取屏幕坐標(biāo)與屏幕的寬高比
??? float x = mouPos.x / Screen.width;
??? float y = mouPos.y / Screen.height;
??? Vector2 viewpos = new Vector2(x, y);
?
??? //將視口坐標(biāo)轉(zhuǎn)為屏幕坐標(biāo)
??? Vector2 mouPos1 = Camera.main.ViewportToScreenPoint(viewpos);
??? text1.text = "轉(zhuǎn)換后的屏幕坐標(biāo): " + mouPos1.ToString();
}
?

總結(jié):
Unity中的坐標(biāo)系當(dāng)然不止上述四種,除此之外如投影坐標(biāo)系、切線坐標(biāo)系、GUI坐標(biāo)系等,坐標(biāo)轉(zhuǎn)換除了點(diǎn)的轉(zhuǎn)換外,還有方向和向量的轉(zhuǎn)換,如果有興趣可以查看Unity官方文檔了解。
坐標(biāo)系轉(zhuǎn)換在游戲中用得非常廣泛,學(xué)習(xí)過程中只要能夠弄懂坐標(biāo)系的定義,再去理解它們之間的轉(zhuǎn)換,就相對容易許多。
最后想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問?http://www.levelpp.com/
游戲開發(fā)攪基QQ群:869551769??
微信公眾號:皮皮關(guān)