UGUI的自适应解决方案
什么是UI自适应?
不同设备的分辨率,宽高比不同。如4K屏幕的像素数是1K屏幕的四倍,在1K屏幕上显示正常的窗口,到了4K屏幕上就会变成原来的四分之一面积,这时候玩家就看不清了。宽高比相同的话我们只要同时缩放即可。
1.以Canvas为根节点,其RectTransform的宽高等于屏幕分辨率。
2.CanvasScaler组件将下面的所有节点都缩放一定倍数。
UIScaleMode为ConstantPixelSize时,固定缩放一定倍数。
UIScaleMode为ScaleWithScreenSize时,根据屏幕分辨率变化。写入基础分辨率,然后自动根据实际分辨率进行缩放。
UIScaleMode为ConstantPhysicalSize时,根据屏幕实际尺寸变化。
而宽高比不同时,需要合理的设置来保持不同UI界面的相对位置。UGUI提供了与父物体角位置不变,与父物体保持边距,与父物体保持比例等方式,下面将详细介绍。
RectTransform的基本字段
在Inspector上右键然后选中Debug,可看到RectTransform上实际存储的字段。
|------------------|---------|
| Anchor | 0~1的比例 |
| AnchoredPosition | 像素值 |
| Pivot | 0~1的比例 |
| SizeDelta | 像素值 |
这四个值是rectTransform的基础属性,其他属性由他们计算得出。
绝对模式的计算流程

当Anchor的最大最小相同时是绝对模式,仅确定一个点。
1.根据Anchor值,按比例在父物体上计算出一个点,可以视为起点。
2.根据AnchoredPosition计算一个偏移,直接加在上一步起点上,计算出一个点,作为中心点。
3.我们最终会计算出UI占据的框,anchors确定了这个框的中心位置。
4.SizeDelta描述了框的宽高。其中心点位置由anchors确定。
编辑器显示上,posX和posY显示的是AnchoredPosition。WIdth和Height显示的是sizeDelta。
相对模式的计算流程

