UGUI的原理和优化

UGUI介绍:

    IMGUI : 是Unity自带得到古老UI系统。

    NGUI : 是最流行的第三方UI插件。

    FairyGUI : 是跨平台的UI系统。(小游戏居多)

    UGUI : 是官方版本。

    UI Element : 是最新版本的UI系统。(Unity2019版)

那么这么多的GUI,为什么选择UGUI呢? 因为团队最擅长(重点),官方支持,流行趋势,多线程支持(UI重建)

UGUI开源地址 : https://bitbucket.org/Unity-Technologies/ui

UI基础控件 : 

Text 文本 , Image 图片 , RawImage 原图片 , Mask 遮罩 , Effects 特殊效果(阴影,描边等)

UI交互控件 : 

Button 按钮 , Toggle 开关 , Slider 滑动条 , Scrollbar 滚动条  , Input Field 输入框 , Scroll Rect 滚动视图 , Dropdown 下拉框

UI布局控件 : 

Horizontal Layout Group 水平布局 , Vertical Layout Group 垂直布局 , Grid Layout Group 网格布局

                                        相机和画布

Canvas : 画布是所有UI元素的父元素,就像优化的画布一样,控制者对UI元素的渲染。也就是说所有UI控件都需要作为Canvas对象的子对象才可以被渲染出来。

渲染顺序 : 画布上的UI元素按照排列顺序渲染。位于上方的元素先被渲染,下方的元素后被渲染。对于嵌套的结构,先渲染父类,在渲染子类。

渲染模式: Canvas有三种渲染模式 : 

Screen Space - Overlay:不需要指定UI相机,渲染会覆盖整个画面,永远在屏幕的最上面。

Screen Space - Camera:需要指定UI相机,画布会被放置在相机前,通过该相机渲染。

World Space:把画布当成普通的3D对象放置在世界坐标系中,画布可以自由移动旋转。

适配策略 : 

UI Scale Model : 

Scale With Screen Size:UI元素的位置和大小是根据指定的标准分辨率来设置。(该模式在不同分辨率的设备上会自动适配)

Constant Pixel Size:UI元素的位置和尺寸是由画面上的像素单位来设置。

Constant Physical Size:UI元素的位置和尺寸可以使用物理单位(厘米,英寸等)来设置。

Screen Match Mode:当屏幕分辨率与标准分辨率的宽高比例不一致的时候,画布如何缩放

Match Width or Height:按照标准分辨率的宽度或高度来缩放画布。

Expand:扩展画布,画布的宽和高同时扩展,画布的尺寸不会低于标准分辨率。在此模式下会保证标准分辨率下的东西能够全部显示出来。

Shrink:收缩画布,画布的宽和高同时收缩,画布的尺寸不会高于标准分辨率。在此模式下不会留黑边但是会导致显示不完全。

                                    布局系统

RectTransform 组件:锚点,轴心,坐标值,旋转值,缩放值。

Blueprint模式:未采用旋转或缩放的矩形框。

RawEditor模式:在编辑pivot时,pivot会始终保持在UI内的相对位置,UI跟随pivot的位置移动。

锚点:UI元素矩形框的位置和大小,以锚点在父物体的矩形框内的位置作为自身偏移和拉伸的参考。

锚点在编辑视图里是四个小三角形(锚点控制柄),这四个小三角组成里了一个锚框。

锚框的范围不能超过父类的矩形框,锚框的四个控制柄可以聚合到同一个位置。

为了便于理解界面里的数值,我们给锚点定义了俩种形态:锚点形态和锚框形态

锚点形态:物体的大小不会随着父物体的大小变化而变化,但是位置会根据轴心到锚点的距离一致的原则发生对应的变化。

锚框形态下界面里数值的意义:下图中,在黑框大小和位置变化的时候,会保证红框的左下角到锚框的左下角距离不变,同时红框的右上角到锚框的右上角距离不变。

锚点界面里数值的意义:Anchors的Min和Max分别是正规化的值(从0到1),表示占父矩形框的百分比。

RectTransform组件重要属性 : 

轴心点就是UI元素旋转和缩放的基准点。

锚点在代码里是由2个位置信息组成(对应界面里的Anchors Min 和 Anchors Max),这俩个位置决定里锚框的范围。

