UGUI

Untiy组件

Canvas

  • Canvas就相当于画画时铺在上边的画板,我们把各类元素放在画布上后,Canvas要做的事情就是合并这些元素。
  • 合并的规则为,同一个Canvas里,相同层级的,相同材质球的元素进行合并,从而减少Drawcall。不过相同层级的概念并不是gameobject 上的节点层级,而是覆盖层级。Canvas说如果两个元素重叠,则可以认为它们是上下层关系,把所有重叠的层级数计算完毕后,第0层的所有元素统一合并,第1层的元素也统一合并,以此类推。覆盖层级」(或称渲染层级)​ 和 ​Layer(层级,即 Unity 的 Layer 系统)​ 是 ​完全不同的概念
  • Canvas上的参数 Render Mode 渲染模式比较重要。你可以选择不以Camera为基准的Overlay模式,也可以选择Camera为基准的Screen Camera模式,也可以选择3D世界为基准的World Space模式。三者适合于三种不同的的使用场景各有不同。
    • Overlay模式:并不与空间上排序有任何关系,空间上的前后位置不再对元素起作用,它常用在纯UI的区域内,这种模式下Camera排序有别与其他模式,Sort order参数在排序时被着重使用到,Sort order参数的值越大,越靠前渲染。在这个模式下没有Camera的渲染机制因此很难加入普通的3D模型物体来增加效果。
    • Screen Camera模式:相对比较通用一点,它依赖于Camera的平面透视,渲染时的布局依赖于它绑定的Camera。想让更多的非UGUI元素加入到UI中,Screen Camera模式更加具有优势。这种模式是实际项目中制作UI最常用的模式,不过UGUI底层有对排序做些规则,如对元素的z轴不为0的元素,会单独提取出来渲染,不参与合并。
    • World Space模式:主要用于当UI物体放在3D世界中时用的,比如,一个大的场景中,需要将一张标志图放在一个石块头上,这时就需要World Space模式。它与 Screen Camera 的区别是,它常在世界空间中与普通3D物体一同展示,依赖于截锥体透视(Perspective)Camera。它的原理挺简单的,与普通物体一样当UI物体在这个Camera视野中时,就相当于渲染了一个普通的3D面片,只不过除了普通的渲染Canvas还对这些场景里的UI进行合并处理。

Canvas Scaler

  • 这是个缩放比例组件,用来指定画布中元素的比例大小。
  • 简单指定比例大小的Constant Pixel Size模式,也有Scale With Screen Size以屏幕为基准的自动适配比例大小,或者Constant Physical Size以物理大小为基准的适配规则。
  • 在实际手游项目里,设备的屏幕分辨率变化比较大,通常使用以屏幕为基准的自动适配比例大小的Scale With Screen Size选项。

Graphic Raycaster

  • 输入系统的图形碰撞测试组件,它并不会检测Canvas以外的内容,检测的都是画布下的元素。当图元素上存在有效的碰撞体时,Graphic Raycaster 组件会统一使用射线碰撞测试来检测碰撞的元素。
  • 我们也可以设置完全忽略输入的方式来彻底取消点击响应,也可以指定阻止对某些layers进行相应。

EventTrigger

  • 输入事件触发器,与此脚本绑定的UI物体,都可以接受到输入事件。
  • 比如(鼠标,手指)按下,弹起,点击,开始拖动,拖动中,结束拖动,鼠标滚动事件等。
  • 它主要是起到点击响应作用,配合前面的 Graphic Raycaster 响应给输入事件系统。

