UGUI重建流程和优化

UGUI重建流程和优化

参考文献
(五)UGUI源码分析之Rebuild(布局重建、图形重绘)_ugui rebuild-CSDN博客
(99+ 封私信 / 83 条消息) UGUI源码解析(二十一)LayoutRebuilder - 知乎
(99+ 封私信 / 83 条消息) UGUI源码解析(五) CanvasUpdateRegistry - 知乎
(99+ 封私信 / 85 条消息) UGUI UI重建二三事(一) - 知乎
(99+ 封私信 / 85 条消息) UGUI UI重建二三事(二) - 知乎

总体流程简述

首先,我们对UI进行修改时,如修改其大小,改材质等很多情况下,UI会将自己标记为脏,然后放进一个队列中。在相机即将渲染时会处理这个队列的元素,进行布局的重新计算,称作布局重建。然后重新生成graphic的网格,称为网格重建。

重建过程的主要接口

ICanvasElement接口

标记此组件需要参与重建。主要是Rebuild方法,会在此节点需要重建时调用,参数表面重建过程。
CanvasUpdate.// 标记了重建过程
Prelayout,
Layout,
PostLayout,
PreRender,
LatePreRender,
MaxUpdateValue,
LayoutComplete在布局完成时调用
GraphicUpdateComplete在网格重建完成时调用

实现情况:

1.Graphic实现,用来生成网格。即网格重建过程。
2.LayoutRebuilder实现,用来进行布局。即布局重建过程。
3.InputField、ScrollRect、Scrollbar、Slider、Toggle实现,主要是根据重建过程实现自己的功能。

ILayoutElement接口

标记此节点需要参与布局重建。给出布局重建时节点的宽高参数。
有宽高的min,preferred,flexible,用于布局。
此外layoutPriority标记布局优先级。
CalculateLayoutInputHorizontal
CalculateLayoutInputVertical
这两个方法计算自己的理想宽高。

实现情况:

Image、Text、InputField实现preferredWidth,preferredHeight会返回最合适的大小。
ScrollRect实现了所有参数,但都返回-1,仅供布局系统调用。
LayoutElement实现了所有参数,并开放到编辑器供配置。

ILayoutController接口

设置子节点的位置宽高。
实现两个方法SetLayoutHorizontal、SetLayoutVertical。作用是设置自己的子节点。

实现情况:

有三个类实现GridLayoutGroup,HorizontalLayoutGroup,VerticalLayoutGroup,

重建框架执行流程

CanvasUpdateRegistry处理重建的类。

待重建元素列表

m_LayoutRebuildQueue保存需要更新布局的队列
m_GraphicRebuildQueue保存需要更新图形的队列
队列元素的类型都是ICanvasElement,此接口为布局元素的基类。

元素如何加入到重建列表?

向布局重建队列添加元素的方法为
CanvasUpdateRegistry.MarkLayoutForRebuild //将需要重建的元素加入重建列表。实际调用下面的
CanvasUpdateRegistry.MarkLayoutRootForRebuild //将需要重建的布局根节点加入重建列表
一般情况下,各个组件在布局需要修改时调用MarkLayoutForRebuild将自己加入重建列表。
比如 OnEnable、OnDisable、OnRectTransformDimensionsChange等
向图形重建队列添加元素的方法为
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild

重建总入口

CanvasUpdateRegistry实现单例模式,单例的构造函数就向Canvas.willRenderCanvases注册了PerformUpdate函数,因此PerformUpdate为重建过程的总入口。

PerformUpdate执行逻辑

首先调用CleanInvalidItems清理无效布局组件。
m_PerformingLayoutUpdate标记了正在进行布局重建。
使用SortLayoutList排序。排序依据为父物体层数少的在前。
依次对m_LayoutRebuildQueue列表中元素调用Rebuild,参数分别是Prelayout、Layout、PostLayout。
这就等于进行了重建的三个步骤。其中Layout进行了实际布局重建。
最后调用每个元素的LayoutComplete方法。
清理m_LayoutRebuildQueue列表。
布局重建完成,然后进行网格重建。此过程与上面布局重建基本一致。
先调用ClipperRegistry.instance.Cull(),不用排序
依次对m_LayoutRebuildQueue列表中元素调用Rebuild,参数分别是PreRender、LatePreRender。
最后调用每个元素的GraphicUpdateComplete方法。
清理m_GraphicRebuildQueue列表。
可以看到基本就是依次通知每个元素重建了。具体行为是让组件自己实现的,即调用Rebuild函数,每个组件都会重写来实现不同行为。

重要组件的具体重建行为

Graphic.Rebuild 网格重建

实现网格重建,即生成图片和文本网格的部分
Graphic会在PreRender时,检查网格刷新,检查材质刷新。
生成网格方法是OnPopulateMesh。
值得注意的是网格生成后,可通过IMeshModifier对网格进行调整,实现网格特效。如Shadow。
Graphic基类中网格生成是直接创建面片显示纯颜色。
Image重写了OnPopulateMesh方法,实现了一些特殊填充,就是Sliced,Tiled那些。
(这部分实现没有技巧,全是硬编码)
Text重写了OnPopulateMesh方法,改成文本的网格生成。其实现未开源。
不过可以通过m_TempVerts访问到每个字符生成完毕的网格数据。(可以用这个做超链接)
RawImage实现和Graphic基本一致,区别在没有主贴图时不会生成网格。

