Unity UI个人总结
个人总结,太简单的直接跳过。
一、缩放模式
1.固定像素大小
就是设置一个100x100的方框,在1920x1080像素下在屏幕中长度占比1/19,在3840x2160,方框在屏幕中长度占比1/38。也就是像素长款不变,在屏幕中占比发生变化
2.根据比例缩放
在1920x1080p下是100x100,在3840x2160中,方框的长宽会变成200x200,也就是在屏幕中占比不变。
3.固定物理大小
就是无论在什么分辨率中,在现实世界中拿尺子去量,它的长度都是一样的,不过这取决于是否能正确获取设备的DPI信息,在编辑器模式本身就是不准的,实机才有这个效果。
二、Canvas模式
1.Screen Space-Overlay
显示在一切之上,使用SortOrder对同为ScreenSpace模式的Canvas进行排序。
2.Screen Space-Camera
用相机渲染画布,也就是画布渲染在世界空间中,可以被世界空间中的物体遮挡。
一般单独创建一个UiCamera用于渲染相机模式的画布,UICamera把Culling mask设置为UI层级就可以只渲染UI。
其中PlanetDistance表示画布距离相机的距离,在和3d物体混合显示时使用这个来排序,sorting layer表示排序层,order in layer表示同一层中的order。
同一sorting layer如果order in layer 相同就依靠hireraarchy的顺序排序,如果orderinLayer不同则依靠order in layer排序,数字越大就显示在前。不同sorting layer根据sorting layer的顺序进行排序。
注意Sprite是2D/3D物体,在overlay模式中Ui永远显示在Sprite之前。由于Sprite的Sortinglayer和order layer是2d/3d空间中使用的(其实2d模式也是3d空间),当canvas使用相机模式或者世界空间模式的时候UI 元素与 Sprite可以在同一渲染队列中比较,从而实现跨系统的排序。此使Sorting layer和Order in layer的设置优先于3D空间中的深度判定,比如A画布在Sprite之后,但是如果A画布的Sprite layer较高或者order in layer较高,则A画布会显示在Sprite之前,Sprite和Sprite对比也是同理。
3.World Space
画布直接被放置在世界空间中,默认使用深度排序,可以被遮挡,与其他相机模式的画布,世界空间的画布,Sprite进行排序时也可以使用sortingLayer和order in layer进行排序。
三、锚点,AnchroedPosition,Pivot
图中绿色为父物体Image,白色为当前选中Image。
中间的空心圆是pivot,表示枢轴点(轴心点),旋转和缩放基于这个。取值范围为0-1,表示在自身UI的长宽的百分比。
anchordPostion,当四个锚点在同一个点时表示pivot相对于这个点的偏移量。比如:
此时 anchordPosition=(x,y);
但是,当锚点设置为区域时情况就大不相同了。
如图,锚点设置为(0.25,0.25)和(0.75,0.75).在说明这种情况下需要先了解下锚点的作用:
锚点设置的左下角(min)和右上角(max),他是基于父物体的,相当于设置了当前Ui在父物体中的的基准区域,设置基准区域后再在inspector中设置相对位置。当左下角和右上角锚点在同一个位置时,相当于区域设置为0,它相对于父物体的大小就是0,所以父物体长宽变换时子物体的长宽不动,但是位置会跟着变化,因为锚点中的数值其实就代表在父物体中的百分比位置。而如果设置在不同的点就会构成一个区域,这个区域占有一定的面积并且四个角也设置好了位置,当父物体变换时,这个基准区域会保持和之前相同比例进行长宽变换,此时inspector面板就变成了left,top,right,bottom的设置了,这个表示Ui和基准区域的四条边的距离。
重点来了:区域模式的anchoredPostition的计算,这个我翻遍百度谷歌和各种ai没找到具体的计算公式,最后自己测试出来的。anchoredPosition=图形和基准区域左下角的相对距离+图形和基准区域的大小的插值*pivot.
具体什么意思呢?如图:

和基准区域相同位置和大小时,无论如何移动pivot,anchoredPosition都为(0,0),此时白色长宽为(100,100)

