Unity - 物件世界座標(World position) <-> UI座標(UGUI position)的互換

  簡單的來做個座標的轉換,雖然不一定會常常用到,但是某些地方還是有點小用處就是了。


  這次就是把UGUI中的Local物件座標跟世界座標來做個轉換。



  首先來看從UI座標轉世界座標,UGUI有自己的座標設定,它的Pivot point預設在中心(0.5, 0.5)的位置,所以在UI中心的Local的座標位置就是(0, 0),先來看一張簡單的示意圖。


  從這張示意圖來看,假設遊戲解析度是(800*600),同時UGUI的canvas設定的也是(800*600),紅色的線條標示的就是Viewport的座標,左下角(0, 0),右上角(1, 1)。

  在這個UI上我放置了一個Text物件,它在Canvas下的Local座標是(164.7, 100.2),但是因為原點在中心,所以如果把原點換到左下角,這個物件的座標就是(564.7, 400.2),在經過換算就可得到VIewport的位置是(0.706, 0.667)。

  到這邊就可以經由Camera.ViewportToWorldPoint()這個方法來求得這個UI座標的位置在World的位置。

  當然也可以直接用該Text的Position來得到這個物件的世界座標,不過如果有時候沒有物件單純只有一個UI座標的時候就會需要用到轉換。





  從UI座標轉換到世界座標就這樣,相反的從世界座標轉換到UI座標也差不多。


  先使用Camera.WorldToViewportPoint()把物件從世界座標轉換成Viewport座標,例如得到的物件Viewport座標是(0.706, 0.667)的位置,經過換算:

    x = (0.706 * 800) - 400 = 164.8;
    y = (0.667 * 600) - 300 = 100.2;

  也就可以得到它在UI中的Local位置應該是(164.8, 100.2)。





  到目前為止都還算可以,這是因為遊戲畫面的解析度(800*600)跟UI的canvas設定的解析度一樣,但是當設定的數值不同時就會造成一點差異。


  這個示意圖來看,遊戲畫面的解析度是(960*600),而canvas的設定是(800*600)並且使用matchWidthOrHeight的設定。

  因為matchWidthOrHeight是以Width為準,所以寬的pixels實際上不改變,而高的pixels實際上會變為500,因為用(960*600)的比例去調整。

    Width = 800; //不改變
    Height = (600 / 960) * 800 = 500; //依據Width調整

  因此上面那個UI物件的Local座標不變,但是就會跟原本,遊戲與UI相同解析度所得到的資料不同了。





  而如果matchWidthOrHeight中,Match的數值不是0或1的時候又會更麻煩一點,這邊可以簡單參考UI Canvas物件RectTransform中的數值。


  從這個UI的Rect Transform當中可以看到,雖然我CanvasScaler是設定(800*600),但是使用了matchWidthOrHeight,所以Rect Transform中的Width跟Height自動調整到了(800*500)。

  因此可以使用RectTransform中的Rect的數值,取得當前Width跟Height的Pixels數值。

  基本上也可以使用CanvasScaler中的Resolution、Width值、當前螢幕解析度,來去計算這兩個值,不過就簡單直接使用這個就好了。



參考Class,這邊我是把它做成Static直接呼叫使用。
public class UIPosition
{
    static public Vector2 WorldToUI(RectTransform r, Vector3 pos)
    {
        Vector2 screenPos = Camera.main.WorldToViewportPoint(pos); //世界物件在螢幕上的座標,螢幕左下角為(0,0),右上角為(1,1)
        Vector2 viewPos = (screenPos - r.pivot) * 2; //世界物件在螢幕上轉換為UI的座標,UI的Pivot point預設是(0.5, 0.5),這邊把座標原點置中,並讓一個單位從0.5改為1
        float width = r.rect.width / 2; //UI一半的寬,因為原點在中心
        float height = r.rect.height / 2; //UI一半的高
        return new Vector2(viewPos.x * width, viewPos.y * height); //回傳UI座標
    }

    static public Vector3 UIToWorld(RectTransform r, Vector3 uiPos)
    {
        float width = r.rect.width / 2; //UI一半的寬
        float height = r.rect.height / 2; //UI一半的高
        Vector3 screenPos = new Vector3(((uiPos.x / width) + 1f) / 2, ((uiPos.y / height) + 1f) / 2, uiPos.z); //須小心Z座標的位置
        return Camera.main.ViewportToWorldPoint(screenPos);
    }
}


  如果有任何問題或錯誤歡迎提出。

1 comment:

Radian said...

這篇文章很詳細,感謝分享!

Post a Comment