Image,RawImage

  • 这两个是UI里的主要部件,它们可以对图片进行展示,包括图片,图集。
  • 两者的区别是Image仅能展示图集中的图元但可以参与合并,而RawImage能展示单张图片但无法合并。通常我们会将小块的图片,打成图集来展示,这样更节省性能也更节省内存,这也是UGUI自动集成的功能,每个图片资源都有一个tag 标记,标记决定了哪些元素会合并到同一张图集内,如果没有tag标记,则默认不会合并图集它自己就是自己的图集。
  • 不使用图集而使用RawImage展示单张图片的时,通常都是由于图片尺寸太大导致合并图集效率太低,或者相同类型的图片数量太多,导致合并图集后的图集太大,而实际在画面上需要展示的这种类型的图片又很少,图集方式反而浪费大量内存空间,则使用RawImage逐一展示即可。

Mask,RectMask2D

  • 遮挡组件,可以将其子节点下矩形区域外的内容剔除,是滚动窗口中最常用的组件。
  • 这两种方式的主要是在剔除的方法上有所区别,在实现效果上都是一样的,其中Mask 使用顶点重构的方式剔除矩形区域外的部分,而 RectMask2D 则采用着色器的剔除方式,每个元素都有自己的材质球实例和实例参数
  • Mask 和 RectMask2D 它俩具体的剔除算法和源代码分析我们将在后面的UGUI源码剖析章节讲解。

其他组件

  • 其他大部分逻辑组件都是可以重写的,比如按钮组件Button,切换组件Toggle,滚动条组件ScrollBar,滑动组件Slider,下拉框组件DropDown,视图组件ScrollView,如果不想使用它们,觉得它们的功能不够用,我们是可以用Image,Mask等几个核心组件组合后重写的。
  • 在实际工作中,很多项目都会自定义属于自己的组件,为什么要自定义呢?很多时候项目里的需求更多样化,有自己的组件可以在特殊需求和特殊逻辑时,能够好不费劲的更改自定义的组件。所以大部分项目中,都会重写一些组件来用来给自己项目使用,也有一些人总结了这些组件的经验,写了些比较好用的组件开源在Github上。

输入与事件模块

  1. UGUI分成了三块,输入事件,动画,核心渲染
  2. 动画:用了tween补间动画的形式,对颜色,位置,大小做了渐进的操作。tween的原理是在启动一个协程,在协程里对元素的属性渐进式的修改,除了修改属性数值,tween还有多种曲线可以选择,比如内番曲线,外翻曲线等,一个数值从起点到终点的过程可以由曲线来控制。
  3. UGUI 把输入事件模块有四部分,事件数据模块,输入事件捕获模块,射线碰撞检测模块,事件逻辑处理及回调模块

事件数据模块

事件数据模块部分对整个事件系统的作用来说,它主要定义并且存储了事件发生时的位置、和事件对应的物体,事件的位移大小,触发事件的输入类型,以及事件的设备信息等。事件数据模块在逻辑上没有做过多的内容,而主要为了获取数据,提供数据服务

事件数据模块,主要作用为在各种事件发生时,为事件逻辑做好数据工作。

输入事件捕获模块

  • 输入事件捕获模块由四个类组成,BaseInputModule,PointerInputModule,Standalone(独立)InputModule,TouchInputModule。
  • BaseInputModule<—PointerInputModule<—StandaloneInputModule(TouchInputModule)
  • BaseInputModule:抽象基类,提供必须的空接口和基本变量
  • PointerInputModule:继承BaseInputModule,并在其基础上扩展了关于点位的输入逻辑,增加了输入的类型和状态
  • StandaloneInputModule:继承PointerInputModule,向标准键盘鼠标输入方向拓
  • TouchInputModule:继承PointerInputModule,向触控板输入方向拓展StandaloneInputModule 的主函数 Process(处理)MouseEvent,它从鼠标键盘输入事件上扩展了输入的逻辑,处理了鼠标的按下,移动,滚轮,拖拽的操作事件。其中比较重要的函数为 ProcessMousePress(按;(被)压;推)、ProcessMove、ProcessDrag 这三个函数,我们来重点看下他们处理的内容。
    • ProcessMousePress 不仅仅处理的是按下的操作,也同时处理鼠标抬起的操作,以及处理了拖拽启动和拖拽抬起与结束的事件。在调用处理相关句柄的前后,事件数据都会被保存在 pointerEvent 中,然后被传递给业务层中设置的输入事件句柄。
    • ProcessDrag 拖拽句柄处理函数与ProcessMousePress类似对拖拽事件逻辑做了判断,包括拖拽开始事件处理,判断结束拖拽事件,以及拖拽句柄的调用。
    • ProcessMove 则相对简单点,每帧都会直接调用处理句柄。
    • 除了鼠标事件外,我们再来看看触屏事件的处理方式,即 TouchInputModule 的核心函数。 ProcessMove 和 ProcessDrag 与前面鼠标事件处理时一样的,只是按下的时间处理不同,而且它对每个触点都做了相同的操作处理。其实 ProcessTouchPress 和鼠标按下处理函数 ProcessMousePress 非常相似,可以说基本上一模一样,只是传入时的数据类型不同而已,由于篇幅有限这里不再重复展示长串代码。