整体移动后,anchoredPosition为(50,50)

整体移动后再拉伸,anchoredPosition=(50,50)+(50,0)*(0.5,0.5)=(75,50),就是相对距离+长宽变换*pivot。

此时移动Pivot为(1,0.5),结果变为(50,50)+(50,0)*(1,0.5)=(100,50)
同理,如果把pivot改为(0,0.5)结果就是(50,50)
四、anchordPosition和localPosition,position的区别
anchoredPosition上面已经说了,而llocalPosition和position都是基于Transform的,只有当Pivot改变时数值才会改变,localPosition和position使用的数值和分辨率是相对应的。要注意的是pivot位置并不是只会主动改变,当修改长宽的时候pivot也会跟着被动改变,注意这里说的是pivot的位置改变,不是pivot的值改变,要注意区分。
五、使用代码设置位置
1.rectTransform.anchoredPosition = targetAnchoredPos;
和获得anchoredPosition不同,设置的时候无论锚点是固定点还是自定义区域都是直接设置轴心点到锚点的相对位置。
2.sizeDelta和SetSizeWithCurrentAnchors
在 Unity UI
中,sizeDelta
和 SetSizeWithCurrentAnchors
都用于设置 RectTransform
的尺寸,但它们的工作方式有所不同。
rectTransform.sizeDelta=sizeDelta;
sizeDelta直接设置RectTransform的宽度和高度,当锚点固定时,他直接设置尺寸。但是当锚点为自定义区域时,它代表的是额外的尺寸,如第三部分中的100x100的白色方框,sizeDelta设置为(10,0)的时候,白色方框的尺寸变为110x100。
SetSizeWithCurrentAnchors
也是设置 RectTransform
的尺寸,但它会把anchor
的影响考虑在内,计算最终的 sizeDelta
,以确保视觉上符合指定的尺寸。
当SetSizeWithCurrentAnchors的时候,无论锚点如何,都是把尺寸长度设置为目标值。
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal,sizeDelta.x);
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical,sizeDelta.y);
3.offsetMin / offsetMax
rectTransform中的OffsetMin/OffsetMax属性用于设置相对于Anchors的偏移量,就是自定义区域模式中的inspector面板上的left,bottom,right,up这些属性。这个对固定锚点也有效,因为固定锚点其实也是有anchormin和anchorMax构成的,只是他们的位置相同而已。
如果先设置SizeDelta(无论哪种方式)再设置offset的话offset会覆盖尺寸的设置。
4.rectTransform.Rect
rectTransform.rect
返回的是一个 本地空间(local space) 下的 矩形区域,它主要用于获取 UI 元素的实际大小和边界信息。
4.1.rect.size和rect.width,rect.height,他们就是表示实际的区域的长宽。
4.2.rect.xMin,rect.xMax,rectyMin,recct.yMax。
表示的是相距pivot的距离值,如100x100的方框中,pivot为0.5,0.5时,xMin=-50,yMin-=50,xMin=50,xMax=50。如果pivot为0,0那么xMin=0,yMin=0,xMax=100,yMax=100。如果要获取世界中边界位置,需要使用GetWorldCorners()方法获得世界空间下的边界。还有,x等效于xMin,y等效于yMin
5.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left,50,200);
此方法表示的是贴边操作,选择Edge后,它会自动设置锚点到父物体对应边并设置和边的间距和ui的长宽,常用于制作侧边栏之类的效果。注意,他不是设置和锚点的间距,而是父物体的边的间距,因为它本身就会修改锚点到指定的边。等效于以下代码:
rectTransform.anchorMin = new Vector2(0, rectTransform.anchorMin.y);
rectTransform.anchorMax = new Vector2(0, rectTransform.anchorMax.y);
rectTransform.anchoredPosition = new Vector2(50 + 200 * rectTransform.pivot.x, rectTransform.anchoredPosition.y);
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 200);
六、UI特效
1.制作Ui特效的几种方式
粒子特效的大部分图片来自B站Up主@别看着我笑了,我自己懒得作图,直接截取的它的视频里的图,应该不算侵权吧,毕竟我这个基本只算是个笔记,写出来防止以后自己忘掉到处翻。别看着我笑了https://space.bilibili.com/457739566
1.1.修改UI网格
比如UGUI中常用的文字的outline效果和Shadow效果,Mask效果,图片的FillType效果等就是修改Ui网格实现的。