ILayoutElement .Rebuild 布局重建

所有加入布局重建中元素,都是RectTransform,加入列表时会包一个LayoutRebuilder。
LayoutRebuilder在Rebuild的Layout阶段时计算了自己理想宽高。过程:
PerformLayoutCalculation递归后续遍历所有子节点。即先子节点,再自己。会对每个ILayoutElement节点执行一个委托CalculateLayoutInputHorizontal,作用是计算自己的最终宽高。
随后PerformLayoutControl递归后续遍历所有子节点。即先子节点,再自己。会对每个ILayoutController节点执行一个委托SetLayoutHorizontal,作用是设置自己的子节点。
特别的,有时需求会需要我们获取布局完成后的组件位置,可调用这个方法立即进行此元素的布局重建。之后可正确获取最佳宽高值。
LayoutRebuilder.ForceRebuildLayoutImmediate
其实现是,创建一个此节点的LayoutRebuilder,然后以CanvasUpdate.Layout为参数立即调用一次Rebuild。也就是立即触发一次布局重建。调用参数为布局根节点。

总结LayoutGroup的重建过程

包括GridLayoutGroup,HorizontalLayoutGroup,VerticalLayoutGroup
调用CalculateLayoutInputHorizontal时,LayoutGroup会收集所有子物体,保存在m_RectChildren中。有ILayoutIgnorer且ignoreLayout都是false的除外。
HorizontalLayoutGroup中重写CalculateLayoutInputHorizontal,计算自己宽高。
SetLayoutHorizontal执行过程
随后SetChildAlongAxisWithScale时设置子节点位置。

优化思路

可修改源码,检查上文提到的两个队列来查看重建情况。
重建优化思路基本就是减少重建的触发,以减少重建次数。
即减少UI元素位置大小图片材质等修改,减少mask矩形区域的变更。
少用布局组件,不会变化的布局组件删除或者关掉。不要频繁修改布局组件元素。

OnRectTransformDimensionsChange

可观察到此函数在网格需要变化时触发布局重建。
如修改Anchor,AnchoredPosition,Pivot,SizeDelta大概率导致网格变化产生重建。
而如果仅改变Scale,Rotation,Position,不会发生重建。
因此可考虑用scale改变代替enable避免重建。

摘抄大佬的笔记,总结触发rebuild的情况

https://zhuanlan.zhihu.com/p/448293298

  1. Text控件 文本的内容及颜色变化、设置是否支持富文本、更改换行模式、设置字体最大最小值、变更文本使用的对齐锚点、设置是否通过几何对齐、变更字体大小、变更是否支持水平及垂直溢出、修改行间距、变更字体样式(正常、斜体.....)。
  2. Image控件 颜色变化、变更显示类型(Simple、Sliced、Tiled、Filled)、变更是否应保留Sprite宽高比(Image.preserveAspect属性的变更),FillCenter属性变更(是否渲染平铺或切片图像的中心)、变更填充方式(Horizontal、Vertical、Radial360....)、变更图像填充率(fillAmount)、变更图像顺逆时针填充类型(Image.fillClockwise)、变更填充过程的原点(Image.FillOrigin)。
  3. RawImage控件 设置Texture、变更纹理使用的UVRcet、
  4. Shadow效果 改变效果的距离(effectDistance)及颜色(effectColor)、变更是否使用Graphic中的Alpha透明度(useGraphicAlpha)。
  5. Mask控件 设置是否展示与Mask渲染区域相关的图形(showMaskGraphic),enable发生变化
  6. 所有继承MaskableGraphic的控件(Image、RawImage、RectMask2D、Text) 设置此图形是否允许被遮盖、enable发生变化、父节点发生变化(TransFromParentChanged)、在Hierachy面板上发生改变(HierachyChanged)。
  7. 所有继承自BaseMeshEffect的效果类(目前只看到Shadow及PositionAsUV1)的enable变化及应用动画属性的操作。
  8. 所有继承自Graphic的UI控件材质(material)发生变化。
相关推荐
SmalBox9 小时前
【节点】[Adjustment-WhiteBalance节点]原理解析与实际应用
unity3d·游戏开发·图形学
那个村的李富贵9 小时前
Unity打包Webgl后 本地运行测试
unity·webgl
nnsix10 小时前
Unity OpenXR开发HTC Vive Cosmos
unity·游戏引擎
nnsix11 小时前
Unity OpenXR,扳机键交互UI时,必须按下扳机才触发
unity·游戏引擎
nnsix11 小时前
Unity XR 编辑器VR设备模拟功能
unity·编辑器·xr
老朱佩琪!11 小时前
Unity访问者模式
unity·游戏引擎·访问者模式
不定时总结的那啥12 小时前
Unity实现点击Console消息自动选中预制体的方法
unity·游戏引擎
nnsix12 小时前
Unity OpenXR 关闭手柄的震动
unity·游戏引擎
CreasyChan12 小时前
Unity 中的反射使用详解
unity·c#·游戏引擎·游戏开发