计算出来的UI元素的最终矩形框,该矩形框也是图片填充的范围。(只读属性)

锚点形态下表示的是UI元素的尺寸( size )

锚框形态下表示的是UI元素和锚框的尺寸差( delta )

所以这个变量被起名为sizeDelta

Question:在锚框形态下要怎么样才能获得UI元素的尺寸?

Recttransform.rect.width

Recttransform.rect.height

Question:在锚框形态下又怎么样去设置UI元素的尺寸?

Recttransform.SetSizeWithCurrentAnchors(Animations.Axis axis,float size)

通俗讲,在锚点形态下,该值就是表示锚点到UI轴心的矢量值。

通俗讲,在锚框形态下,该值就是表示锚框里某一个点到UI轴心的矢量值。

UGUI原理的重要概念:

Canvas Batch:Canvas下的UI元素最终都会被Batch到同一个Mesh中,而在Batch前,会根据这些UI元素的材质(通常就是Atlas)以及渲染顺序进行重排,在不改变渲染结果的前提下,尽可能将相同材质的UI元素合并在同一个SubMesh中,从而把DrawCall降到最低。Batch的结果会被缓存复用,直到这个Canvas被标记为dirty。

Unity官方的重要提示:当给定Canvas上的任何可绘制UI元素发生更改时,Canvas必须重新执行合批过程。此过程重新分析Canvas上的每个可绘制UI元素,不管它是否被修改。注意,“更改”是指影响UI元素外观的任何变动,包括修改sprite renderer的sprite、transform的position和scale、文本网格的text等。

�Canvas嵌套:Canvas可以嵌套使用,一个子Canvas下dirty的子物体不会触发父Canvas的rebuild。

网格重建因素 : 

UI顶点属性变化会引发网格更新

修改Image、Text的color属性,会改变UIVertex.color

修改RectTransform的Size、Anchors、Pivot等,会改变UIVertex.position

注意:在UGUI中颜色的变化是通过修改顶点色实现的,避免生成了新的DrawCall

注意:UIVertex.position记录的是本地空间下的坐标

Rebuild流程图


流程图说明:

该过程由CanvasUpdateRegistry监听Canvas的WillRenderCanvases(上图中1)而执行,主要是对当前标记为dirty的layout和graphic执行rebuild。

在rebuild layout之前会对Layout rebuild queue中的元素依据它们在heiarchy中的层次进行排序(上图中的2),排列的结果是越靠近根的节点越会被优先处理。

rebuild layout(上图中的3),主要是执行ILayoutElement和ILayoutController接口中的方法来计算位置,Rect的大小等布局信息。

rebulid graphic(上图中的4),主要是调用UpdateGeometry重建网格的顶点数据(上图中5)以及调用UpdateMeterial更新CanvasRender的材质信息(上图中6)。

合批原理:

 UGUI的合批规则是进行重叠检测,然后分层合并。

第一步计算每个UI元素的层级号:如果有一个UI元素,它所占的矩形范围内,如果没有任何UI在它的底下,那么它的层级号就是0(最底下);如果有一个UI在其底下且该UI可以和它Batch,那它的层级号与底下的UI层级一样;如果有一个UI在其底下但是无法与它Batch,那它的层级号为底下的UI的层级+1;如果有多个UI都在其下面,那么按前两种方式遍历计算所有的层级号,其中最大的那个作为自己的层级号。

第二步合并相同层级中可以Batch的元素作为一个批次,并对批次进行排序 :有了层级号之后,Unity会将每一层的所有元素进行一个排序(按照材质、纹理等信息),合并掉可以Batch的元素成为一个批次。经过以上排序,就可以得到一个有序的批次序列了。这时Unity会再做一个优化,即如果相邻间的两个批次正好可以Batch的话就会进行Batch。合批的Batch数据,最后会分别放在CanvasMesh的SubMesh里。

                                            UGUI优化

优化Unity UI系统的首要任务是找到性能问题的准确原因。

开发过程中四个常见的问题

过多的GPU片段着色器使用率(如屏幕填充率过高)

过多的CPU时间开销在重建一个画布上

过多的CPU时间开销在生成顶点上(通常是文本)

过多的画布重建次数