1.2.使用自定义Shader

unity的Image组件会自动把Sprite传递给shader的_MainTex参数,然后编辑shader就可以制作Ui的shader效果了,包括边框流动,反光,置灰等效果。
比如置灰的效果:
half4 frag (v2f i) : SV_Target
{
half4 color = tex2D(_MainTex, i.uv);
float gray = dot(color.rgb, half3(0.299, 0.587, 0.114));
return half4(gray, gray, gray, color.a);
}
再比如边框流光的效果:
shader代码太长核心部分:
half4 frag (v2f i) : SV_Target
{
half4 color = tex2D(_MainTex, i.uv)*_Color;
half4 flowingColor=tex2D(_FlowingTex,i.uv1);
if(color.a>0.1)
{
return color+flowingColor;
}
return color;
}



1.3.粒子特效
Ui和粒子特效有时候需要结合使用。粒子特效是3d空间的,在overlay的Canvas下建一个粒子特效是根本看不见的。所以需要Cmaera模式的Canvas来配合使用。这种情况下就需要一个单独的Ui相机,这个ui相机需要叠加到主相机上,所以需要设置模式为Depth Only,然后主相机就可以去除掉Ui的渲染,UI相机则用来渲染需要和3d空间排序的UI。

关于图中右上角的正常Ui,这是要表达无需和3d空间排序的Ui放在overlay模式的canvas中就可以了。图中右下角所说的"放所需背景“的含义是这种模式下UI主要是作为特效的背景进行使用。
camera模式常用的特效效果如下


上面这张图片就是用两种canvas制作的,首先是camera模式的Ui,上下两个横幅Ui充当商人的背景。
然后是正常Ui在最前面遮挡所有的
1.4.更复杂的粒子特效

上图看着有点复杂,其实简而言之,就是复杂的内容需要自己处理,就比如给粒子系统加上遮罩,那就获取到particle system然后在OnPopulateMesh方法中获得单个粒子的quad判断位置决定显隐就行了。不过一般用插件就行了,我倒是没用过,好像UIParticle不错,
1.5.动画/序列帧动画
上面的内容很多情况下都可以考虑使用动画系统,包括流光,闪光,这些其实都可以用动画系统控制相关的位置来达到相关效果,包括加载动画的粒子特效,遮罩等。
七、UI技巧
1.自适应
这个算是非常常见的需求了,比如文本自适应大小,提示框自适应大小。
首先是文本自适应大小,这个很简单,直接在Text上加ContentsizeFilter然后设置要扩展的方向为prefeeredSize就行了 。简单来说,ContentSizeFilter就是根据Text等能够提供期望宽高的提供的期望宽高来动态调整自身宽高的组件,所以可以自适应宽高。

