游戏 UI 中的“框架”通常指两类东西:负责显示界面的 UI 系统,以及负责组织界面代码的架构框架。
以 Unity 为例,uGUI、UI Toolkit 属于第一类;MVC、MVP、MVVM,以及窗口管理、页面导航、数据绑定等机制属于第二类。
它们可以组合使用,例如:
用 uGUI 制作界面,用 MVP 组织代码,再用 UIManager 管理窗口的打开、关闭和层级。
UI系统
| UI 系统 | 核心方式 | 常见用途 | 主要特点 |
|---|---|---|---|
| uGUI | Canvas + GameObject + Component | 游戏 HUD、背包、商店、弹窗 | 与 Unity 场景、Prefab、动画系统结合紧密 |
| UI Toolkit | VisualElement + UXML + USS | 编辑器工具、菜单、设置、列表型界面 | 文档结构、样式与逻辑分离 |
| IMGUI | 在 OnGUI() 中通过代码绘制 |
简单调试界面、旧编辑器工具 | 写起来直接,但不适合作为复杂游戏 UI 的主系统 |
| FairyGUI 等第三方方案 | 专用编辑器 + 对应运行时 | 界面量大、依赖 UI 设计协作的项目 | 强调界面制作、组件复用、过渡动画等完整工作流 |
这里的选择决定了“界面怎么制作和渲染”,但不会自动解决“业务逻辑怎么组织”。
代码框架
无论用哪种 UI 系统,都会遇到同一个问题:
用户点了按钮,谁处理操作?数据变了,谁更新界面?
MVC、MVP、MVVM 就是在回答这个问题。
| 架构 | 主要角色 | 界面如何更新 | 适合情况 |
|---|---|---|---|
| MVC | Model、View、Controller | 由 Controller 或模型通知机制推动 | 流程简单、团队约定清晰 |
| MVP | Model、View、Presenter | Presenter 显式调用 View | uGUI 项目、希望逻辑清晰且容易测试 |
| MVVM | Model、View、ViewModel | 通过绑定或状态通知更新 | 表单、列表、状态较多的界面 |
| 单向数据流 | State、Action、Reducer/处理器、View | 操作更新状态,界面读取状态 | 状态组合复杂、需要追踪变化的界面 |
| 这些名字不是自动就能带来良好设计。关键在于:显示、交互协调和业务规则是否有明确边界。 |
项目设计
MVC、MVP、MVVM 只解决代码组织的一部分。游戏项目还需要处理以下问题。
| 能力 | Unity 中的典型问题 |
|---|---|
| 窗口管理 | 同一个窗口能否重复打开?关闭后销毁还是缓存? |
| 生命周期 | 什么时候绑定事件、刷新数据、播放动画、释放资源? |
| 分层 | HUD、普通页面、弹窗、加载遮罩谁在上面? |
| 导航 | 返回键关闭弹窗,还是回到上一个页面? |
| 资源加载 | Prefab 从哪里加载?异步加载如何避免重复? |
| 数据同步 | 金币变化后,哪些可见页面需要刷新? |
| 列表复用 | 几百个背包物品如何避免生成几百套复杂控件? |
| 输入管理 | 弹窗打开后,下面的页面还能否被点击? |
| 分辨率适配 | 安全区域、横竖屏、锚点、长文本如何处理? |
| 异步取消 | 窗口关闭后,请求结果是否还会写入控件? |
| 其中最容易被低估的是生命周期和异步处理。 |
假设玩家打开排行榜,界面开始请求数据,然后玩家马上关闭它:
打开排行榜
↓
开始网络请求
↓
关闭排行榜
↓
请求返回
此时如果直接执行:rankText.text = result;
可能出现对象已经销毁、数据更新到下一次打开的界面,或旧请求覆盖新请求的情况。
框架需要明确约定:关闭时取消请求,或在结果返回时检查本次打开是否仍然有效。
窗口生命周期
一种常见设计是:
创建
↓
初始化
↓
打开
↓
显示
↓
关闭
↓
隐藏并缓存 / 销毁
假如约定以下方法:
1 | OnCreate() |
这些名字的具体含义由框架决定,并不是 Unity 内置生命周期。
还要与 Unity 的 Awake、OnEnable、OnDisable、OnDestroy 区分。例如隐藏一个缓存窗口,只会使它停用;下一次打开时,Awake() 不会重新执行。
分层与导航
常见层级可以是:
HUD
Page
Popup
Loading
Toast
但“画在上面”和“阻止下面接收输入”是两个问题。一个透明遮罩即使视觉上位于最上层,也需要正确配置射线或输入处理,才能阻止点击穿透。
导航则适合使用页面栈:
主界面 → 背包 → 物品详情
返回时:
物品详情 → 背包 → 主界面
Toast、加载遮罩和长期显示的 HUD 通常不应该全部放进同一套返回栈。
缓存与列表虚拟化
这两者容易混淆:
- 窗口缓存:关闭背包后保留整个背包实例,下次直接复用。
- 对象池:反复使用物品格子,减少创建和销毁。
- 列表虚拟化:只保留可视区域附近的格子,根据滚动位置展示不同数据。
有 1000 件物品时,即使通过对象池创建了 1000 个格子,它们仍可能带来大量布局和渲染成本。虚拟化解决的是“同时存在多少个显示单元”的问题。