1. 程式人生 > 其它 >Unity UI 適配問題彙總

Unity UI 適配問題彙總

技術標籤:unityunity3d錨點

Unity UI 適配問題彙總

Canvas元件下的RenderMode選擇

總共有三種模式

  • Overlay UI的渲染總是在畫面的最上層,缺陷:不能渲染粒子特效
  • WorldSpace UI在空間總渲染
  • Camera 優勢:可以渲染粒子

最終選擇Camera模式。RenderCamera選擇另建一個UICamera專門渲染UI。UICamera設定 ClearFlags選擇DepthOnly,CullingMask選擇只渲染UI層,這裡注意既然UICamera已經渲染了UI了,主攝像機就不要再選擇渲染UI層了。

UI Camera 渲染模式下渲染粒子特效

粒子特效layer要設為UI層,要不UICamera渲染不到。
粒子和其他UI的層級關係:

  • 粒子設定層級 particlesystem/Renderer/Order in Layer
  • UI的層級。如果UI元素都在同一個Canvas下,那麼每個UI元素的層級都是這個canvas的層級。canvas的層級設定 Canvas/Order in Layer。
  • 因為粒子之間不能像UI之間一樣以順序排列渲染順序,粒子之間的遮擋關係是靠order in layer的設定來實現的層級越高越後渲染。
  • 如果要實現粒子在兩個UI的中間,即先渲染UI A,再渲染粒子,再渲染UI B。我們知道同一個canvas下,所有UI元素層級一樣。但是可以給UI元素新增元件canvas,在新新增的canvas元件上修改order in layer這樣就可以給單個元素設定新的層級,因為UI元素的父級已經有一個canvas了,所以新新增的canvas是繼承自父級的。

UI Camera 渲染模式下的解析度適配

CanvasScaler/UI Scale Mode UI縮放模式 選擇Scale With Screen Size
CanvasScaler/Reference Resolution 參考解析度 選擇美術提供的參考解析度
CanvasScaler/Match 根據實際手機的解析度選擇0或者1

public class CanvasManager : MonoBehaviour
{
    private void Awake()
    {
        if((float)Camera.main.pixelWidth / (float)Camera.main.pixelHeight >= 
            this.GetComponent<CanvasScaler>().referenceResolution.x / this.GetComponent<CanvasScaler>().referenceResolution.y)
        {
            this.GetComponent<CanvasScaler>().matchWidthOrHeight = 0;
        }
        else
        {
            this.GetComponent<CanvasScaler>().matchWidthOrHeight = 1;
        }
    }
}

這裡主要是對UI的背景板做適配。要實現的效果是如果手機實際解析度寬高比比參考解析度高,即更寬,則以寬適配UI大小,這樣UI背景板會在高度上切去一部分。如果手機實際解析度寬高比比參考解析度低,即更高,則以高適配UI大小,這樣UI背景板會在寬度上切去一部分。即背景板始終會被切去一部分,而不會以黑邊填充。

  • UI Camera 渲染模式下的背景板錨點選擇,一定不要選擇拉伸錨點,選擇錨點聚在一塊(也就是非拉伸模式)居中。這樣才能和上面配合實現始終裁剪背景
  • 正常UI元素(即按鈕等等)的錨點選擇,注意選擇適合的錨點,比如距離底部一定距離的按鈕,錨點一定要定在底部,因為這裡寬高適配是不固定的。如果一定是以高適配UI大小的話,距離底部一定距離的按鈕,錨點也可以定在頂部,換算一下距離即可,因為這時UI高度是一定的。

UI Camera 渲染模式下螢幕座標轉換為anchorposition

有兩種螢幕座標。
第一種input.mouseposition或者pointerhandler的eventdata.position等的輸入座標。這些座標都是根據手機的實際解析度給出的和參考解析度無關,比如參考解析度2048 * 1024,實際解析度 1024 * 1024,輸入點在螢幕中間,則mouseposition為 (512, 512)。
第二種Camera.main.WorldToScreenPoint(vecter3 position),將空間中的物體位置轉換為螢幕座標。如果是main camera的話,這裡返回的座標點是和第一種一樣的。即看到場景中的物體,在螢幕上的座標位置就是相當於輸入座標的位置。比如場景正中央的物體轉化為螢幕座標時,實際解析度1024 * 1024 參考解析度為 2048 * 1024,則螢幕座標為(512, 512)。什麼情況下不一樣呢,就是camera有rendertexture,並且rendertexture的解析度不是手機的實際解析度。經常是參考解析度。這時返回的座標是參考解析度的座標。比如場景正中央的物體轉化為螢幕座標時,實際解析度1024 * 1024 參考解析度為 2048 * 1024,相機上的rendertexture解析度為2048 * 1024,則螢幕座標為(1024, 512)。

