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