此模式下Anchors的最大最小值不同,确定一个矩形范围。
编辑器显示上,会显示left,right,top,bottom,即上下左右。即实际框距离父物体确定的锚框的像素距离,当父物体锚框改变时,上下左右的距离不变,根据这个计算出节点范围。可实现与父物体上下左右边距的自适应。
当left,right,top,bottom都为0时,字节的大小根据Anchors成比例改变。由此可实现与父物体左半边或者右半边保持一致的自适应。
关于相对模式下left,right,top,bottom与基础值的互相转换
内部代码计算流程如下
1.先根据父物体大小和锚点设置,计算锚框
2.根据pivot确定一个锚点中心点。是的,这步也使用了中心点计算。
3.然后锚点中心点加上AnchoredPosition实现偏移,得到中心点。
4.最后根据实际大小,pivot和中心点,计算节点实际范围。
cs
var rtf = transform as RectTransform;
if (!rtf) return;
var parentRTF = rtf.parent as RectTransform;
if (!parentRTF) return;
var parentWidth = parentRTF.rect.width;
var parentHeight = parentRTF.rect.height;
// 先根据父物体大小和锚点设置,计算锚框高度和宽度
var anchorWidth = parentWidth * (rtf.anchorMax.x - rtf.anchorMin.x);
var anchorhHeigth = parentHeight * (rtf.anchorMax.y - rtf.anchorMin.y);
// 计算节点实际高度和宽度。
// 绝对模式下anchorWidth为0,sizeDelta为正,大小与实际宽高相同。
// 相对模式下anchorWidth不为0,sizeDelta大小为-(left+right)
var realWidth = anchorWidth + rtf.sizeDelta.x;
var realHeight = anchorhHeigth + rtf.sizeDelta.y;
// 根据pivot,在锚框中确定一个锚点,然后用anchoredPosition偏移得到中心点,再用实际大小和pivot计算得到实际节点大小。
// 由此我们可以确定上下左右的表达式。
var left = (anchorWidth - realWidth) * rtf.pivot.x + rtf.anchoredPosition.x;
var right = (anchorWidth - realWidth) * (1 - rtf.pivot.x) - rtf.anchoredPosition.x;
var bottom = (anchorhHeigth - realHeight) * rtf.pivot.y + rtf.anchoredPosition.y;
var top = (anchorhHeigth - realHeight) * (1 - rtf.pivot.y) - rtf.anchoredPosition.y;
// 将realWidth带入化简得到
left = -rtf.sizeDelta.x * rtf.pivot.x + rtf.anchoredPosition.x; //1
right = -rtf.sizeDelta.x * (1 - rtf.pivot.x) - rtf.anchoredPosition.x; //2
bottom = -rtf.sizeDelta.y * rtf.pivot.y + rtf.anchoredPosition.y; //3
top = -rtf.sizeDelta.y * (1 - rtf.pivot.y) - rtf.anchoredPosition.y; //4
////////////////////////////////////////////
// 根据上下左右的表达式,稍加化简即可得到sizeDelta和anchoredPosition的表达式
var sizeDelta = new Vector2(
-left - right,
-top - bottom);
var anchoredPosition = new Vector2(
left - (left + right) * rtf.pivot.x,
bottom - (bottom + top) * rtf.pivot.y);
// 由此我们得到了相对模式下left,right, top, bottom的互相转换方法
showText.text = $"left = {left}, top = {top}\n" +
$"right = {right}, bottom = {bottom}\n" +
$"sizeDelta = {sizeDelta}\n" +
$"anchoredPosition = {anchoredPosition}\n" +
$"rect={rtf.rect.size}";
// 建议使用RectTransform.rect.size来获取大小,这个办法不因锚点设置改变。
此设计的优点
可以看到,UGUI这个位置确定十分繁琐,那么他到底实现了那些功能?
1.AnchoredPosition和其他软件中位置功能类似,可以视为位置偏移量。
2.Anchor在这个位置的基础上,实现了位置起点和终点能自由设置,而其他软件中起点终点往往固定。这样在我们做从左到右平铺的列表时,将Pivot设为最左就能很轻松的计算每个元素坐标。
3.Pivot确定的中心点,也是是缩放和旋转的中心,可以控制元素如何缩放和旋转。比如控制放大时,仅向下增加高度,或者仅向上增加宽度。
4.优雅的实现屏幕自适应。将Anchor设置到右上角,就能实现固定在屏幕右上角的小地图等。
5.同时能实现与父物体保持边距的自适应,和与父物体保持比例的自适应。
此设计的缺点
1.总体设计相当复杂,难以理解和上手。
2.难以比较两个非同父物体节点的位置。
3.想要将一个节点设置到与另一个节点大小位置相同,会很困难。
快速设置锚点和中心点的小技巧

点击红框区域会出现锚点预设窗口。点击可快速设置锚点位置,实际使用中基本都是这里面的几个预设。按住shift可同步设置中心点,按住alt可同步修改位置。
关于UGUI的三个坐标系和坐标转换
屏幕坐标指真实分辨率下像素的坐标,根据设备不同,分辨率不同,有很大区别。
UGUI坐标指在UGUI内使用的坐标,我们在Inspector上看到的数值都是UGUI坐标,他与设备分辨率无关,根据自适应流程自动转换为屏幕坐标。
世界坐标指ui的3D坐标。所有UI最终都会转换为3D空间中的面片渲染,因此每个UI都会有世界坐标。
一些接口返回的坐标会是其他坐标系下的,这时就需要我们进行坐标转换。例如:
Input.mousePostion获取屏幕坐标(绝对坐标)
recttransform.postion获取UI的世界坐标(绝对坐标)
recttransform.ancherpostion获取锚点坐标,即UGUI坐标(相对坐标)
RectTransformUtility.ScreenPointToWorldPointInRectangle屏幕坐标转世界坐标
RectTransformUtility.ScreenPointToLocalPointInRectangle屏幕坐标转UGUI坐标
在三个坐标系下转换可见下图

如果要将点在不同UGUI坐标下转换,可以先转成世界坐标,再转成本地坐标。