Unity UI系统概述
知乎分流:https://zhuanlan.zhihu.com/p/678123862
引
UGUI,感觉大家都习惯这么叫了,Unity自己比较正式的称呼是Unity UI。为什么大家都叫UGUI呢?(评论区提了包名,我认为确实是这样,是我之前写的过于绝对)因为在UGUI面世前Unity还有两套UI系统,一套是自带的IMGUI,简称GUI,另一套则是第三方的大佬写的NGUI(Next-Gen UI,翻译成下一代UI可能会好懂一点)。而UGUI实际是NGUI的作者被收编了,协助Unity开发的,直到遥远的5.x时代才慢慢开始崭露头角,然后在Unity更换命名方式后的2017版本开始普及起来,所以自然而然,大家都叫的是UGUI(Unity GUI)。
现如今,文章所写的这个世代,Unity正在推广它的新一代UI系统——UI ToolKit,类似于安卓UI,或者说早期winformUI的开发模式,不过每项新技术在大规模普及前都要经历很长的一段时间,在我看来目前开发的主流仍然会停留在UGUI很长一段时间。
FairyGUI也是比较新颖的一套UI系统,不过它本身是跨平台的,也就是说非Unity专属的UI方案,有着独立的美术编辑器,也许不久的将来会成为一种商业开发的主流选择吧。
几种UI系统的比较
Unity对于几种UI系统的评估是:UGUI更适合于熟悉引擎的TA(并非国内定义下会管线的TA,更偏向于会引擎的美术)去使用,而UI ToolKit则适合UI设计师去使用,而对于程序员来说就没有任何限制了。它也给出了它的理由,由此我们可以了解到这几种UI系统之间的差异,这里我会简单说一下,不会详细展开,具体可以通过阅读官方文档来了解。
Unity中UI系统的对比
docs.unity3d.com/cn/2023.2/Manual/UI-system-compare.html
IMGUI
IMGUI的布局,元素,以及元素的定制,都是由代码去操控的,对于运行时,只要场景里有一个挂载该脚本的物体,那么就会有对应脚本所绘制的UI,编辑器下通常会以外挂窗口,或者Inspector上的展示为主。IMGUI最好用于编辑器下的工具开发,或者运行时的外挂插件开发,如果作为运行时的UI来说,IMGUI的开发难度会很大,不方便调试,缺乏很多复杂动效的实现。
如果你玩过一些自动翻译的Unity游戏,应该会了解BeplnEX这个框架,它的本质便是通过代码注入的方式去增加IMGUI的界面,实现类似于GM工具的外挂面板。
UI ToolKit
UI工具包主要是把元素的绘制和元素的逻辑进行了分离,并且元素的绘制也是脱离于场景的,Toolkit使用UXML去记录元素,与UGUI相比,UIToolKit甚至可以脱离引擎来编辑UI元素,UIBuilder可以直接创建,编辑,测试UXML文件,从结构上不再需要Canvas和Gameobject的限制,转而变成一个UI Document跟着相关联的UXML文件。Unity说这对工作流会有一个比较大的改进,我觉得其实一般般,因为UGUI里大家也都是划分成很多个场景和预制体来并行开发的,不会真的傻乎乎的只在一个场景里做编辑,但能脱离引擎来编辑UI元素是个好事情,因为如果想让美术参与编辑的话,引擎对他们来说实际是个干扰项。
UGUI的结构
我自己是把UGUI拆成三部分,渲染,布局,事件,当然实际上UGUI源码展现的要多得多,这里只是我浅显的一些理解。我们以下面这张图为例,来讲讲运行时三个部分都发生了什么。
渲染
当你创建了一个UI元素,例如图中的女仆爱丽丝和背包按钮,会向画布层注册一次渲染的事件。这样做的好处在于 一帧内发生多次数据变化,并不会执行多次的渲染更新,而是画布每一帧都去运行一次渲染的事件,批量更新元素,也就是说降低了渲染更新的频率。这样事实上还是会遇到瓶颈问题,所有的显示用的UI元素(图片、文字)有共同的基类Graphic,画布在渲染的时候会对所有的Graphic进行合批,这就导致了UGUI重绘的时候开销比较大,一个元素的更改会引起同一画布下的其他元素也被触发重新渲染。
布局
RectTransform是UI组件专有的,直译就是矩形变换,因为在UGUI的概念里画布就是一个Rect矩形。九宫格的自适应缩放计算便是基于此实现的。在这里爱丽丝的对齐方式和背包按钮的对齐方式都是我们决定好的,假如此时屏幕尺寸发生变化,便会重新计算它们的位置。UGUI同时也提供了很多自动布局的组件:Horizontal Layout/Vertical Layout/Grid Layout,背包中的元素经常会需要使用Grid Layout,但是使用这些组件方便的同时也是有代价的 ,这些自动布局实现自适应需要多次计算,大致可以分为下面几个过程:
布局计算->计算后更新布局->对于嵌套的布局递归计算
在游戏运行过程中布局下的元素数量发生动态改变时也会触发计算。
事件
场景中会有一个挂载了EventSystem组件的物体,该组件每一帧都会去调用同物体下的Standalone Input Module的Process方法,驱动事件的生成和处理。你可以理解为UGUI的事件Update就是跑在EventSystem里的。当我们按下了背包按钮,首先Process发现了鼠标点击的行为,这里才是真正处理输入行为的地方,Standalong Input Module用射线去确定鼠标点击的元素是哪一个,它会去找Canvas渲染的UI元素里有谁是可以作为射线碰撞的对象的,然后尝试去获取基类IPointerClickHandler,发现这个物体确实有这个基类,因为Button是继承这个基类的,所以去执行了鼠标点击事件应该执行的对应逻辑,也就是onClick对应的事件逻辑。
UGUI源码?
2019.2之后的版本里,包管理器应该都自带了UGUI C#部分的源码,正常情况你在调试的时候是可以看到UGUI这部分的代码的,甚至还可以做一些小小的修改。我们可以由此一观UGUI运行时都发生了什么。
总结
UGUI算是UI仔绕不过去的一个坑吧,最麻烦的是目前的热更主流还要和lua结合调用,同时UGUI和Addressable结合也有不少隐藏的坑点在里面,我个人认为在源码的部分上不必下太多时间,毕竟我们也不是要自己写一套NGUI这样的系统,重点还是应该放在如何去用上面。