针对这四个问题来分组介绍优化策略

网格重建优化策略

屏幕填充率优化策略

合批优化策略

字体优化策略

滚动视图优化策划

其它优化策略

网格重建优化策略(Mesh)

使用尽可能少的UI元素:在制作UI时,一定要仔细查检UI层级,删除不必要的UI元素,这样可以减少深度排序的时间以及Rebuild的时间。

减少Rebuild的频率:将动态UI元素(频繁改变例如顶点、alpha、坐标和大小等的元素)与静态UI元素分离出来,放到特定的Canvas中。

谨慎使用UI元素的active操作:因为它们会触发耗时较高的rebuild。

谨慎使用Canvas的Pixel Perfect选项:该选项的开启会导致UI元素在发生位移时,其长宽会被进行微调(为了对齐像素),从而造成layout Rebuild。(比如ScrollRect滚动时,会使得Canvas.SendWillRenderCanvas消耗较高)

Animator最佳用法: Animator每帧都会改变元素,即使动画中的数值没有变化,因为Animator没有空指令检查。对于仅响应事件时才变化的元素,可以自行编写代码或使用第三方补间插件。

谨慎用Tiled类型的Image

屏幕填充率优化策略(OverDraw):

禁用不可见的面板:比如当打开一个系统时如果完全挡住了另外一个系统,则可以将被遮挡住的系统面板禁用。(龙与少女优化方案:通过修改Canvas对象的Layer隐藏面板。)

不要使用空的Image做按键响应:在Unity中Raycast使用Graphic作为基本元素来检测touch。如果使用空的image也会产生不必要的overdraw。可以实现一个只在逻辑上响应Raycast但是不参与绘制的组件即可。

Polygon Mode Sprites:如果图片边缘有大片留白就会产生很多无用填充。Unity和Texture Packer目前都支持了Polygon Mode,也就是说将原来的矩形Sprite用更加紧致的Polygon来描述。

Image Fill Center:在Image Type选项为Sliced的情况下,不需要Fill Center的时候去掉勾选。

合批优化策略(DrawCall):

相同层级原则:父节点下所有子节点,尽量保持相同的层次结构。相同层级下的UI元素可以Batch。

Mask组件:Mask组件使用了模版缓存,Mask中的UI元素无法与外界UI元素合批,Mask组件还会额外增加2个DrawCall.

隐藏的Image:Image组件中sprite为空,都是占用drawcall渲染的,并且还会打断前后元素的合批。

Screen Space-Camera模式:一个Canvas中的任何一个UI元素只要在屏幕中,则这个Canvas中的其他UI元素即使在屏幕外DrawCall仍不会减少。

Hierarchy穿插重叠问题:如下图红点和Icon在不同图集中,如果红点稍微大一点,遮挡了旁边的Icon,就不能合批,须要调整Icon和红点的节点关系,4个Icons放在一个节点下,4个红点放在一个借点下。


字体优化策略(Font):

字体图集的重建机制:当一个新文字出现的时候,会被添加到字体图集,如果图集已经没有空余的地方,那么图集会被重建。图集会以相同的尺寸重建,打包当前激活的所有UI text组件中要显示的文字,如果发现图集尺寸不够用的时候,图集会重新扩充尺寸。

后备字体机制:对于字体库里没有的文字,会被放进后备字体图集里,后背字体图集会常驻内存里,不会被销毁。后备字体取自于系统自带的系统字库Arial.ttf,在发布的游戏安装包里该字库是不存在的。我们在一些Unity开发的游戏里,偶尔会发现一些生僻字的字形和其它常见文字的字形不统一。

Text的网格重建:Text组件被重新启用的时候,会重建Text的网格。如果含有大量的文字,会造成严重的CPU开销。

提前生成动态字体:准备游戏非常常用的文字集合,通过Font.RequestCharactersInTexture接口提前放入字体图集里。注意使用Font.textureRebuilt 委托,在字体图集被重新重建的时候,把我们提前准备的文字集合再次添加进去。

使用美术数字:游戏的分数,可以使用美术数字(精灵图片)来代替Text组件。