將螢幕座標轉化為anchorposition程式碼如下:

 /// <summary>
/// 必須是無拉伸的才可以呼叫這個方法,並且獲取的是世界anchorPosition
/// </summary>
/// <param name="source"></param>
/// <param name="screenPoint"></param>
public static Vector2 GetAnchorpositionByScreenPoint(Vector2 anchorMin, Vector2 anchorMax, Vector2 screenPoint, bool needScale = true)
{
    Vector2 point = Vector2.zero;
    float width = 0, height = 0;
    float widthScale = (float)GameManager.Instance.referrenceWidth / (float)Camera.main.pixelWidth;
    float heightScale = (float)GameManager.Instance.referrenceHeight / (float)Camera.main.pixelHeight;
    float screenPointScale = widthScale <= heightScale ? widthScale : heightScale;
    if (needScale)
    {
        screenPoint = screenPoint * screenPointScale;
    }
    if (widthScale <= heightScale)
    {
        width = GameManager.Instance.referrenceWidth;
        height = Camera.main.pixelHeight * widthScale;
    }
    else
    {
        width = Camera.main.pixelWidth * heightScale;
        height = GameManager.Instance.referrenceHeight;
    }
    //
    if (anchorMin == Vector2.zero && anchorMax == Vector2.zero)
    {
        point = screenPoint;
    }
    if (anchorMin == Vector2.right * 0.5f && anchorMax == Vector2.right * 0.5f)
    {
        point = screenPoint - Vector2.right * width / 2;
    }
    if (anchorMin == Vector2.right && anchorMax == Vector2.right)
    {
        point = screenPoint - Vector2.right * width;
    }
    if (anchorMin == Vector2.up * 0.5f + Vector2.zero && anchorMax == Vector2.up * 0.5f + Vector2.zero)
    {
        point = screenPoint - Vector2.up * height / 2;
    }
    if (anchorMin == Vector2.up * 0.5f + Vector2.right * 0.5f && anchorMax == Vector2.up * 0.5f + Vector2.right * 0.5f)
    {
        point = screenPoint - Vector2.right * width / 2 - Vector2.up * height / 2;
    }
    if (anchorMin == Vector2.up * 0.5f + Vector2.right && anchorMax == Vector2.up * 0.5f + Vector2.right)
    {
        point = screenPoint - Vector2.right * width - Vector2.up * height / 2;
    }
    if (anchorMin == Vector2.up + Vector2.zero && anchorMax == Vector2.up + Vector2.zero)
    {
        point = screenPoint - Vector2.up * height;
    }
    if (anchorMin == Vector2.up + Vector2.right * 0.5f && anchorMax == Vector2.up + Vector2.right * 0.5f)
    {
        point = screenPoint - Vector2.right * width / 2 - Vector2.up * height;
    }
    if (anchorMin == Vector2.up + Vector2.right && anchorMax == Vector2.up + Vector2.right)
    {
        point = screenPoint - Vector2.right * width - Vector2.up * height;
    }
    return point;
}

這個函式可以將螢幕座標轉化為anchorposition,也就是在UI縮放適配的情況下,可以讓UI元素比如button跟著滑鼠走。前兩個引數是recttransform 下的 anchorMax anchorMin,後者是輸入座標,最後一個引數是是否需要對輸入的座標縮放,預設是需要。除非你用相機的函式WorldToScreenPoint獲取的螢幕座標,並且這個相機還得設定了rendertexture,並且rendertexture的解析度不是手機實際的解析度。也就是說如果相機帶有rendertexture慎用這個函式。

程式碼動態設定UI的錨點

程式碼如下:

using System;
using UnityEngine;

public enum AnchorPresets
{
    TopLeft,
    TopCenter,
    TopRight,

    MiddleLeft,
    MiddleCenter,
    MiddleRight,

    BottomLeft,
    BottonCenter,
    BottomRight,
    BottomStretch,

    VertStretchLeft,
    VertStretchRight,
    VertStretchCenter,

    HorStretchTop,
    HorStretchMiddle,
    HorStretchBottom,

    StretchAll
}
public enum NoStrechAnchorPresets
{
    TopLeft,
    TopCenter,
    TopRight,

    MiddleLeft,
    MiddleCenter,
    MiddleRight,

