1. 程式人生 > >關於RectTransform的一些研究

關於RectTransform的一些研究

座標

unity裡面的座標是笛卡爾座標系,和flash的還是有區別的..

世界座標

世界座標是三維的全域性座標,一般作為基準座標

螢幕座標

二維座標,螢幕左下角是(0,0),右上角是(sizeX,sizeY). flash裡面的螢幕左上角是(0,0),右下角是(sizeX,sizeY)

Rect

官網文件Rect

需要注意的是雖然文件寫法設定了左上角是起點,但在unity的實際結果中,返回的rect還是以左下角為起點的.

RectTransform裡面的三要素

  1. 錨點(四個) 
  2. 角(四個) 
  3. 中心點(一個) 

這三者的關係可以這麼形容:在Unity裡面,一個UI物件的座標是指該物件的中心點相對於其父容器的四個錨點upda的中心來定義的,UI物件RectTransform裡面的四個角決定了這個物件的寬高.當四個錨點不在一起時,四個錨點和對應的四個點之間的距離決定了該容器相對於父容器的大小.

也就說說錨點和角決定尺寸,錨點和中心點決定位置.

Image[4]

在這張圖裡,最上面四個小三角就是四個錨點,周圍四個實心的點是RectTransform的角,中間的圓環就是中心點,四個錨點在一起,所以物件的尺寸沒有隨父容器尺寸的變化而變化.

Image(1)[4]

在這張圖裡面水平錨點被分開了,所以物件的寬度會隨著父容器尺寸的變化而變化.

RectTransform涉及到得API說明

anchoredPosition

中心點相對於四個錨點中點的座標

rect

rect的x和y返回左下角相對於中心點的距離,w和h返回本身的寬高.

offsetMin和offsetMax

分別指左下角相對於左下角錨點的距離以及右上角相對於右上角錨點的距離

anchorMin和anchorMax

這個是針對錨點的,錨點時相對於父容器定義的,所以這兩個屬性也是相對於父容器的.分別指錨點佔父容器尺寸的百分比位置.

sizeDelta

這個值挺好玩的,如果四個錨點都在一定,就是寬度和高度,如果水平的錨點分開了,y還是高度,x變成了-(left+right).如果垂直的錨點分開了,x還是寬度,y變成了-(top+bottom)

中心點的螢幕座標

overlay模式就是position,否則是世界座標,需要worldtoscreen進行轉換

private Vector3 GetSpacePos(RectTransform rect, Canvas canvas, Camera camera)
{
    if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
    {
        return rect.position;
    }
    return camera.WorldToScreenPoint(rect.position);

}
GetWorldCorners

返回四個角的世界座標,對應的螢幕座標依然和渲染模式有關

private void GetSpaceCorners(RectTransform rect, Canvas canvas, Vector3[] corners,Camera camera)
{
    if (camera == null)
    {
        camera = Camera.main;
    }
    rect.GetWorldCorners(corners);
    if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
    {

    }
    else
    {
        for (var i = 0; i < corners.Length; i++)
        {
            corners[i] = camera.WorldToScreenPoint(corners[i]);
        }
    }
}
RectTransformUtility.RectangleContainsScreenPoint

在overlay模式下不能用..

一些實際中可能用到的例子

獲取滑鼠點下圖片的畫素
public Rect GetSpaceRect(Canvas canvas, RectTransform rect, Camera camera)
{
    Rect spaceRect = rect.rect;
    Vector3 spacePos = GetSpacePos(rect, canvas, camera);
    //lossyScale
    spaceRect.x = spaceRect.x * rect.lossyScale.x + spacePos.x;
    spaceRect.y = spaceRect.y * rect.lossyScale.y + spacePos.y;
    spaceRect.width = spaceRect.width * rect.lossyScale.x;
    spaceRect.height = spaceRect.height * rect.lossyScale.y;
    return spaceRect;
}

public bool RectContainsScreenPoint(Vector3 point, Canvas canvas, RectTransform rect, Camera camera)
{
    if (canvas.renderMode != RenderMode.ScreenSpaceOverlay)
    {
        return RectTransformUtility.RectangleContainsScreenPoint(rect, point, camera);
    }

    return GetSpaceRect(canvas, rect, camera).Contains(point);
}

// Update is called once per frame
void Update()
{
    if (RectContainsScreenPoint(Input.mousePosition, _canvas, rect, _canvas.camera))
    {
        Image image = _uiObject.GetComponent<Image>();
        var spaceRect = GetSpaceRect(_canvas, rect, camera);
        var localPos = Input.mousePosition - new Vector3(spaceRect.x, spaceRect.y);
        var realPos = new Vector2(localPos.x , localPos.y );
        var imageToTextre = new Vector2(image.sprite.textureRect.width/spaceRect.width,
            image.sprite.textureRect.height/spaceRect.height);
        _resultImage.color = _uiObject.GetComponent<Image>().sprite.texture.GetPixel((int)(realPos.x*imageToTextre.x), (int)(realPos.y*imageToTextre.y));
    }
}

只在RenderMode是ScreenSpaceOverlay測試通過,主要過程涉及到座標轉換.

  1. 將圖片的座標轉換到左下角為起點的座標系 
  2. 將滑鼠的螢幕座標轉換到相對於圖片的座標,並獲得改座標 
  3. 將該座標換算到對應紋理的座標點. 

幾點發現

  1. lossyScale 是一個物件的全域性縮放量,比如你給canvas加了一個CanvasScaler,那麼image的縮放量會包括改值,localScale卻不包括. 
  2. lossyScale 在Canvas的渲染模式是ScreenSpaceCamera時,數值略詭異,似乎和Camerasize有關,所以上述程式碼在ScreenSpaceCamera無效 
  3. RectTransform裡面的rect返回的尺寸沒有經過lossyScale,需要自己計算,詳見GetSpaceRect函式 
  4. 計算滑鼠相對於圖片的座標也要考慮lossyScale
  5. 最後計算出來的座標是相對於圖片的座標,圖片和內部的Texture之間的尺寸可能不是1:1的,所以也需要計算比例,參考imageToTextre變數 

總結

相對於flash,這次的ui系統最大的更新就是加入了四個錨點並且可以分開

吐槽 unity api文件寫的真是爛,一定是程式設計師寫了註釋直接轉過來的,還有開源一點誠意都木有,關鍵的RectTransform和Canvans之類的都沒原始碼

Canvas的三種渲染模式的實際用處還有待發現,目前做重度UI相關的業務用ScreenSpaceOverlay一切正常,

渲染模式是WorldSpace的一個視訊