Unity UI 適配問題彙總
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