    BottomLeft,
    BottonCenter,
    BottomRight,
    BottomStretch,
}
public enum StrechAnchorPresets
{
    VertStretchLeft,
    VertStretchRight,
    VertStretchCenter,

    HorStretchTop,
    HorStretchMiddle,
    HorStretchBottom,

    StretchAll
}

public enum PivotPresets
{
    TopLeft,
    TopCenter,
    TopRight,

    MiddleLeft,
    MiddleCenter,
    MiddleRight,

    BottomLeft,
    BottomCenter,
    BottomRight,
}

public static class RectTransformExtensions
{      
    public static void SetStrechAnchor(this RectTransform source, PivotPresets pivot, StrechAnchorPresets allign, Vector2 anchorPosition, Vector2 sizeDelta,Vector2 leftbottom, Vector2 rightup)
    {
        SetPivot(source,pivot);
        string sna = Enum.GetName(typeof(StrechAnchorPresets), allign);
        SetAnchor(source,(AnchorPresets)Enum.Parse(typeof(AnchorPresets),sna), anchorPosition.x, anchorPosition.y);

        switch (allign)
        {
            case StrechAnchorPresets.VertStretchLeft:
                source.offsetMin = Vector2.up * leftbottom.y;
                source.offsetMax = Vector2.up * -rightup.y;
                source.sizeDelta = new Vector2(sizeDelta.x, -leftbottom.y - rightup.y);
                break;
            case StrechAnchorPresets.VertStretchCenter:
                source.offsetMin = Vector2.up * leftbottom.y;
                source.offsetMax = Vector2.up * -rightup.y;
                source.sizeDelta = new Vector2(sizeDelta.x, -leftbottom.y - rightup.y);
                break;
            case StrechAnchorPresets.VertStretchRight:
                source.offsetMin = Vector2.up * leftbottom.y;
                source.offsetMax = Vector2.up * -rightup.y;
                source.sizeDelta = new Vector2(sizeDelta.x, -leftbottom.y - rightup.y);
                break;
            case StrechAnchorPresets.HorStretchBottom:
                source.offsetMin = Vector2.right * leftbottom.x;
                source.offsetMax = Vector2.right * -rightup.x;
                source.sizeDelta = new Vector2(-leftbottom.x - rightup.x, sizeDelta.y);
                break;
            case StrechAnchorPresets.HorStretchMiddle:
                source.offsetMin = Vector2.right * leftbottom.x;
                source.offsetMax = Vector2.right * -rightup.x;
                source.sizeDelta = new Vector2(-leftbottom.x - rightup.x, sizeDelta.y);
                break;
            case StrechAnchorPresets.HorStretchTop:
                source.offsetMin = Vector2.right * leftbottom.x;
                source.offsetMax = Vector2.right * -rightup.x;
                source.sizeDelta = new Vector2(-leftbottom.x - rightup.x, sizeDelta.y);
                break;
            case StrechAnchorPresets.StretchAll:
                source.offsetMin = leftbottom;
                source.offsetMax = -rightup;
                break;
        }
    }
    public static void SetNoStrechAnchor(this RectTransform source, PivotPresets pivot,NoStrechAnchorPresets allign, Vector2 anchorPosition, Vector2 sizeDelta)
    {
        SetPivot(source, pivot);
        string sna = Enum.GetName(typeof(StrechAnchorPresets), allign);
        SetAnchor(source, (AnchorPresets)Enum.Parse(typeof(AnchorPresets), sna), anchorPosition.x, anchorPosition.y);
        source.sizeDelta = sizeDelta;
    }
    private static void SetAnchor(this RectTransform source, AnchorPresets allign, float offsetX = 0, float offsetY = 0)
    {
        source.anchoredPosition = new Vector3(offsetX, offsetY, 0);

        switch (allign)
        {
            case (AnchorPresets.TopLeft):
            {
                source.anchorMin = new Vector2(0, 1);
                source.anchorMax = new Vector2(0, 1);
                break;
            }
            case (AnchorPresets.TopCenter):
            {
                source.anchorMin = new Vector2(0.5f, 1);
                source.anchorMax = new Vector2(0.5f, 1);
                break;
            }
            case (AnchorPresets.TopRight):
            {
                source.anchorMin = new Vector2(1, 1);
                source.anchorMax = new Vector2(1, 1);
                break;
            }

            case (AnchorPresets.MiddleLeft):
            {
                source.anchorMin = new Vector2(0, 0.5f);
                source.anchorMax = new Vector2(0, 0.5f);
                break;
            }
            case (AnchorPresets.MiddleCenter):
            {
                source.anchorMin = new Vector2(0.5f, 0.5f);
                source.anchorMax = new Vector2(0.5f, 0.5f);
                break;
            }
            case (AnchorPresets.MiddleRight):
            {
                source.anchorMin = new Vector2(1, 0.5f);
                source.anchorMax = new Vector2(1, 0.5f);
                break;
            }

            case (AnchorPresets.BottomLeft):
            {
                source.anchorMin = new Vector2(0, 0);
                source.anchorMax = new Vector2(0, 0);
                break;
            }
            case (AnchorPresets.BottonCenter):
            {
                source.anchorMin = new Vector2(0.5f, 0);
                source.anchorMax = new Vector2(0.5f, 0);
                break;
            }
            case (AnchorPresets.BottomRight):
            {
                source.anchorMin = new Vector2(1, 0);
                source.anchorMax = new Vector2(1, 0);
                break;
            }

            case (AnchorPresets.HorStretchTop):
            {
                source.anchorMin = new Vector2(0, 1);
                source.anchorMax = new Vector2(1, 1);
                break;
            }
            case (AnchorPresets.HorStretchMiddle):
            {
                source.anchorMin = new Vector2(0, 0.5f);
                source.anchorMax = new Vector2(1, 0.5f);
                break;
            }
            case (AnchorPresets.HorStretchBottom):
            {
                source.anchorMin = new Vector2(0, 0);
                source.anchorMax = new Vector2(1, 0);
                break;
            }

            case (AnchorPresets.VertStretchLeft):
            {
                source.anchorMin = new Vector2(0, 0);
                source.anchorMax = new Vector2(0, 1);
                break;
            }
            case (AnchorPresets.VertStretchCenter):
            {
                source.anchorMin = new Vector2(0.5f, 0);
                source.anchorMax = new Vector2(0.5f, 1);
                break;
            }
            case (AnchorPresets.VertStretchRight):
            {
                source.anchorMin = new Vector2(1, 0);
                source.anchorMax = new Vector2(1, 1);
                break;
            }

            case (AnchorPresets.StretchAll):
            {
                source.anchorMin = new Vector2(0, 0);
                source.anchorMax = new Vector2(1, 1);
                break;
            }
        }
    }

