相关图示:工具速查总览
UI 框架
- UI 分层:HUD、普通面板、全屏面板、弹窗、Toast、引导、Loading、系统提示。
- 生命周期:打开、关闭、隐藏、恢复、缓存、销毁、重复打开、快速关闭。
- 面板栈:返回键、遮罩层、互斥面板、弹窗优先级、二次确认。
- 异步加载:面板资源加载中取消、加载失败重试、加载完成时状态是否仍有效。
- 数据绑定:手动刷新、事件刷新、响应式绑定的成本和适用范围。
- 红点系统:数据源、依赖树、聚合规则、缓存、刷新频率、调试视图。
- 通用组件:奖励格子、货币栏、页签、列表、筛选、排序、确认框、Loading。
- 适配:安全区、刘海屏、平板、PC 窗口、超宽屏、横竖屏。
- 性能:Canvas 拆分、避免频繁 Rebuild、图集、字体、ScrollView 虚拟列表。
UI 分层
HUD 全称 Heads-Up Display(抬头显示),指常驻屏幕、不打断操作的信息层(血条、小地图、货币等)。
分层主要靠 Canvas / sortingOrder,不是靠 CanvasGroup。CanvasGroup 管显隐、透明度和能否点击。
| 逻辑层 | 典型 sortingOrder 区间 | 作用 |
|---|---|---|
| HUD | 100 | 常驻信息,不挡操作 |
| 普通面板 | 200 | 背包、商店等 |
| 全屏面板 | 300 | 主界面、结算 |
| 弹窗 | 400 | 确认框、二次确认 |
| Toast | 500 | 飘字 |
| 引导 | 600 | 遮罩、高亮 |
| Loading / 系统提示 | 700+ | 最高,必须盖住一切 |
落地习惯:
- 每个逻辑层一个根节点(空节点或独立 Canvas);打开面板时挂到对应层并设 sortingOrder。
- Canvas 按重建成本拆:常驻少变(HUD)、动态频繁、Toast/Loading/引导常单独 Canvas。
- 不同 Canvas 不能合批;合批发生在同一 Canvas 内。拆层是用 Draw Call 换 Rebuild 隔离。
UIManager 用层枚举映射层根与 sortingOrder,业务不要手拖层级。
生命周期
收成 状态机 + UIManager 统一调度,业务不直接 Instantiate / Destroy。
典型状态:None → Loading → Opening → Opened → Hiding → Hidden,以及 Closing → Closed(Cached 或 Destroyed)。
| 操作 | 含义 |
|---|---|
| Open | 加载 → 挂层 → 开启动画 → 绑数据/事件 |
| Close | 解绑 → 关动画 → 出栈;再决定缓存或销毁 |
| Hide | 不销毁,常不卸资源;被全屏盖住时用 |
| Resume/Show | 从 Hidden 回来,刷新数据,不一定重新加载 |
| Cache | 关了但实例留下,下次秒开 |
| Destroy | 卸事件、卸资源、销毁实例 |
| 重复打开 | 聚焦置顶 / 刷新 / 忽略,看配置 |
| 快速关闭 | 取消加载、打断动画、防回调落到已关面板 |
配置驱动:层级、是否互斥/进栈、关闭策略(Destroy / Cache / HideOnly)、缓存上限、是否允许重复打开。
缓存淘汰常用 LRU(Least Recently Used,最近最少使用):满了或内存紧时先丢最久未打开的面板。
关闭必做:取消异步与网络回调、解绑事件/红点、停动画、归还列表池、Release 资源句柄。
面板栈
可入栈的界面(普通面板、全屏、弹窗)推进栈;HUD / Toast / Loading 通常 不入栈。
- 返回键 / Esc:全局
UIManager.Back(),只关栈顶;BlockBack(二次确认、引导、Loading)时吞掉或走面板OnBack。 - 遮罩:弹窗/全屏自动加 Mask;
CloseOnClickMask可配;sortingOrder 夹在下层与当前弹窗之间,防点穿。 - 互斥:配置互斥组,Open 前由 Manager 关同组或 Hide 被盖住的面板,并同步改栈。
- 弹窗优先级:系统断线 > 付费/重要奖励 > 普通确认 > 弱提示/运营窗;高优先级可压低优先级,同级排队。
- 二次确认:独立高优先级入栈;返回键等于取消;回调前检查原面板是否仍在。
面板配置常见字段:Layer / 是否入栈、MutexGroup、Priority、ShowMask、ClickMaskClose、HandleBack。
异步加载
把「打开面板」当成带生命周期的异步请求,贯穿 openId + CancellationToken。
流水线:Open(requestId) → LoadAsync → 成功则校验仍有效再实例化进栈 → 无效则 Destroy/Release → 失败则有限重试或失败 UI。
取消后 Addressables 句柄要 Release。 句柄(Handle)是一次异步加载的凭证(如 AsyncOperationHandle);取消是业务语义,Release 是资源语义,所有出口(成功/失败/取消/openId 失效)走同一 Cleanup(),且只 Release 一次。
资深常盯四点:
- 取消后句柄有没有 Release。
- 失败重试是否也看 token(Delay 也传 token;用户已取消不要再弹失败框)。
- 连点:按钮防抖 + Manager 对同面板 Opening 做 Ignore / Join / Replace;已 Opened 则 Refresh 置顶。
- 加载完成回调先回主线程再碰 Unity 对象;再验 openId,无效则绝不 Instantiate。
加载中算 Opening;可先不正式 push,或 push Loading 占位。快速关闭 = Cancel + 清理同面板排队请求。
数据绑定
大厂很少全项目一种绑定,按变更频率与成本混用:
| 模式 | 做法 | 适用 |
|---|---|---|
| 手动刷新 | OnOpen / Refresh() 赋值 |
打开时定死、列表 cell 的 SetData |
| 事件刷新 | 领域事件订阅后改 UI | 货币、邮件、多界面要听的推送 |
| 响应式绑定 | Observable / MVVM 声明式 | 表单、设置、局部双向;少全量上 |
注意:关面板退订;打开先 Snapshot 再 Subscribe;事件风暴合并刷新;列表不对每行做深度响应式。成本不仅在 GC,更在生命周期与可调试性。
红点系统
独立子系统(树 + 叶子算、向上聚),UI 只绑节点 key。
- 数据源(叶子):未读数、可领奖励、新解锁未点等;业务变更时
Notify,禁止 UIUpdate轮询。 - 依赖树:配置/注册
父 ← 子;动态活动可增删子节点。 - 聚合:OR(入口最常见)、SUM(数量)、MAX(优先级)、少数 CUSTOM。
- 缓存:节点缓存
isShow/count/dirty;脏了向上传。 - 刷新频率:同帧多叶子变 → 标脏 → 帧末/节流再算,只对真变化的节点通知 UI。
- 调试视图:整树可视化、叶子条件、已读标记、强制 Set;能解释「为什么亮/不亮」。
适配
原则:规则吃掉设备差异,禁止每个面板手调一版。
- 安全区 / 刘海:
Screen.safeArea(必要时厂商校正)→ 全局 SafeRoot padding;方向/分屏变化要重算。 - CanvasScaler:固定设计稿契约(如 1080×1920)+ Match;美术出一套锚点规范。
- 平板:IsTablet 判断;限制内容最大宽、居中,避免巨空白。
- PC 窗口:监听 resize;最小窗口;Mobile/PC profile。
- 超宽:中间安全内容区(如仍按 16:9 逻辑宽);左右贴边仍受 SafeArea 约束。
- 横竖屏:锁方向最省事;双方向由 OrientationService 广播,面板切布局,不各自听 orientation。
性能
UGUI 瓶颈主要在 Rebuild(CPU) 与 Draw Call / Overdraw(GPU)。
Canvas 拆分: 静态 vs 动态、按层、高频小组件;嵌套 Canvas 把狂刷子树隔离。跨 Canvas 不合批。overrideSorting 默认关,只为排序不够用再开,并测 Mask。
少 Rebuild: 脏标记合并写 UI;赋值前比较;少 Layout 套娃;动画避开 Layout 子节点;整页淡入优先 CanvasGroup。
图集: 按模块/生命周期打包;一界面少图集;动态头像勿期待与静态合批;Mask/多材质仍断批。
字体(TMP): 常用字预热;控动态扩图集;少材质变体;列表少富文本、少每帧改字。
虚拟列表: 只创建可见区+缓冲 cell,滑出回收 SetData;固定行高优先;回收解绑事件/红点/异步回调(index/version 防错图)。
排查顺序:Profiler UI Rebuild → Frame Debugger DC → 打开/滑动卡顿查虚拟列表与字体扩容。
需要掌握的工具
- UGUI:Canvas、Graphic、Layout、EventSystem、Mask、ScrollRect(见下节深度要求)。
- TextMeshPro:字体资产、Fallback、富文本、缺字处理。
- UI Toolkit:了解适用场景,尤其是编辑器工具和部分运行时 UI。
- Unity Profiler UI 模块:分析 Canvas Rebuild、Batch、Layout 和 GC。
- Frame Debugger:检查 UI Draw Call、图集和材质切换。
- Device Simulator:验证安全区、分辨率、横竖屏和屏幕比例。
- Figma、Sketch、Photoshop 标注:对齐视觉尺寸、切图、字体和交互动效。
- FairyGUI、UGUI Extension 或项目自研 UI 工具:根据团队技术栈掌握。
UGUI 深度要求(高级/资深)
不是「用过组件」,而是能讲原理、排坑、定规范。
| 组件 | 要求 |
|---|---|
| Canvas | Render Mode 选型;sortingOrder 分层;拆分/嵌套与 Rebuild vs 合批交易;Scaler 与 SafeArea |
| Graphic | 脏与重建;Raycast Target 能关尽关;合批与断批;CanvasGroup 整页淡入 |
| Layout | 知代价;固定尺寸/锚点优先;虚拟列表 cell 少套娃;动画不打在 Layout 链上 |
| EventSystem | 命中链;单例;UI/场景隔离;Loading/引导门控;手柄焦点 |
| Mask | Mask(Stencil) vs RectMask2D;列表用矩形裁;知合批代价;嵌套 Canvas 要测裁剪 |
| ScrollRect | 拖点冲突阈值;嵌套滚动;虚拟列表为默认加分/必考 |
自学优先序:Canvas/Rebuild 合批 → EventSystem 命中 → ScrollRect 虚拟列表 → Layout/Mask 代价 → Graphic 脏与 Raycast。
可继续细分方向
- UI 生命周期和面板栈。
- 红点系统和功能入口。
- UI 适配和多语言布局。
- UI 性能、图集和虚拟列表。
- 引导框架与目标寻址。
交互与输入
- 输入抽象:触控、键鼠、手柄、快捷键统一成项目内的输入事件。
- UI 与场景隔离:点击 UI 时不触发场景射线,拖拽时不误触点击。
- 射线检测:地面、NPC、建筑、道具、怪物、UI 的 Layer 和优先级清楚。
- 交互距离:可点击、可靠近、自动寻路、距离不足提示、交互冷却。
- 手势:拖拽、滑动、双指缩放、长按、短按、惯性、边界限制。
- 键鼠:悬停提示、右键菜单、快捷键冲突、输入框焦点。
- 手柄:焦点导航、确认/取消、摇杆移动、功能轮盘。
- 防误触:连点保护、点击穿透、Loading 遮罩、网络请求中的按钮状态。
UI 与场景点击优先级
三个角色:
| 组件 | 挂哪 | 作用 |
|---|---|---|
| EventSystem | 场景通常 1 个 | 收输入、RaycastAll、派发事件 |
| GraphicRaycaster | 可点 UI 的 Canvas | 命中 Graphic |
| PhysicsRaycaster | 玩法 Camera | 命中 3D Collider |
流程:InputModule → RaycastAll(UI + 场景)→ 结果排序 → 交给最前对象。
排序概念上比:sortOrderPriority → renderOrderPriority → sortingLayer/Order → depth → distance。GraphicRaycaster 跟 Canvas Sort Order 挂钩;PhysicsRaycaster 默认 priority 往往最低,正常 UI 通常排在 3D 前面——前提是 UI 真被射中。
EventSystem 上没有「UI vs 场景优先级」总开关。 可调的是:
| 想控制的 | 去哪设 |
|---|---|
| UI 之间谁先被点 | Canvas Sort Order / Sorting Layer |
| UI 能不能被点 | Graphic Raycast Target + GraphicRaycaster |
| UI 是否被 3D 挡(Camera 模式) | GraphicRaycaster Blocking Objects / Mask |
| 场景能点哪些 | PhysicsRaycaster Event Mask |
两种架构:
- 场景也走 EventSystem(IPointerClick):排序基本交给引擎;遮罩必须能 Raycast。
- 场景自研
Physics.Raycast(更常见):引擎不会自动挡住玩法射线,必须门控:IsPointerOverGameObject或InputGate.CanSceneInteract()(Loading/模态/引导/指针在 UI 上则禁止场景点击)。
遮罩靠「可 Raycast 的全屏图 + CanvasGroup.blocksRaycasts」,不是靠半透明「看起来挡住了」。拖拽相机时在 PointerDown 采样是否在 UI 上,整段手势策略一致。
需要掌握的工具
- Unity Input System:抽象触控、键鼠、手柄、快捷键和重绑定。
- EventSystem、Graphic Raycaster、Physics Raycaster:处理 UI 和场景点击优先级(见上节)。
- Physics Debugger:排查射线、碰撞层和交互触发问题。
- Cinemachine:处理镜头跟随、缩放、碰撞和交互聚焦。
- Device Simulator 与真机调试:验证触控、手势、安全区和设备返回键。
- 自研输入调试面板:显示当前输入源、命中对象、射线路径和焦点状态。
可继续细分方向
- UI 输入与场景输入隔离。
- 触控手势。
- 键鼠和手柄适配。
- 射线交互、防误触和焦点系统。
UI 架构常见模块
- UIManager:统一打开、关闭、查找、层级、返回、缓存。
- WindowBase:面板生命周期、资源句柄、事件注册、动画状态。
- ViewModel 或 Presenter:隔离表现和业务数据。
- RedDotManager:红点树、节点刷新、调试路径。
- GuideLayer:引导遮罩、聚焦目标、点击穿透、步骤恢复。
- SafeAreaAdapter:统一处理设备安全区。
- ListVirtualizer:大量列表复用和增量刷新。
引导框架要点
配置驱动步骤 + 高优先级 GuideLayer;强/弱引导策略不同,共用 Manager。
三坑与底线解法:
- 勿用 Hierarchy 绝对路径找按钮 → 稳定 key + 注册表(
Register/Unregister);动态目标用业务 ID;预制体可挂 Authoring 组件自动登记。 - 引导勿私自 Instantiate 业务面板 → 只调 UIManager.Open;步骤用
requires/commands 声明依赖;恢复读档也跑同一套 Open。 - 必须有超时与失败出口 → findTimeout;SkipStep / SkipGuide / AbortUnlockInput;失败必撤遮罩、解锁输入、写档防反复卡;打点 + 远程关引导。
进阶:强类型 TargetId、Step 依赖图、失败降级弱引导、关键进度以服务端为准、能减则减强引导。调试要能回答「卡在哪一步、目标是谁、为什么找不到」。
开发高级/资深关注点
- UI 打开失败是否有日志和用户可恢复路径。
- UI 关闭后是否释放事件、异步回调和资源引用(含 Addressables 句柄)。
- 快速关闭、重复打开、加载完成时 openId 是否仍有效。
- 红点是否能解释“为什么亮”和“为什么不亮”。
- 适配是否用规则解决,而不是每个界面手动调。
- 列表、背包、邮件、排行榜等高频 UI 是否做虚拟化和增量刷新。
- 引导是否可失败退出、可远程关闭,寻址是否与正式 Open 同路径。
- UI/场景点击是否有统一输入门控,遮罩是否真正可 Raycast。
评论