一、Unity3D中有哪些坐標系?
坐標系這個概念最早是由法國數學家笛卡爾提出的,並由此創造了用代數方法來研究幾何圖形的數學分支——解析幾何。解析幾何的基本思想是將幾何圖形抽象成點的運動軌跡,從而點可以作為組成圖形的基本元素,而描述一個點的位置首先需要建立合適的坐標系。所以,首先我們來了解下Unity3D中都有哪些坐標系吧!Unity3D中的坐標系目前可以分為以下四類:世界坐標、屏幕坐標、視圖坐標和GUI坐標。下面我們來對這5類坐標進行詳細說明:
世界坐標
世界坐標按照笛卡爾坐標系定義出來的絕對坐標系,下面的各種坐標系都建立在世界坐標的基礎上。我們知道二維平面內任意一個點可以用二維坐標(x,y)來表示,如果將這個概念延伸到三維空間內,那么三維空間內任意一個點都可以用三維坐標(x,y,z)來表示。這就是世界坐標的概念啦,坐標系通常可以分為左手坐標系和右手坐標系,而Unity3D采用的是左手坐標系。在Unity3D中我們可以使用transform.position來獲取場景中一個物體的世界坐標,通常情況下編輯器中的Inspector窗口是以世界坐標來描述一個3D物體的位置的,除非當一個3D物體存在父物體的時候,它會以相對坐標來描述其位置。
屏幕坐標
屏幕坐標是以像素來定義的,它的范圍是以左下角為(0,0),右上角為(Screen.width,Screen.height)定義的這樣一個矩形。屏幕坐標是一個3D坐標,Z軸是以相機的世界單位來衡量的。屏幕坐標和相機之間滿足:Screen.width=Camera.pixelWidth和Screen.height=Camera.pixelHeight這兩個條件。例如我們將相機正對着場景中的原點(0,0,0),相機的Z軸分量為-10,按照屏幕坐標的定義,假設屏幕為800X640的大小,則此時原點轉化為屏幕坐標后應該是(400,320,10)。在Unity3D中我們可以使用WorldToScreenPoint來將一個世界坐標轉換為屏幕坐標。
視口坐標
視口坐標是標准化后的屏幕坐標。標准化的概念我們可以引申到向量的標准化中,比如一個向量(x,y)將過標准化后可以得到一個單位向量(x’,y’)。類似地,視口坐標是以0到1間的數字來表示的,它的范圍是以左下角為(0,0),右上角為(1,1)定義的這樣一個矩形。視口坐標是一個3D坐標,Z軸是以相機的世界單位來衡量的。通過對比可以發現視口坐標和屏幕坐標特別的相似,所以這里大家可以對比着來學習。同樣以屏幕坐標中的例子來將這里的轉換,例如我們將相機正對着場景中的原點(0,0,0),相機的Z軸分量為-10,按照屏幕坐標的定義,假設屏幕為800X640的大小,則此時原點轉化為屏幕坐標后應該是(0.5,0.5,10)。在Unity3D中我們可以使用WorldToViewportPoint來將一個世界坐標轉換為視口坐標。
GUI坐標
GUI坐標是指通過OnGUI方法繪制UI時使用的坐標。這個坐標系和屏幕坐標類似,它同樣是以像素來定義的,它的范圍是以左上角為(0,0),右下角為(Screen.width,Screen.height)定義的這樣一個矩形,GUI坐標是一個2D坐標(絕對坐標)。因為GUI在早期的文章中曾有所設計,屬於Unity3D中較為基礎的內容,此外我們知道使用絕對坐標來進行布局的話是沒有辦法做自適應的,所以這部分內容我們就不展開講了,UI自適應的一個主要觀點就是不要使用絕對坐標!不要使用絕對坐標!不要使用絕對坐標!重要的事情要說三遍的嘛!這里想特別說說Unity4.6以后推出的全新UI支持:uGUI。在uGUI的Screen Space模式下,Unity3D的編輯器以屏幕坐標來顯示UI元素的位置;在World Space模式下,Unity3D的編輯器以世界坐標來顯示UI元素的位置。相信這一點大家在使用的時候都沒有注意到,最近在看一個論壇帖子的時候發現對uGUI坐標轉換的相關知識一片茫然,所以對這部分內容進行了深入的研究,最終得出的結論是:uGUI的坐標本質上是特殊的屏幕坐標,因為uGUI的Anchor決定了該坐標系的原點,pivot決定了元素本身坐標系的原點,正是這兩個屬性讓uGUI的坐標看起來顯得撲朔迷離。RectTransform組件繼承自Transform,所以它們的position和localPosition是等價的,都是世界坐標;anchoredPosition是UI元素的屏幕坐標,在對UI元素進行操作的時候應該考慮使用這個坐標。
二、Unity3D各種坐標系間的轉換
雖然Unity3D里提供了各種坐標系間的轉換API,可是我們這是在寫博客不是在說API不是?所以從實用性的角度出發,我們這里提供了一個Unity3D各種坐標系間轉換的FAQ!
1、屏幕坐標如何轉換為世界坐標?
在Unity3D中通過camera.ScreenToWorldPoint(Vector3 v)方法可以將一個屏幕坐標轉化為世界坐標。其中,camera是游戲場景中的場景相機。通過Input.mousePosition或者 Input.touches[0].position可以獲得鼠標或者單根手指的屏幕坐標。典型的應用場景是第一人稱射擊游戲中子彈的發射和RPG游戲中點擊地面移動角色。
2、世界坐標如何轉換為屏幕坐標?
在Unity3D中通過camera.WorldToScreenPoint(Vector3 v)方法可以將一個世界坐標轉化為屏幕坐標。其中,camera是游戲場景中的UI相機。比如我們需要將一個世界坐標轉換到NGUI坐標的時候,可以使用場景相機將世界坐標轉為屏幕坐標,然后再利用UI相機將屏幕坐標轉換為世界坐標,最后再賦值給NGUI控件。典型的應用場景是NPC頭頂的文字顯示和怪物頭頂的血條顯示。
3、屏幕坐標如何轉換為視口坐標?
在Unity3D中可以通過camera.ScreenToViewportPoint()來將一個屏幕坐標轉換為視口坐標,其中camera是游戲場景中的場景相機。當我們了解了視口坐標本質就是屏幕坐標的標准化坐標以后,這個真心沒有什么可說的。
4、世界坐標如何轉換為視口坐標?
在Unity3D中可以通過camera.WorldToViewportPoint()來將一個世界坐標轉換為視口坐標,其中camera是游戲場景中的場景相機。當我們了解了視口坐標本質就是屏幕坐標的標准化坐標以后,這個真心沒有什么可說的。
5、屏幕坐標如何轉化為GUI坐標?
我們注意到屏幕坐標和GUI坐標的根本差異在於坐標系原點選定不同,即屏幕坐標的原點在左下角,而GUI坐標的原點在左下角。因此我們可以定義一個ScreenToGUIPoint方法:
[mw_shl_code=csharp,true]private Vector2 ScreenToGUIPoint(Vector2 v)
{
return new Vector2(v.x,Screen.height-v.y)
}[/mw_shl_code]
6、GUI坐標如何轉換為屏幕坐標?
一個更為神奇的事情是你可以繼續調用ScreenToGUIPoint方法。
7、屏幕坐標如何轉換為uGUI坐標?
當我們接觸到uGUI以后常常會遇到類似NGUI需要為uGUI控件坐標賦值的問題,此時我們我們有以下兩種思路處理:
直接把屏幕坐標賦值給transform.position,這種方式簡單粗暴在ScreenSpace-Overlay模式下沒有問題,然而在ScreenSpace-Camera模式下會出現因為深度數值不正確而引發的問題,需要注意(並且需要確保被賦值的物體在Canvas的根節點下),前段時間有人在群里問我position和localPosition的區別,我就呵呵噠,現在人都是一般的浮躁啊,寧肯整天鑽研某某插件的用法都不願意鑽研下API文檔、計算機圖形學、3D數學這些東西。
通過RectTransformUtility.ScreenPointToWorldPointInRectangle方法將屏幕坐標轉化為世界坐標然后再賦值給transform.position。兩種方法看起來大同小異,然而這種方法是更為保險的,因為它在這兩種模式下都能穩定地運行。更重要的是它可以和uGUI的事件系統完美地融合。我猜測它是直接改變UI元素的世界坐標,因為它是賦值給transform.position。可是話說回來,以前給怪物頭頂顯示血條的時候都是直接把世界坐標轉屏幕坐標再賦值給transform.position,這樣看來當初的想法可能是錯誤的呀,如果要給UI控件屏幕坐標那不是應該給anchoredPosition賦值嗎?好吧,這塊兒暫時還沒有搞明白,如果有朋友知道怎么回事,可以告訴我,謝謝!