    private static void SetPivot(this RectTransform source, PivotPresets preset)
    {

        switch (preset)
        {
            case (PivotPresets.TopLeft):
            {
                source.pivot = new Vector2(0, 1);
                break;
            }
            case (PivotPresets.TopCenter):
            {
                source.pivot = new Vector2(0.5f, 1);
                break;
            }
            case (PivotPresets.TopRight):
            {
                source.pivot = new Vector2(1, 1);
                break;
            }

            case (PivotPresets.MiddleLeft):
            {
                source.pivot = new Vector2(0, 0.5f);
                break;
            }
            case (PivotPresets.MiddleCenter):
            {
                source.pivot = new Vector2(0.5f, 0.5f);
                break;
            }
            case (PivotPresets.MiddleRight):
            {
                source.pivot = new Vector2(1, 0.5f);
                break;
            }

            case (PivotPresets.BottomLeft):
            {
                source.pivot = new Vector2(0, 0);
                break;
            }
            case (PivotPresets.BottomCenter):
            {
                source.pivot = new Vector2(0.5f, 0);
                break;
            }
            case (PivotPresets.BottomRight):
            {
                source.pivot = new Vector2(1, 0);
                break;
            }
        }
    }
}

總共兩個函式一個設定有拉伸(四個錨點沒有聚成一個花)的錨點資訊: SetStrechAnchor,一個設定無拉伸(四個錨點聚在一塊)的錨點資訊:SetNoStrechAchor。
pivot, anchors, anchorPosition, sizeDelta,left_buttom_right_up,五個值需要一塊填入。
pivot元素的中心點,總共6種。
StrechAnchorPresets 總共有7種拉伸模式,在inspector介面可以看到
anchors在inspector介面可以看到就在pivot設定的上面 有min 和max兩個vector2數值。
anchorPosition x, y 兩個數值inspector介面有顯示就填入數值沒顯示就填0
sizeDelta x,y 兩個數值inspector介面有顯示就填入數值沒顯示就填0
leftbottom x,y 兩個數值inspector介面有顯示就填入數值沒顯示就填0 x代表left y代表bottom
rightup x,y 兩個數值inspector介面有顯示就填入數值沒顯示就填0 right y代表up