谨慎使用Text的Best Fit选项:虽然这个选项可以动态的调整字体大小以适应UI布局而不会超框,但其代价是很高的,Unity会为用到的该元素所用到的所有字号生成图元保存在图集里,不但增加额外的生成时间,还会使得字体对应的图集变大。

减少长文本Text的变动,慎用UI/Effect:描边和阴影效果都会增大四倍的顶点数

滚动视图优化策略(ScrollView):

有两种方法填充滚动视图:

1.用所有需要出现在滚动视图的元素填充滚动视图

2.用池处理这些元素,根据需要重新放置它们的位置

RectMask2D组件:俩种方法可以通过给滚动视图添加一个RectMask2D组件来提高性能。该组件确保在滚动视图窗口外面的滚动视图元素不会出现在可画的元素列表中,省去了该元素的batch。

一种简单的缓存池策略:在UI中布局中,使用带有Layout Element组件的对象占位( Slot )。给可见UI元素实例一个池,来填充滚动视图看可见区域,Slot作为父物体来定位。

基于位置的缓存池策略:通过移动布局里UI元素的RectTransforms坐标值,来排序显示位置。通常写一个自定义的滚动视图类或者写一个自定义布局组的组件。

其它优化策略:

禁用无用的Raycast:UGUI的touch处理消耗也可能会成为性能热点。因为UGUI在默认情况下会对所有可见的Graphic组件调用raycast。对于不需要接收touch事件的grahic,一定要禁用raycast。(龙与少女为策划提供了检视的辅助脚本)

OverrideSorting:子Canvas中的OverrideSorting属性将会造成Graphic Raycast测试停止遍历Transform层级。

UI对象的坐标Z值:Z值不为零的时候会影响对象渲染顺序并不能合批。(例如:龙与少女里的阵型界面都是修改Spine的SortingOrder来实现位置排序)

网格开销巨大:如果出现了WaitingForJob或PutGeometryJobFence,则说明合并网格开销巨大(子线程网格合并)

高级技巧:对于处于选中播放动画的需求,并且所处canvas下内容比较多的情况下,可以单独把选中对象放到预先建好的动态canvas里,取消选中时再放回去。

CanvasGroup的使用:

在窗口的GameObject上添加一个CanvasGroup,通过控制它的Alpha值来淡入或淡出整个窗口。

在窗口的GameObject上添加一个CanvasGroup,通过设置它Interactable值来控制底层所有控件的交互开关。

                                                优化工具

Profiler , FrameDebug 

                                            美术相关的制作规范

规范化的重要性:有规范就会有约束有限制,在一个团队的角度上来讲,大家遵守同一套规范,可以避免多余的沟通,增加开发效率,是保证团队协作、项目稳定推进的利器。

设计模板:根据游戏风格和类型设计几套模板:尺寸(比如大中小三套)、布局(比如左右,左中右等)、样式(一级底+二级底+三级底)等,根据游戏内容选择模板,既保持UI统一,又能方便拼UI,大概百分之九十的窗口都在这几个模板中选择。其他比如充值等需要有表现力的窗口再自由设计尺寸和布局。

路径一致性:美术UI目录和客户端目录保持一致,可以很方便替换新版UI,而不会出现名字不一致,目录不一致,策划找瞎眼的情况。

图片命名:可以参考功能、颜色、尺寸等特点命名,命名尽量使用英文,可以添加前缀表示所属功能,后缀也可以使用拼音。

公用图集:多个面板都会用到的图片放到公用图集里面。为了减少合批的障碍,有必要的时候,需要复制公共图片到单独的面板图集里。

合理的出图尺寸:可以减小硬盘大小,减少第一次导入项目时的图片序列化时间。

图片分类:图片可以根据用途分为UISprite、UIFrame 、 Icon、Photo、背景原画。

UISprite:尽量九宫格或者平铺,并且尽量复用。

UIFrame:是一些尺寸巨大的背景框。

Photo:英雄形象等大尺寸图片合并图集太大,因此不会打进图集。

字体大小:研发过程中确立大中小几号字体。每级再分三类,一共九种字体大小。

色值表:颜色可以由美术出一张色值表,包括一种颜色的RGBA值和16进制值,方便开发人员快速定位准确颜色。颜色值可以存储在unity的颜色模板里。


你可能感兴趣的:(UGUI的原理和优化)