射线碰撞检测模块

主要工作是从摄像机的屏幕位置上,做射线碰撞检测并获取碰撞结果,把结果返回给事件处理逻辑类,交由事件处理模块处理事件。射线碰撞检测模块主要为3个类,分别作用于 2D射线碰撞检测3D射线碰撞检测GraphicRaycaster图形射线碰撞检测

  • 2D、3D射线碰撞测试相对比较简单,用射线的形式做碰撞测试,区别在2D碰撞结果里预留了2D的层级次序以便在后面的碰撞结果排序时,以这个层级次序为依据做排序,而3D的碰撞检测结果则是以距离大小为依据排序的。
  • GraphicRaycaster 为UGUI元素点位检测的类,它被放在了 Core 渲染块里。它主要针对 ScreenSpaceOverlay 模式下输入点位做碰撞检测,因为这个模式下的检测并不依赖于射线碰撞,而是遍历所有可点击的UGUI元素来检测比较,从而判断是该响应哪个UI元素。因此 GraphicRaycaster 是比较特殊的。GraphicRaycaster 对每个可以点击的元素(raycastTarget是否为true,并且 depth 不为-1,为可点击元素)进行计算,判断点位是否落在该元素上。再通过 depth 变量排序,判断最先该落在哪个元素上,从而确定哪个元素响应输入事件。所有检测碰撞的结果数据结构为 RaycastResult 类,它承载了所有碰撞检测结果的依据,包括了距离,世界点位,屏幕点位,2D层级次序,碰撞物体等,为后面事件处理提供了数据上的依据。

事件逻辑处理模块

事件主逻辑处理模块,主要的逻辑都集中在 EventSystem 类中,其余的类都是对它起辅助作用的。EventInterfaces,EventTrigger,EventTriggerType 定义了事件回调函数,ExecuteEvents 编写了所有执行事件的回调接口。

  • EventSystem 主逻辑里只有300行代码基本上都在处理由射线碰撞检测后引起的各类事件。判断事件是否成立,成立则发起事件回调,不成立则继续轮询检查,等待事件的发生。
  • EventSystem 是事件处理模块中唯一继承 MonoBehavior 并且有在 Update 帧循环中做轮询的。也就是说,所有UI事件的发生都是通过 EventSystem 轮询监测到的并且实施的。EventSystem 通过调用输入事件检测模块,检测碰撞模块,来形成自己主逻辑部分。因此可以说 EventSystem 是主逻辑类,是整个事件模块的入口。

核心源码剖析

Culling 裁剪模块

Culling 里是对模型裁剪的工具类,大都用在了 Mask 遮罩上,只有 Mask 才有裁剪的需求。

Layout 布局模块