然后是对话框自适应大小 。
要制作对话框,首先创建一个Image把文本框包裹起来,文本框使用和上面文本自适应一样的设置。然后在Image上添加一个布局组件,gridLayoutGroup,verticalLayoutGroup,HorizontalGroupLayout都行,取决于你的文本框想要横向自适应大小还是竖向。添加布局组建后再添加一个ContentSizeFilter就可以并设置自适应方向为PreferredSize就行了。此时自适应对话框就制作完成了。简单来说,就是文本会自适应宽高,然后背景图片的layoutGroup组件可以获取子物体的宽高然后得到自己的期望宽高,背景图片上的ContentSizeFilter得到这个期望宽高再调整自己的宽高,就完成了自适应。
要制作多个子级的话原理也是一样,背景添加一个布局组和ContetntSIzeFilter就行,然后各个子物体添加contentsizeFilter就行。
如图,黑色背景添加VerticalLayoutGroup,contentSizeFilter,ContetntSizeFilter设置为纵向扩展,然后添加自定义文字区域(添加ContentSizeFilter),然后添加两张图片,图片不需要ContentSizeFilter,因为他的长度不是自适应变化的,然后再加一个文字区域(添加ContentSizeFilter)。
其实上面的方法不是特别标准,也会有上图中的警告,虽然能达到效果。这就需要引出layoutGroup的知识,这一块很复杂,懒得学的话也可以直接就用上面的方法制作自适应。
1.1.LayoutGroup控制
以下LayoutGroup相关内容来自B站UP主@Async_Officia的视频,我对其做了文字版总结。
Unity的Horizontal Layout Group 和Vertical Layout Group的作用
首先建立如图的UI结构,黑色背景,长宽为900x900,挂载HorizontalLayoutGroup,关闭ChildForceExpand,然后添加红绿蓝三个Image,它们都是正方形,长度分别为100,200,200。
1.1.1.Child Force Expand
ChildForceExpand是将剩余空间分配各个子物体,简单来说,有几个子物体就把剩余空间等分后分给子物体。当勾选childForceExpande的width,变为如下效果
然后上图中可以看到剩余空间均分后构成了三个区域,child Alignment就决定子物体在区域的哪一部分,childLayoutment中前面的Uppe,middle,lower等表示在父物体的上中下方,后面Left,center,right的表示子物体在分成的区域中的左中右
就比如这个例子中把upperLeft改为upperCenter就会发现子物体在区域的中间
1.1.2.ControlChildSize
先关闭Child Force Expand,单独打开ControlChildSize的Width,会发现子物体都看不见了
此时子物体的width都变成了0,因为没有宽高信息,如果Image的Sprite不为空的话其实就有宽高信息,就不会变成0,但是Sprite为空的时候就没有宽高信息,这个时候子物体的width就由布局组件来控制了,不再允许手动设置的大小。此时我们就需要LayoutElement来告诉它我们想要的宽度和高度。当然了,在Image有sprite可以正常显示的情况下LayoutElement也可以覆盖Sprite的宽高信息
如图,把期望宽度(preffered)设置为100就会显示了,还有一个MinWidth我没有设置,它是最小宽度,如果设置了,当布局组件发生长宽变化的时候如果父物体空间不够的话子物体的长度宽度就会缩小,缩小到MinWidth的时候就不会再继续缩小了,之后就会强行使用MinWidth来显示宽度。Height也是同理,只不过我们没有勾选Height,勾选ControlChildSize的Height后就可以用同样的方式来控制子物体的长宽。
1.1.2.1.flexible width
当父物体的空间大于所有子物体的期望空间之和的时候(这里是没有勾选Child Force Expand的情况),如果勾选Flexible width,子物体就会根据flexible width的比例来侵占剩余空间。比如都没有勾选,那就都是0,所以不会侵占空间,如果有一个勾选并设置大于0的值,那么那个就会侵占所有剩余空间。有两个勾选了并且设置的数值相等,那么就两个子物体每个占50%,如果他们设置的值不等,比如绿色设置1,蓝色设置3,那么绿色就获得剩余空间的25,蓝色获得剩余空间的75%。注意,这里的侵占是和Child Forece Expand不同,这里是直接更改子物体的长宽,因为勾选的就是Control child Size。
1.1.3.Control Child Size和Child Force Expand结合使用
首先要知道的是,ChilForceExpand会把多余区域分给子物体构成子物体区域,子物体可以在其中设置左中右位置。
然后Control Child Size使用Layout Element控制最小长宽(Min Width),期望长宽(Preferred Width)和可变长宽(Flexible Width),MinWdith决定最小宽度,Preferred Width决定期望宽度,当子物体剩余空间不足时会向最小宽度缩小,而当子物体剩余空间足够而又没有启用可变长宽,那么自子物体会保持期望长宽。当子物体剩余空间足够又启用了可变长宽,那么子物体会根据可变长宽比例瓜分剩余空间。
如果结合起来使用,勾选child Force Expand后会对子物体进行分区,此时勾选Control child size,那么就会强制所有Flexible为1以侵占剩余空间,此时修改每个子物体的FlexibleWidth也是有效的,但是只能设置大于等于1的值。这种情况和只勾选control child size然后把所有子物体的flexible设置为1的效果是一样的。因为child Force Expand分区的时候已经把剩余区域根据比例分给子物体了,所以这里认为子物体是侵占了分区的空间和按比例侵占了剩余的空间是等价的。
关于横向布局中的childForceExpand中的Height和Control childSize中的height。把childForceExpand理解成把剩余高度都分到一个区域内就行了,此时如果再勾选Control child Size的height,那就是填满整个区域。就算不勾选childForceExpand,只勾选Control childSize也会把高度自动铺满整个空间。
1.1.4.UseScale
这个就很简单了,勾选后再布局的时候把缩放也考虑在内而已,比如绿色是200长度。勾选后缩放改为2,计算的时候就把绿色当作400长度计算布局。
1.2.标准的的自适应Ui设置方法
在学习了上面的知识后就可以知道标准的自适应内容方法了。
之前说过,LayoutGroup组件是基于子物体的PreferedHeight或者PreferredWidth来计算布局的,没有Sprite的Image的Preferred宽高为0,所以要用LayoutElement来提供。但其实Text组件,InputField组件都会实时提供这些属性,所以layoutGroup可以直接根据这些属性来控制他们的宽高以实现自适应宽高,带有Sprite的Image组件没有明说会提供,但是应该也是提供的。对了,前提是开启了ControleChildSize
所以,LayoutGroup本身就可以让子物体自适应宽高而无需让子物体添加ContentSizeFilter,图片等要控制宽高的话就添加LayoutElment就可以设置期望宽高。
如图,分别为自适应高度文字区域1,两个图像,和自适应高度文字区域2,他们都没有添加ContentSIzeFilter和LayoutElement,如果觉得图片小了,添加LayoutElement设置就可以了。

