UI系统概述
OpenMon的UI层位于 Assets/OpenMon/Core/Runtime/UI/,完全基于Unity的UGUI(Canvas + RectTransform)构建。每个屏幕、菜单和对话框都共享两个基础构建块:MenuSelector 用于可导航的列表/网格,以及 DialogManager 用于模态文本框、选项和复杂弹出窗口。
架构一览
UI/
├── MenuSelector.cs # 所有可导航菜单的基类
├── ScrollableMenuSelector.cs # MenuSelector + ScrollRect支持
├── VirtualizedMenuSelector.cs # MenuSelector + 回收/池化项
├── MenuItem.cs # MenuSelector中的单个按钮
├── VirtualizedMenuItem.cs # 感知其在池中行的MenuItem
├── UIElementsInstaller.cs # UI工厂的Zenject安装器
├── Battle/ # 战斗HUD:主菜单、招式菜单、目标选择器
├── Bags/ # 带标签页的背包/物品栏画面
├── Dex/ # 带标签页的完整图鉴风格画面
├── Dialogs/ # DialogManager + 选项菜单、文本输入、弹窗
├── GameMenu/ # 游戏内暂停菜单 (图鉴、宝可梦、背包、地图等)
├── MainMenu/ # 标题画面:新游戏、继续、制作人员
├── Monsters/ # 宝可梦列表画面、摘要、存储
├── Options/ # 设置画面(音量、分辨率、语言等)
├── Shops/ # 买卖商店界面
├── Quests/ # 任务日志画面
├── Profile/ # 玩家资料 / 训练家卡
├── Map/ # 世界地图叠加层
└── ...
两大支柱
MenuSelector — 通用菜单导航
MenuSelector 是几乎所有交互式菜单继承的基类。它处理:
- 垂直或水平导航 — 通过
Navigation枚举。 - 选择器箭头 — 通过 MCE 内置的补间 API 动画移到悬停项。
- 音频反馈 — 导航和选择时。
- 输入集成 — 通过
IInputReceiver接口和IInputManager。 - 循环导航 — 从最后一项继续导航会回到第一项,反之亦然。
- 长按滚动 — 当
CanHoldToScrollFaster启用时,按住输入会以可配置的间隔重复导航。
有两种专门的变体:
| 类名 | 用例 |
|---|---|
ScrollableMenuSelector | 添加 ScrollRect,自动滚动以保持选中项可见。 |
VirtualizedMenuSelector<TData, TButton, TFactory> | 大数据集(例如图鉴、存储)的对象池列表。随着玩家滚动回收按钮实例。 |
详见菜单页面深入了解 MenuSelector。
DialogManager — 模态对话框和复杂弹窗
DialogManager 是一个 单例,拥有游戏中所有模态UI覆盖层。它管理:
- 基本文本对话框 — 带有打字机动画、说话者姓名面板和本地化。
- 选项菜单 — 是/否,最多11个选项。
- 全屏画面 — 通过静态方法访问:图鉴、背包、阵容、商店、选项、资料、任务、地图。
- 专业化弹窗 — XP获取面板、招式替换对话框、招式传授、新宝可梦弹窗、文本输入对话框。
- 通知 — 通过子
NotificationManager。
DialogManager通过Zenject的工厂模式实例化:
// DialogManagerInstaller.cs
Container.BindFactory<DialogManager, DialogManager.Factory>()
.FromComponentInNewPrefab(Prefab);
详见对话框页面了解完整API。
依赖注入
所有UI工厂通过两个Zenject ScriptableObjectInstaller 注册:
DialogManagerInstaller— 从预制件绑定DialogManager.Factory。UIElementsInstaller— 绑定物品按钮、宝可梦按钮、图鉴按钮、招式按钮、任务按钮、存档按钮等的工厂。每个绑定通过.WhenInjectedInto<T>()限定作用域,以便每个屏幕使用正确的预制件变体。
// UIElementsInstaller示例
Container.BindFactory<ItemButton, ItemButton.Factory>()
.FromComponentInNewPrefab(BagItemButtonPrefab)
.AsCached()
.WhenInjectedInto<BagTab>()
.Lazy();
专用屏幕控制器
每个主屏幕扩展 MenuSelector(用于网格/列表导航)、VirtualizedMenuSelector(用于大型池化列表)或 HidableUiElement(用于自定义布局)。所有屏幕都通过 DialogManager 启动:
| 屏幕 | 控制器 | 基类 | 通过以下启动 |
|---|---|---|---|
| 游戏菜单 | GameMenuScreen | MenuSelector | DialogManager.ShowGameMenu(...) |
| 宝可梦列表 | MonstersMenuScreen | HidableUiElement | DialogManager.ShowPlayerRosterMenu(...) |
| 背包 / 物品栏 | BagScreen | HidableUiElement | DialogManager.ShowBag(...) |
| 图鉴 | DexScreen | VirtualizedMenuSelector | DialogManager.ShowDexScreen(...) |
| 单宝可梦图鉴 | SingleMonsterDexScreen | HidableUiElement | DialogManager.ShowSingleMonsterDexScreen(...) |
| 商店 | ShopDialog | HidableUiElement | DialogManager.ShowShop(...) |
| 选项 | OptionsScreen | MenuSelector | DialogManager.ShowOptionsMenu() |
| 资料 | ProfileScreen | HidableUiElement | DialogManager.ShowProfileScreen(...) |
| 任务 | QuestsScreen | VirtualizedMenuSelector | DialogManager.ShowQuestsScreen(...) |
| 文本输入 | TextInputDialog | HidableUiElement | DialogManager.RequestTextInput(...) |
| 招式传授 | MoveTutorDialog | HidableUiElement | DialogManager.ShowMoveTutorDialog(...) |
战斗UI
战斗HUD有自己的一套 MenuSelector 子类,不由DialogManager管理:
| 控制器 | 基类 | 用途 |
|---|---|---|
MainBattleMenu | MenuSelector | 战斗 / 背包 / 宝可梦 / 逃跑 选择 |
BattleMoveMenuSelector | MenuSelector | 战斗中的招式选择 |
TargetMonstersMenuSelector | MenuSelector | 多战斗中的目标选择 |
BattleMonstersMenu | HidableUiElement | 战斗中 切换宝可梦 |
BattleInfoPanel | HidableUiElement<MoveInfoPanel> | 详细战斗状态覆盖层 |
LastBallMenu | MenuSelector | 快速投掷上次使用的球 |
输入系统集成
所有菜单实现 IInputReceiver,它扩展了Unity新输入系统的操作接口:
public interface IInputReceiver : MCEInputActions.IMainActions,
MCEInputActions.IUIActions,
MCEInputActions.ITextInputActions
{
InputType GetInputType();
void OnStateEnter();
void OnStateExit();
string GetDebugName();
}
IInputManager 维护一个接收器的栈。当菜单调用 RequestInput() 时,它会将自己推入栈中并成为活动接收器。调用 ReleaseInput() 会将其弹出。这自然地处理了嵌套菜单——从游戏菜单中打开背包会将背包的输入接收器推到顶部,关闭时恢复游戏菜单的输入。
自定义UI屏幕的最佳实践
- 继承
MenuSelector(大型列表使用ScrollableMenuSelector/VirtualizedMenuSelector)。 - 为菜单中的每个按钮添加
MenuItem组件。设置选择器箭头的ArrowSelectorPosition变换。 - 如果您的屏幕使用池化/虚拟化项目,在
UIElementsInstaller中注册工厂。 - 按照现有模式添加静态方法和序列化引用,从
DialogManager启动。 - 对所有检视器引用使用
[SerializeField] private— 除非字段是API的一部分,否则绝不将它们公开为public。 - 对所有玩家可见的文本使用本地化键。
LocalizedTextMeshPro和LocalizedTypeWriterTMP组件会自动处理。 - 新编辑器工具优先使用UI Toolkit,但运行时游戏UI坚持使用UGUI以保持与现有系统的一致性。