Layout 主要功能都是布局方面,包括横向布局,纵向布局,方格布局等等。总共12个文件,有9个带有 Layout 字样,它们都是处理布局的。除了处理布局内容以外,其余3个文件,CanvasScaler,AspectRatioFitter(/ ˈæspekt / ˈreɪʃiəʊ 宽高比适配器),ContentSizeFitter 则是调整自适应功能。从 ContentSizeFitter,AspectRatioFitter 都带有 Fitter 字样可以了解到,它们的功能都是处理自适应。其中 ContentSizeFitter 处理的是内容自适应的, 而 AspectRatioFitter 处理的是朝向自适应的,包括以长度为基准的,以宽度为基准的,以父节点为基准的,以外层父节点为基准的自适应,四种类型的自适应方式。另外 CanvasScaler 做的功能非常重要,它操作的是 Canvas 整个画布针对不同屏幕进行的自适应调整。不同 ScreenMathMode 模式下 CanvasScaler 对屏幕的适应算法,包括优先匹配长或宽的,最小化固定拉伸的,以及最大化固定拉伸三种数学计算方式。其中代码中在优先匹配长或宽算法中,介绍了使用Log和Pow来计算缩放比例可以表现的更好。

MaterialModifiers, SpecializedCollections, Utility

材质球修改器,特殊收集器,实用工具,这三块相对代码量少却很重要,他们是其他模块所依赖的工具。

VertexModifiers

顶点修改器为效果制作提供了更多基础方法和规则。主要用于修改图形网格,尤其是在UI元素网格生成完毕后对其进行二次修改。其中 BaseMeshEffect 是抽象基类,提供所有在修改UI元素网格时所需的变量和接口。IMeshModifier 是关键接口,在下面的渲染核心类 Graphic 中会获取所有拥有这个接口的组件,然后依次遍历并调用 ModifyMesh 接口来触发改变图像网格的效果。当前在源码中拥有的二次效果包括,Outline(包边框),Shadow(阴影),PositionAsUV1(位置UV) 都继承了 BaseMeshEffect 基类,并实现了关键接口 ModifyMesh。其中 Outline 继承自 Shadow, 他们的共同的关键代码,我们可以重点看一下:此函数作用是,在原有的Mesh顶点基础上,加入新的顶点,这些新的顶点复制了原来的顶点数据,修改颜色并向外扩充,使得原图形外渲染出外描边或者阴影。

核心渲染类

  1. 我们常用的组件 Image,RawImage,Mask,RectMask2D,Text,InputField 中,Image,RawImage,Text 都是继承了 MaskableGraphic ,而 MaskableGraphic 又继承自 Graphic 类,这里 Graphic 类相对比较重要,是基础类也存些核心算法。
  2. Graphic 的运作机制。
    • SetAllDirty 设置并通知元素需要重新布局、重新构建网格、以及重新构建材质球。 它通知 LayoutRebuilder 布局管理类进行重新布局,在 LayoutRebuilder.MarkLayoutForRebuild 中它调用 CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild 加入重构队伍,最终重构布局
    • 网格构建函数,Graphic 构建 Mesh 的部分,先调用OnPopulateMesh创建自己的Mesh网格,然后调用所有需要修改 Mesh 的修改者(IMeshModifier)也就是网格后处理组件(描边等效果组件)进行修改,最后放入 CanvasRenderer 。其中 CanvasRenderer 是每个绘制元素都必须有的组件,它是画布与渲染的连接组件,通过 CanvasRenderer 我们才能把网格绘制到 Canvas 画布上去
  3. Mask 遮罩部分
    • Mask 组件调用了模板材质球构建了一个自己的材质球,因此它使用了实时渲染中的模板方法来裁切不需要显示的部分,所有在 Mask 组件后面的物体都会进行裁切。我们可以说 Mask 是在 GPU 中做的裁切,使用的方法是着色器中的模板方法。
    • RectMask2D 会先计算并设置裁切的范围,再对所有子节点调用裁切操作。其中:获取了所有有关联的 RectMask2D 遮罩范围,然后计算了需要裁切的部分,实际上是计算了不需要裁切的部分,其他部分都进行裁切。最后对所有需要裁切的UI元素,进行裁切操作。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