但是还有个问题就是,背景没有随着所有子物体所占区域进行自适应。之前也说了GroupLayout本身会提供所有子物体所占的PreferredSize,所以再给LayoutGroup组件添加一个ContentSizeFilter,并设置verticalFit为prefferedSize就行了。
1.2.1.更新不及时导致的bug
Unity 的 Layout Group
组件默认不会在每一帧都刷新布局,它通常会等到 End of Frame
或 LateUpdate
之后才会刷新。因此,你可以在文本变动后手动强制刷新 Layout。
解决方法: 在修改 Text
内容后,手动调用
LayoutRebuilder.ForceRebuildLayoutImmediate(targetPanel);
具体例子:
void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) // 模拟文本变化
{
textComponent.text += " 新增文本";
ForceRefresh();
}
}
void ForceRefresh()
{
//targetPanel 需要是 GridLayoutGroup 所在的 RectTransform(或者更高一级的 Layout Group)
LayoutRebuilder.ForceRebuildLayoutImmediate(targetPanel);
}
1.3.安全区
安全区是指现在手机有许多异性屏,某些地方不应该显示Ui,不然就被手机的刘海或者前置摄像头挡住了,因此要修改画布的锚点来避开显示。
Unity提供了Scrren.safeArea来获取当前设备的安全区域,用它来动态调整UI布局。
using UnityEngine;
public class SafeAreaHandler : MonoBehaviour
{
private RectTransform rectTransform;
private Rect lastSafeArea = new Rect(0, 0, 0, 0);
void Start()
{
rectTransform = GetComponent<RectTransform>();
ApplySafeArea();
}
void ApplySafeArea()
{
Rect safeArea = Screen.safeArea;
if (safeArea != lastSafeArea) // 只有在区域变更时更新
{
lastSafeArea = safeArea;
// 转换 SafeArea 到 UI 坐标系
Vector2 anchorMin = new Vector2(safeArea.x / Screen.width, safeArea.y / Screen.height);
Vector2 anchorMax = new Vector2((safeArea.x + safeArea.width) / Screen.width,
(safeArea.y + safeArea.height) / Screen.height);
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
}
}
void Update()
{
// 处理屏幕旋转,避免适配失效
if (Screen.safeArea != lastSafeArea)
{
ApplySafeArea();
}
}
}
七、UI优化
待续,说实话个人感觉Ui没必要优化,能耗多少性能?