今天我们来学习Unity的UI的详解,这部分的内容相对较少,对于程序员来说主要的工作是负责将各种格式的图片呈现在显示器上并允许操作这些图片。
本篇帖子的理论依据依然是官方开源的UGUI代码,网址为:GitHub - Unity-Technologies/uGUI: Source code for the Unity UI system.
我们首先来认识一下UGUI,全称:Unity GUI,开发者习惯称其为UGUI,大体上可以根据功能分为这么几个部分:
在工作流程中各个层的作用:
我们在Unity或者说UGUI中首先对布局造成改动,比如移动某个UI元素的位置,然后布局层首先把这些改动传到图形层,图形层会生成新的相关数据如顶点位置、uv坐标等之后传给渲染层,渲染层根据图形层的数据重新渲染(Draw Call较多时会采取合批),然后后渲染的内容覆盖先渲染的内容,最后再响应玩家的交互。这是大体上UGUI的工作流程,接下来我们就来看看这些流程中涉及到的工具及其功能是如何实现的。
渲染层 (Rendering Layer)
渲染层的组成如下:
1. Canvas系统
├── Canvas组件功能
├── 渲染模式 (Screen Space/World Space/Camera)
├── 排序与深度管理
└── 多Canvas协作
2. CanvasRenderer
├── 网格渲染机制
├── 材质管理
├── 批处理优化
└── 裁剪处理
3. Canvas更新调度系统
├── willRenderCanvases事件
├── CanvasUpdateRegistry机制
├── 五阶段更新流程
└── 脏标记优化
Canvas系统
Canvas组件功能
Canvas是UGUI的核心容器,所有UI元素都必须在Canvas内部,子元素按Hierarchy顺序绘制,与EventSystem协作处理UI交互。
关于eventsystem:
渲染模式 (Render Mode)
关于UGUI主要有三种渲染模式:Screen Space - Overlay、Screen Space - Camera、World Space,分别代表屏幕覆盖、摄像机、世界坐标。
屏幕覆盖非常好理解,就是我们的UI会直接显示在屏幕上,无视所有其他的场景内物体;摄像机模式则是UI会直接显示在摄像机画面上,显然这个渲染模式依赖于摄像机;世界坐标则是把UI视作场景中的一个物体,与其他物体类似。
排序与深度管理
Canvas的深度排序通过多个层级实现,大体上来说有这些:
多Canvas协作
多Canvas协作的本质是通过层级关系和排序机制实现UI的分层管理和性能优化。
具体来说:每个场景有一个Root Canvas作为根容器,可以嵌套多个子Canvas。通过overrideSorting属性,子Canvas可以脱离父Canvas的排序规则,形成独立的排序域。在渲染时,不同Canvas通过sortingLayer和sortingOrder协调显示顺序;在事件处理时,同一Canvas内的元素使用depth排序,不同Canvas间的元素则忽略depth比较,使用Canvas级别的排序规则。这种设计既实现了UI的灵活分层(如弹窗、HUD、背景UI),又通过独立更新和渲染批次优化了性能。
🎯 多Canvas排序的双重体系:
1️⃣ Canvas级别排序 (用于Canvas间):
sortingLayer → sortingOrder → renderOrder
2️⃣ 元素级别排序 (用于Canvas内):
depth (基于Hierarchy顺序计算的absoluteDepth)
🔄 继承关系:
Root Canvas
├── Child Canvas (overrideSorting=false) ← 继承父Canvas的sorting属性
│ ├── UI Element (depth=1)
│ └── UI Element (depth=2)
└── Independent Canvas (overrideSorting=true) ← 使用自己的sorting属性
├── UI Element (depth=1)
└── UI Element (depth=2)
CanvasRenderer
网格渲染机制
CanvasRenderer是UGUI系统中负责实际渲染执行的核心组件,它作为Unity渲染管线和UI逻辑层之间的桥梁,将Graphic组件生成的网格数据和材质信息传递给GPU进行渲染。CanvasRenderer通过高效的网格管理、智能的材质合并、自动化的批处理优化和精确的裁剪机制,实现了UI元素的高性能渲染。每个Graphic组件都自动关联一个CanvasRenderer,通过"脏标记+统一更新"的模式,确保只有发生变化的UI元素才会触发重新渲染,从而最大化渲染性能。
🎯 UI渲染流程:
1. 脏标记触发 → SetVerticesDirty() / SetMaterialDirty()
2. Canvas更新调度 → CanvasUpdateRegistry.PerformUpdate()
3. 重建阶段 → Graphic.Rebuild(CanvasUpdate.PreRender)
4. 网格生成 → UpdateGeometry() → DoMeshGeneration()
5. 材质设置 → UpdateMaterial()
6. CanvasRenderer渲染 → SetMesh() / SetMaterial()
材质管理
CanvasRenderer通过维护一个材质数组实现统一的材质管理,每个渲染器可以设置材质数量并通过SetMaterial()方法分配材质到不同槽位,同时自动处理材质属性同步(如父子组件间的裁剪模式、透明度等),并通过材质实例化机制避免不同UI元素间的材质属性冲突,确保每个UI元素都能正确显示其独特的视觉效果。
看起来似乎非常简单:不就是用一个数组来保存材质吗?但其实CanvasRenderer的材质管理的核心在于动态材质实例化和属性隔离,而材质数组是这一机制的载体。在Unity UGUI中,每个CanvasRenderer
实例内部维护一个材质数组(Material[]
),用于存储实际渲染所需的材质对象。初始化时,该数组默认直接引用编辑器中的原始材质(sharedMaterial
),使多个未修改材质的UI元素共享同一材质以节省内存。当UI元素需修改材质属性(如颜色、纹理)或受父子组件影响(如遮罩效果)时,系统会自动复制原始材质生成独立实例(material
),并将新实例替换到材质数组中对应槽位,确保视觉属性隔离。这种设计通过按需实例化机制,在内存效率(共享静态材质)与视觉灵活性(独立动态材质)间取得平衡。
批处理优化
UGUI通过多层次的优化策略实现高效批处理:使用静态共享的workerMesh避免频繁的内存分配,对频繁更新的网格使用MarkDynamic()告诉GPU优化内存布局(具体来说就是GPU会将这些频繁更新的网格存储在可以高速读取的区域如高速缓存),将使用相同材质的UI元素自动合并为单个绘制调用减少CPU-GPU通信开销,并结合脏标记系统确保只有发生变化的元素才重新生成顶点数据,从而最大化渲染性能。
这里我补充一下关于合批,合批本身是一个针对特定物体减少DrawCall的操作:
各种合批操作的区别如下:
裁剪处理
裁剪是UGUI中同时实现性能优化和视觉效果的重要机制,通过计算UI元素边界矩形与裁剪区域的重叠关系来判断元素是否可见,对于被裁剪(不可见)的元素完全跳过网格生成和GPU渲染过程以节省性能,同时实现ScrollView等组件的内容遮罩功能,当裁剪状态发生变化时系统会自动同步相关元素的渲染状态并补偿之前跳过的更新操作,确保UI显示的正确性。
Canvas更新调度系统
Canvas更新调度系统是UGUI的核心管理机制,负责协调整个UI系统的有序更新和渲染,通过监听Unity渲染管线的willRenderCanvases事件来触发统一的更新流程,并通过CanvasUpdateRegistry单例模式管理所有需要更新的UI元素,将复杂的UI更新过程划分为五个明确的阶段(Prelayout、Layout、PostLayout、PreRender、LatePreRender)按顺序执行,同时结合脏标记机制确保只有发生变化的元素才参与更新,从而实现高效的UI渲染性能和正确的显示结果。
这里我们再复习一下关于Unity渲染管线的内容:
渲染管线的流程一般分为:几何处理,光栅化,着色,测试与混合。
willRenderCanvases事件
willRenderCanvases是Unity渲染管线在每帧渲染Canvas之前发出的关键事件,UGUI通过在CanvasUpdateRegistry构造函数中订阅这个事件(Canvas.willRenderCanvases += PerformUpdate)来确保UI更新与Unity的渲染时机完美同步,这个事件的触发时机保证了UI元素的所有更新都能在实际渲染前完成,避免了渲染时数据不一致的问题,同时也确保了UI更新不会影响到Unity主渲染管线的性能。
一句话:willRenderCanvases事件就是触发 Canvas 重建流程的入口。
CanvasUpdateRegistry机制
CanvasUpdateRegistry是一个单例模式的调度中心,维护了两个关键的更新队列:布局重建队列(m_LayoutRebuildQueue)和图形重建队列(m_GraphicRebuildQueue),所有需要更新的UI元素都必须实现ICanvasElement接口并注册到对应队列中,注册系统提供了多种注册方法(RegisterCanvasElementForLayoutRebuild、RegisterCanvasElementForGraphicRebuild等)来应对不同的更新需求,同时具备智能的重复注册检测和无效对象清理机制,确保更新队列的高效性和正确性。
为什么是布局重建队列和图形重建队列呢?因为UI的修改都可以被归类成这两类变化——要么变化布局要么变化UI图形,流程图如下:
五阶段更新流程
Canvas更新被划分为五个严格按顺序执行的阶段:Prelayout(布局预处理)、Layout(布局计算)、PostLayout(布局后处理)、PreRender(渲染预处理)、LatePreRender(延迟渲染预处理),前三个阶段主要处理布局相关的计算(如LayoutGroup、ContentSizeFitter等),在布局完成后执行裁剪计算(ClipperRegistry.instance.Cull()),最后两个阶段处理图形渲染相关的工作(如网格生成、材质更新等),这种分阶段设计确保了依赖关系的正确处理,比如布局必须在渲染前完成,裁剪必须在布局后但渲染前执行。
脏标记优化
脏标记系统通过在UI元素发生变化时设置对应的标记(如m_VertsDirty、m_MaterialDirty、m_LayoutDirty),只有被标记为"脏"的元素才会被注册到更新队列中参与实际的重建过程,这避免了每帧对所有UI元素进行无意义的检查和更新,大幅提升了性能,同时系统还提供了智能的脏标记传播机制,当父元素布局改变时会自动标记相关子元素需要更新,确保UI显示的一致性和正确性。
图形层 (Graphics Layer)
接下来我们来介绍图形层,图形层的组成如下:
1. Graphic基类体系
├── Graphic抽象基类
├── MaskableGraphic遮罩支持
├── 顶点生成机制 (OnPopulateMesh)
└── 材质与纹理管理
2. 核心图形组件
├── Image (九宫格、平铺、填充)
├── Text (字体渲染)
├── RawImage (原始纹理)
└── 自定义图形组件开发
3. 遮罩系统
├── Mask (模板缓冲遮罩)
├── RectMask2D (矩形裁剪)
├── 嵌套遮罩处理
└── 性能考虑
Graphic基类体系
Graphic基类体系构建了UGUI中所有可视化UI元素的核心架构,通过抽象基类Graphic定义了统一的顶点生成、材质管理和渲染接口,MaskableGraphic在此基础上增加了完整的遮罩支持能力,整个体系通过模板方法模式的OnPopulateMesh机制允许子类定制化顶点生成逻辑,同时提供了统一的材质和纹理管理框架,确保所有UI元素都能以一致的方式参与到Canvas的更新和渲染流程中。
Graphic抽象基类
Graphic作为所有UI视觉组件的根基类,实现了UI元素的基本生命周期管理(本质上是 GameObject 生命周期的子集)和渲染接口,它自动创建和管理CanvasRenderer组件,提供统一的脏标记机制(顶点脏、材质脏、布局脏),实现ICanvasElement接口参与Canvas更新调度,并通过虚拟方法为子类提供可定制的顶点生成、材质管理和事件响应能力,是整个UGUI图形系统的架构基石。
MaskableGraphic遮罩支持
MaskableGraphic继承自Graphic并实现了IClippable、IMaskable、IMaterialModifier接口,为UI元素提供完整的遮罩功能支持,它通过模板缓冲区深度计算(m_StencilValue)和材质修改器模式动态生成遮罩材质,支持嵌套遮罩的正确处理,同时提供矩形裁剪功能通过CanvasRenderer的EnableRectClipping实现高效的视口裁剪,并通过onCullStateChanged事件通知裁剪状态变化。
顶点生成机制 (OnPopulateMesh)
OnPopulateMesh是UGUI顶点生成的核心机制,它使用模板方法模式允许每个图形组件定制自己的网格几何体,基类提供默认的矩形顶点实现(四个顶点加两个三角形),子类可以重写此方法生成复杂的几何体如九宫格、圆形、文字等,VertexHelper工具类提供了便捷的顶点添加和三角形构建API,整个机制确保顶点数据只在必要时重新生成,并自动应用IMeshModifier修改器链进行后处理。
补充一句:UGUI中的UI元素都是由顶点和网格组成的。
材质与纹理管理
Graphic基类建立了完整的材质管理体系,包括默认材质获取(defaultGraphicMaterial)、自定义材质支持(material属性)、渲染材质计算(materialForRendering属性考虑遮罩等因素)、主纹理管理(mainTexture虚拟属性由子类实现),材质脏标记系统确保材质变化时触发重新渲染,同时支持材质修改器链允许遮罩等功能动态修改材质参数,所有材质变化最终通过CanvasRenderer.SetMaterial和SetTexture方法应用到实际渲染。
核心图形组件
UGUI的核心图形组件实现了常见UI元素的可视化需求,Image组件通过强大的Sprite渲染能力支持简单显示、九宫格拉伸、平铺重复、填充动画等多种显示模式,Text组件基于字体资产和网格生成实现灵活的文字渲染,RawImage提供原始纹理的直接显示能力,整个组件系统通过继承Graphic基类体系获得统一的渲染管理,同时各组件都可以作为自定义开发的参考模板,展示了如何在UGUI框架内实现特定的视觉效果。
又要复习一下关于Image和Sprite的关系了:
一句话总结来说:Sprite是资源而Image是组件,这就是二者的根本差别。
Image (九宫格、平铺、填充)
Image组件是UGUI中最灵活的图片显示组件,通过Type枚举支持四种显示模式:Simple模式直接拉伸Sprite填满RectTransform,Sliced模式基于Sprite.border信息实现九宫格显示保持边缘不变形,Tiled模式将可拉伸区域以重复平铺方式填充避免拉伸失真,Filled模式通过fillAmount和fillMethod实现各种形状的填充动画效果(水平、垂直、径向等),每种模式都通过不同的顶点生成算法在OnPopulateMesh中实现,同时支持preserveAspect保持宽高比和useSpriteMesh使用原始Sprite网格等高级功能。
Text (字体渲染)
Text组件(传统Text和TextMeshPro)实现了复杂的字体渲染系统,通过字体资产(FontAsset)管理字符纹理和字形信息,使用字符映射表进行Unicode到字形的转换,文本布局引擎处理换行、对齐、行间距等排版需求,顶点生成时为每个字符创建四边形网格并应用正确的UV坐标映射到字体纹理,支持富文本标签、多材质渲染、SDF(有向距离场)技术实现平滑缩放,整个渲染过程通过TextInfo结构缓存字符信息和网格数据以优化性能。
RawImage (原始纹理)
RawImage组件提供对任意Texture2D纹理的直接显示能力,无需像Image组件那样依赖Sprite资产,它通过texture属性直接接受纹理输入,支持uvRect属性实现纹理的局部显示和UV动画效果,在OnPopulateMesh中生成简单的四边形几何体并应用指定的UV坐标范围,常用于显示动态生成的纹理、视频帧、渲染贴图等场景,相比Image组件具有更直接的纹理控制能力但缺少九宫格等高级功能。
自定义图形组件开发
开发自定义图形组件需要继承MaskableGraphic基类并重写关键方法:OnPopulateMesh生成自定义几何体,GetPixelAdjustedRect获取像素对齐的绘制区域,mainTexture属性返回使用的纹理,可选实现ILayoutElement接口参与自动布局,通过SetVerticesDirty和SetMaterialDirty方法标记需要更新的内容,利用VertexHelper工具类简化顶点和三角形的构建,可以参考Image组件的多模式实现来设计复杂的渲染逻辑,整个开发过程完全融入UGUI的生命周期和更新机制。
遮罩系统
UGUI遮罩系统提供了两种不同的实现方案来满足UI元素的裁剪需求,Mask组件基于GPU模板缓冲区实现任意形状的精确遮罩但需要额外绘制调用,RectMask2D组件基于CPU几何裁剪实现高效的矩形遮罩并支持嵌套优化,整个系统通过MaskableGraphic的遮罩接口和材质修改器机制实现遮罩效果的应用,同时提供完善的嵌套遮罩支持和性能优化策略,在实现UI遮罩效果的同时最大化渲染性能。
这两种遮罩方式虽然名字相仿还被归到一类中,但是其底层原理完全不同:
Mask (模板缓冲遮罩)
Mask组件基于GPU模板缓冲区实现精确的像素级遮罩,它通过IMaterialModifier接口为关联的Graphic组件动态生成特殊的模板材质,使用StencilMaterial工具类管理模板参数(StencilOp、CompareFunction等),支持最多8层嵌套遮罩通过模板深度计算实现正确的层级关系,遮罩区域由Graphic组件的形状定义可以是任意复杂几何体,虽然每个Mask会增加绘制调用影响性能但提供了最大的遮罩灵活性,特别适用于圆形头像、不规则窗口等场景。
RectMask2D (矩形裁剪)
RectMask2D组件实现了高效的CPU端矩形裁剪,它通过IClipper接口参与ClipperRegistry的统一裁剪调度,使用RectangularVertexClipper进行顶点级别的几何裁剪,支持padding参数扩展裁剪区域和softness参数实现边缘柔化效果,相比Mask组件不需要额外的绘制调用和模板缓冲区,能够自动剔除完全超出裁剪区域的UI元素以优化性能,通过CanvasRenderer的EnableRectClipping实现GPU端的最终裁剪,是ScrollView等组件的首选遮罩方案。
嵌套遮罩处理
UGUI支持复杂的嵌套遮罩场景,Mask组件通过MaskUtilities.GetStencilDepth计算模板深度确保正确的嵌套关系,RectMask2D通过MaskUtilities.GetRectMasksForClip收集遮罩链并计算交集区域,MaskableGraphic组件的GetModifiedMaterial方法根据遮罩深度动态生成对应的材质参数,系统自动处理遮罩的启用/禁用状态变化并通知相关子元素重新计算遮罩状态,同时支持Mask和RectMask2D的混合使用通过不同的裁剪阶段实现复合遮罩效果。
性能考虑
遮罩系统的性能优化主要包括:RectMask2D优于Mask因为避免了模板缓冲区操作和额外绘制调用,合理的遮罩嵌套深度避免过多的材质变换,利用RectMask2D的裁剪功能自动剔除不可见元素减少顶点处理,避免频繁的遮罩启用/禁用操作因为会触发大量的材质重建,对于简单矩形遮罩优先使用RectMask2D,对于复杂形状遮罩才使用Mask组件,同时通过ClipperRegistry的统一调度确保裁剪计算只在布局变化时执行而非每帧都计算。
布局层 (Layout System)
然后是我们的布局层,结构如下:
1. 布局基础
├── RectTransform详解
├── 锚点与轴心系统
├── 尺寸计算机制
└── 自适应布局原理
2. 布局组件
├── Layout Group系列
├── Content Size Fitter
├── Aspect Ratio Fitter
└── ILayoutElement接口
3. 布局算法
├── 两阶段布局计算
├── 优先级与依赖关系
├── 性能优化策略
└── 布局重建流程
布局基础
布局基础是UGUI自适应UI的核心基础设施,通过RectTransform替代传统Transform实现2D矩形布局,锚点系统定义UI元素与父容器的相对关系,轴心系统控制变换操作的中心点,尺寸计算机制整合绝对尺寸和相对尺寸的复杂逻辑,自适应布局原理基于内容驱动尺寸的理念实现响应式设计,这些基础概念和机制共同构建了UGUI强大而灵活的布局能力,为各种屏幕尺寸和设备的UI适配提供了坚实的技术基础。
关于RectTransform和Transform:RectTransform 是 Unity 中专门为 UI 系统设计的组件,继承自 Transform,但针对 UI 布局和屏幕适配进行了深度优化。
RectTransform详解
RectTransform是UGUI布局系统的核心组件,继承自Transform并专门为2D矩形UI设计,它通过sizeDelta属性表示相对于锚点的尺寸偏移,anchoredPosition表示轴心相对于锚点的位置偏移,rect属性提供计算后的实际矩形区域,offsetMin和offsetMax定义相对于锚点矩形的边界偏移,这些属性协同工作实现了比传统Transform更适合UI布局的定位和尺寸系统,同时保持了Transform的层级关系和变换能力。
锚点与轴心系统
锚点系统通过anchorMin和anchorMax定义UI元素在父容器中的相对位置基准,支持固定锚点(四个角点相同)实现固定偏移,拉伸锚点(角点不同)实现自适应缩放,轴心系统通过pivot属性定义元素的变换中心点,影响旋转、缩放、定位的计算基准,锚点和轴心的组合使用能够实现复杂的布局关系:如底部对齐、居中显示、边界拉伸等,这套系统为响应式UI设计提供了直观而强大的控制能力。
尺寸计算机制
RectTransform的尺寸计算整合了绝对尺寸和相对尺寸的复杂逻辑,当锚点为固定模式时,sizeDelta直接表示元素的宽高,当锚点为拉伸模式时,sizeDelta表示相对于锚点矩形的尺寸偏移,rect.size表示元素的最终显示尺寸,通过GetWorldCorners()获取世界坐标下的四个角点,这套计算机制确保UI元素在各种锚点配置下都能正确显示,同时支持嵌套布局的尺寸传递和约束。
自适应布局原理
自适应布局基于"内容驱动尺寸"的设计理念,UI元素根据其内容(文本长度、图片尺寸、子元素数量等)自动计算所需的最小、首选、弹性尺寸,布局控制器收集这些信息并分配可用空间,RectTransform与布局系统的集成通过OnRectTransformDimensionsChange回调响应尺寸变化,通过SetInsetAndSizeFromParentEdge等方法设置计算后的位置和尺寸,这种设计让UI能够智能适应内容变化和屏幕尺寸变化,实现真正的响应式布局效果。
布局组件
UGUI布局组件实现了丰富的自动布局功能,Layout Group系列提供子元素的排列和尺寸管理,Content Size Fitter实现基于内容的容器自适应,Aspect Ratio Fitter维持固定宽高比适配不同屏幕,ILayoutElement接口定义布局信息的标准协议,这些组件通过标准化的接口协议协同工作,既可以独立使用解决特定布局需求,也可以组合使用构建复杂的嵌套布局,为开发者提供了从简单到复杂的完整布局解决方案。
Layout Group系列
Layout Group系列包括HorizontalLayoutGroup(水平排列)、VerticalLayoutGroup(垂直排列)、GridLayoutGroup(网格排列)三个核心组件,它们继承自LayoutGroup基类共享通用功能如padding内边距、childAlignment子元素对齐、spacing元素间距等,HorizontalLayoutGroup和VerticalLayoutGroup支持childControlWidth/Height控制子元素尺寸、childForceExpandWidth/Height强制扩展,GridLayoutGroup使用cellSize统一子元素尺寸、constraint约束行列数、startCorner和startAxis控制排列方向,所有Layout Group都实现了完整的布局生命周期:收集子元素信息、计算自身尺寸需求、分配子元素位置和大小。
Content Size Fitter
Content Size Fitter实现容器根据内容自动调整尺寸的功能,通过horizontalFit和verticalFit分别控制水平和垂直方向的适应策略(Unconstrained不约束、MinSize最小尺寸、PreferredSize首选尺寸),在布局计算阶段调用LayoutUtility获取子元素的布局信息并设置自身RectTransform的尺寸,常与LayoutGroup配合使用实现内容驱动的嵌套布局,如按钮根据文字长度自动调整、面板根据子元素数量自动扩展,这个组件是连接内容和容器的重要桥梁。
Aspect Ratio Fitter
Aspect Ratio Fitter专门用于维持UI元素的固定宽高比,通过aspectMode属性控制适配策略(WidthControlsHeight宽度决定高度、HeightControlsWidth高度决定宽度、FitInParent适应父容器、EnvelopeParent包围父容器),aspectRatio属性设置目标宽高比数值,在布局计算时根据一个轴向的尺寸自动计算另一个轴向的尺寸,这个组件特别适用于需要保持固定比例的UI元素如视频播放器、相册图片、游戏界面等,确保在不同分辨率设备上保持正确的显示比例。
ILayoutElement接口
ILayoutElement接口定义了布局系统中元素尺寸信息的标准协议,包括minWidth/minHeight(最小尺寸)、preferredWidth/preferredHeight(首选尺寸)、flexibleWidth/flexibleHeight(弹性尺寸)、layoutPriority(布局优先级)等属性,Image、Text等图形组件实现此接口提供基于内容的尺寸信息,LayoutElement组件允许手动覆盖这些值实现精确控制,布局控制器通过LayoutUtility工具类收集所有ILayoutElement的信息进行尺寸计算和空间分配,这个接口是整个布局系统信息流通的核心标准。
布局算法
UGUI布局算法实现了精密的两阶段计算和智能的空间分配机制,通过水平计算和垂直计算的分离处理确保布局逻辑的清晰性,优先级与依赖关系管理保证复杂嵌套布局的正确计算,性能优化策略通过脏标记和缓存机制避免不必要的重计算,布局重建流程统一调度所有布局变化确保更新的一致性,这套算法在保证功能完整性的同时最大化了运行效率,为复杂UI界面的流畅运行提供了坚实的技术保障。
两阶段布局计算
UGUI布局计算采用严格的两阶段分离策略:水平计算阶段(CalculateLayoutInputHorizontal和SetLayoutHorizontal)和垂直计算阶段(CalculateLayoutInputVertical和SetLayoutVertical),这种分离设计基于UI布局中水平和垂直约束通常独立的特点,水平阶段确定所有元素的宽度和X坐标,垂直阶段确定高度和Y坐标,两阶段之间可能存在依赖关系(如文本的宽度影响其高度),布局系统通过多次迭代确保所有依赖都得到正确解决,这种设计简化了复杂布局的计算逻辑并提高了算法的可靠性。
优先级与依赖关系
布局计算遵循严格的优先级和依赖关系管理:父元素优先于子元素进行计算确保约束的正确传递,layoutPriority高的元素优先获得尺寸分配,布局控制器之间通过深度优先的计算顺序避免循环依赖,LayoutRebuilder通过ParentCount排序确保从根到叶的计算顺序,当检测到潜在的循环依赖时系统会限制迭代次数并输出警告,布局组通过收集所有子元素信息后统一分配避免了增量计算的复杂性,这套机制保证了即使在复杂嵌套布局中也能得到稳定正确的计算结果。
性能优化策略
布局系统采用多层次的性能优化策略最大化运行效率:脏标记机制(SetLayoutDirty)确保只有发生变化的元素才参与重建,LayoutUtility通过缓存避免重复的递归计算,布局计算按需执行而非每帧都计算,LayoutGroup一次性收集子元素信息避免多次遍历,DrivenRectTransformTracker跟踪布局控制的属性避免不必要的变化检测,ObjectPool复用临时对象减少GC压力,合理的计算顺序减少了总体计算复杂度,开发者可以通过禁用自动布局、使用LayoutElement覆盖等方式在性能敏感场景下进行精确控制。
布局重建流程
布局重建流程通过LayoutRebuilder统一管理所有布局变化的处理:当布局相关属性发生变化时通过MarkLayoutForRebuild注册需要重建的RectTransform,在Canvas更新的布局阶段(Prelayout、Layout、PostLayout)按顺序处理所有重建请求,重建过程按照父到子的层级顺序执行确保依赖关系正确,每个阶段完成后调用LayoutComplete通知相关组件,系统还提供ForceRebuildLayoutImmediate支持立即重建的特殊需求,重建过程中会处理嵌套布局的复杂依赖关系,通过有限次数的迭代确保所有布局元素达到稳定状态,避免了布局抖动和无限循环的问题。
交互层 (Interaction Layer)
1. EventSystem核心
├── 事件系统架构
├── InputModule模块
├── 焦点管理机制
└── 多输入设备支持
2. 射线检测系统
├── GraphicRaycaster
├── Physics2DRaycaster
├── 检测优先级
└── 性能优化
3. 事件处理
├── 事件接口体系 (IPointerHandler等)
├── 事件冒泡机制
├── ExecuteEvents调度
└── 自定义事件处理
EventSystem核心
EventSystem核心是UGUI交互系统的总控制中心和基础设施,通过统一的事件系统架构管理输入处理、事件分发、对象选择等核心功能,InputModule模块化设计支持多种输入设备的无缝集成,焦点管理机制确保应用状态变化时的正确响应,多输入设备支持让UI能够同时适配鼠标、触摸、键盘、手柄等各种交互方式,整个系统采用单例模式保证全局状态的一致性,为上层UI组件提供了统一、可靠、高效的事件服务基础。
事件系统架构
EventSystem采用分层架构设计,底层是BaseInputModule抽象输入处理,中间层是EventSystem单例管理器,上层是ExecuteEvents事件分发器,整个架构通过接口分离实现了高度的模块化和可扩展性,EventSystem维护全局状态包括当前选中对象、活跃输入模块、射线检测结果等,通过Update循环驱动整个事件处理流程:UpdateModule更新输入状态、Process处理输入并生成事件、ExecuteEvents分发事件到目标组件,这种架构确保了事件处理的高效性和可靠性,同时为自定义输入方式和事件类型提供了标准的扩展接口。
InputModule模块
InputModule模块系统实现了输入处理的插件化架构,BaseInputModule定义通用接口和基础功能,PointerInputModule专门处理指针类输入并维护指针状态,StandaloneInputModule整合多种输入方式适配不同平台,EventSystem通过UpdateModules动态收集和管理所有输入模块,根据ShouldActivateModule的返回值自动选择最适合的模块,这种设计使得系统能够同时支持鼠标、触摸、手柄等多种输入方式,并能根据当前环境自动切换,开发者也可以轻松添加自定义输入模块支持特殊的输入设备。
焦点管理机制
EventSystem通过isFocused属性跟踪应用的焦点状态,在OnApplicationFocus回调中响应焦点变化,当应用失去焦点时输入模块会相应调整行为:暂停输入处理、清理拖拽状态、释放按下的按键等,这个机制确保了应用在后台时不会产生异常的输入响应,焦点管理还与选择状态管理协同工作,维护currentSelectedGameObject表示当前被选中的UI对象,支持键盘和手柄导航,通过SetSelectedGameObject切换选择并发送相应事件,为非指针输入设备提供了完整的UI导航能力。
多输入设备支持
EventSystem天然支持多种输入设备的同时工作,通过不同的InputModule处理不同类型的输入:StandaloneInputModule处理鼠标和键盘,同时也处理触摸输入,TouchInputModule专门处理触摸输入(现已整合),手柄输入通过导航事件系统处理,每种输入类型都有对应的事件数据结构(PointerEventData、AxisEventData等),输入优先级机制确保不同输入方式之间不会产生冲突,如触摸优先于鼠标模拟,指针事件优先于导航事件,这种设计让UI能够无缝适配从PC到移动设备的各种使用场景。
射线检测系统
射线检测系统是UGUI事件精确投递的关键基础设施,通过多种Raycaster组件实现不同类型对象的交互检测,GraphicRaycaster检测Canvas上的UI元素,Physics2DRaycaster检测2D物理对象,PhysicsRaycaster检测3D物理对象,检测优先级机制确保复杂场景中事件的正确分发,性能优化策略通过缓存、排序、裁剪等手段保证检测效率,整个系统为EventSystem提供准确的交互目标信息,是实现精确UI交互的核心技术支撑。
GraphicRaycaster
GraphicRaycaster是UGUI中最重要的射线检测器,专门负责检测Canvas上的Graphic组件,它通过GraphicRegistry获取Canvas上所有可检测的Graphic列表,使用RectTransformUtility.RectangleContainsScreenPoint进行矩形范围检测,支持ignoreReversedGraphics忽略背面图形,blockingObjects配置物理遮挡检测,sortOrderPriority和renderOrderPriority提供检测优先级控制,检测结果包含深度信息(depth)用于处理重叠UI元素的层级关系,这个组件是所有UI交互的基础,确保鼠标和触摸事件能够准确投递到正确的UI元素上。
Physics2DRaycaster
Physics2DRaycaster扩展了UI事件系统对2D物理世界的支持,它继承自PhysicsRaycaster并专门处理2D Collider的检测,使用Physics2D.RaycastAll执行射线检测,支持eventMask层级过滤,检测结果按距离排序确保前景对象优先响应,这个组件让2D游戏对象能够接收UI事件(如点击、拖拽等),实现了UI事件系统与2D物理系统的无缝集成,常用于2D游戏中的交互对象、可点击的游戏元素等场景,扩展了UGUI的应用范围超越传统UI界面。
检测优先级
射线检测系统通过多层次的优先级机制确保复杂场景中事件的正确分发:首先是摄像机深度优先级,深度大的摄像机优先处理,然后是Raycaster的sortOrderPriority和renderOrderPriority,接着是SortingLayer和sortingOrder的渲染层级,最后是同一Canvas内的depth深度关系,EventSystem通过RaycastComparer统一排序所有检测结果,这套优先级系统处理了UI重叠、3D遮挡、多摄像机等复杂情况,确保用户交互的直观性和准确性,开发者可以通过调整相应的优先级参数精确控制交互行为。
性能优化
射线检测系统采用多种性能优化策略确保高效运行:GraphicRegistry维护每个Canvas的Graphic列表避免全局搜索,支持raycastTarget开关让不需要交互的元素退出检测,检测结果缓存避免重复计算,合理的裁剪算法减少不必要的检测,Physics Raycaster使用分层检测减少物理查询开销,检测结果按需排序避免全量排序的性能损失,同时系统提供了详细的性能分析标记帮助开发者定位性能瓶颈,通过合理的配置和使用模式,射线检测系统能够在复杂场景中保持良好的性能表现。
事件处理
事件处理是UGUI交互系统的执行层和接口层,通过丰富的事件接口体系定义UI组件可响应的交互类型,事件冒泡机制支持层级化的事件响应策略,ExecuteEvents调度器提供高效的事件分发和执行服务,自定义事件处理能力让开发者能够扩展和定制交互行为,整个事件处理系统通过类型安全的接口设计和灵活的分发机制,为UI组件提供了完整而强大的用户交互响应能力,是UGUI交互功能的最终实现层。
事件接口体系 (IPointerHandler等)
UGUI定义了完整的事件接口体系覆盖所有常见的UI交互需求:IPointerXXXHandler系列处理指针事件(IPointerClickHandler点击、IPointerDownHandler按下、IPointerUpHandler释放、IPointerEnterHandler进入、IPointerExitHandler退出、IPointerMoveHandler移动),IDragHandler系列处理拖拽操作(IBeginDragHandler开始、IDragHandler拖拽中、IEndDragHandler结束、IDropHandler放置),ISelectHandler系列处理选择状态(ISelectHandler选中、IDeselectHandler取消选中),以及IScrollHandler滚轮事件等,每个接口都有明确的语义和标准的事件数据参数,UI组件通过实现相应接口来响应用户交互,这套接口体系提供了类型安全和编译时检查的交互处理机制。
事件冒泡机制
事件冒泡机制实现了层级化的事件响应策略,当事件发生时系统会沿着Transform层级向上查找事件处理器,直到找到处理该事件的组件或到达根节点,ExecuteEvents.ExecuteHierarchy方法实现了标准的冒泡逻辑,这种机制让父容器能够截获和处理子元素的事件,支持事件委托模式统一处理多个子元素的交互,也支持在父级实现通用的交互逻辑如拖拽整个面板,冒泡过程可以通过返回值或事件标记来控制是否继续向上传播,为复杂UI的交互设计提供了灵活的架构支持。
ExecuteEvents调度
ExecuteEvents是整个事件系统的执行引擎,通过类型安全的委托机制实现高效的事件分发,为每种事件类型定义了对应的EventFunction委托(如s_PointerClickHandler),Execute方法直接在指定对象上执行事件,ExecuteHierarchy方法实现事件冒泡执行,GetEventHandler方法查找合适的事件处理器,ValidateEventData确保事件数据类型的正确性,这套机制避免了反射调用的性能开销,提供了编译时的类型检查,同时支持灵活的事件路由和处理策略,是整个事件系统高效可靠运行的核心基础。
自定义事件处理
UGUI事件系统提供了完整的自定义扩展能力,开发者可以定义新的事件接口和对应的EventFunction委托,通过继承BaseEventData创建自定义事件数据类型,使用ExecuteEvents.Execute系列方法分发自定义事件,也可以通过继承BaseInputModule创建自定义输入模块处理特殊的输入设备,自定义Raycaster支持特殊的检测需求,整个扩展过程完全遵循UGUI的标准模式和接口规范,确保自定义功能与系统的无缝集成,这种设计让UGUI能够适应各种特殊的交互需求和创新的输入方式。
总结
我们再来回顾这个框图:
大体上来说UGUI是一个相对没有那么复杂的组件,且本身逻辑的脉络和依赖关系相对清楚,多看几遍就可以掌握个大概。