6.6 KiB
UIModule 使用说明(可选插件)
1. 定位
UIModule 是基于 UGF 的可选 UI 插件,通过 .asmref 融入现有项目程序集:
Base/→SepCore.BaseRuntime/→SepCore.RuntimeUI/→SepCore.Presentation
插件本身没有独立运行时程序集;基础抽象、事件、Controller、View 会并入宿主程序集编译。
不启用插件时,项目仍可继续使用原有 UI 路线。
2. 接入步骤
- 确保以下
.asmref已存在于插件目录:SepCore.Base.UIModule.asmrefSepCore.Runtime.UIModule.asmrefSepCore.Presentation.UIModule.asmref
- 将
Assets/Plugins/UIModule/Prefab/DialogForm.prefab复制到:Assets/GameMain/UI/UIForms
- 处理原有 Dialog 资源(任选其一):
- 重命名旧
DialogForm资源,或 - 确认无引用后删除旧
DialogForm资源
- 重命名旧
- 在
Launcher场景的Customs下创建对象并挂载UIRouterComponent
3. UIRouter 配置
UIRouterComponent 通过 Inspector 配置 UIFormType -> Controller 映射:
UI Form TypeController Type
编辑器会扫描所有实现 IUIFormController 的可实例化类型(非抽象、非接口、非开放泛型)。
运行时 Awake() 会自动注册配置项。
当前 Router 对外接口:
BindUIUseCase(UIFormType uiFormType, IUIUseCase useCase)OpenUIAsync(UIFormType uiFormType, object userData = null, float timeout = 30f)CloseUIAsync(UIFormType uiFormType, object userData = null, float timeout = 30f)
说明:
- 缺少 Controller 绑定时会输出
Error并直接失败 - 不会 fallback 到原始
GameEntry.UI.OpenUIForm(...) - 重新注册同一
UIFormType时,旧 Controller 会异步关闭并被新配置覆盖
4. 当前运行时结构
4.1 基础抽象
IUIFormController 当前是 async-first 接口:
UniTask<int?> OpenUIAsync(object userData = null, float timeout = 30f);
UniTask CloseUIAsync(object userData = null, float timeout = 30f);
void BindUseCase(IUIUseCase useCase);
UIFormControllerBase<TContext, TForm> 当前只提供薄机制层能力:
OpenFormAsync(TContext context, float timeout = 30f)CloseFormAsync(object userData = null, float timeout = 30f)
它负责:
- 调用
GameEntry.UI.OpenUIFormAsync(...) / CloseUIFormAsync(...) - 维护
_context / _form / _formSerialId - 在打开后调用
RefreshUI - 在打开 / 关闭时订阅与解除
SubscribeCustomEvents()
它不负责:
- 解析
userData - 构造
Context - 缓存业务回调
- 安排子类的业务时序
这些流程由具体 Controller 显式实现。
4.2 UGF 异步适配
插件的 async 行为并没有改 UGF 底层事件机制,而是通过以下适配层桥接:
AsyncTaskHelperUIAsyncExtension
其中 UIAsyncExtension 目前已支持:
OpenUIFormAsync(string uiFormAssetName, string uiGroupName, ...)OpenUIFormAsync(int uiFormId, object userData = null, float timeout = 30f)CloseUIFormAsync(int serialId, object userData = null, float timeout = 30f)
5. Controller 实现约定
当前推荐写法是:
- 子类自己在
OpenUIAsync(...)里:- 校验
userData - 构造
RawData / Context - 缓存本次交互需要的临时状态
await OpenFormAsync(...)
- 校验
- 子类自己在
CloseUIAsync(...)里:- 清理本次交互缓存
await CloseFormAsync(...)
- 基类只提供共用开关窗机制,不隐藏业务时序
也就是说,实现 Controller 时,应该尽量做到:
- 看子类本身就能看懂完整流程
- 不依赖“阅读基类钩子调用顺序”来推断业务时机
6. Dialog 当前实现
6.1 相关类型
-
DialogRawData(SepCore.UI)- 外部输入模型
- 可携带:
- 标题 / 内容 / 按钮文本
PauseGameOnClickConfirm / OnClickCancel / OnClickOtherUserData
-
DialogContext(SepCore.UIModule)- 纯展示数据
- 不携带回调
-
DialogController(SepCore.UIModule)- 显式实现
OpenUIAsync / CloseUIAsync - 负责缓存按钮回调与
UserData - 负责把
DialogRawData转成DialogContext
- 显式实现
-
DialogForm(SepCore.UIModule)- 只负责渲染和派发 UI 专用事件
-
DialogEventArgs(SepCore.Event)- 当前 Dialog 只使用一个事件类型
- 通过
ButtonId区分按钮:1:Confirm2:Cancel3:Other
6.2 当前数据流
-
外部调用:
await uiRouter.OpenUIAsync(UIFormType.DialogForm, rawData); -
DialogController.OpenUIAsync(...):- 校验
DialogRawData - 构造
DialogContext - 缓存按钮回调和
UserData await OpenFormAsync(context, timeout)
- 校验
-
DialogForm在按钮点击时只发出:DialogEventArgs.Create(1, null)DialogEventArgs.Create(2, null)DialogEventArgs.Create(3, null)
-
DialogController收到DialogEventArgs后根据ButtonId选出对应回调 -
DialogController调用异步关闭流程,再执行回调:await CloseUIAsync()callback?.Invoke(userData)
-
CloseUIAsync()会先清理缓存,再交给基类关闭 UI
6.3 当前限制
DialogController.OpenUIAsync(null)会失败并输出 warning- Dialog 当前不依赖 UseCase
DialogForm当前没有本地化默认文本回退:- 标题、正文、按钮文本都以
DialogRawData传入值为准
- 标题、正文、按钮文本都以
7. 注意点
-
先注册再打开
使用前需确保UIRouterComponent已完成Awake自动注册。 -
Controller 必须绑定且可实例化
缺少绑定时 Router 直接失败,不会回退到原生打开路径。 -
统一走 async 接口
启用 UIModule 管理的 UI,应统一通过:OpenUIAsync(...)CloseUIAsync(...)
-
Controller 负责显式业务流程
基类只提供开关窗机制;userData校验、Context构造、回调缓存与关闭后的后续动作都应在具体 Controller 中明确写出。 -
保持依赖方向
SepCore.Base:UI 专用事件SepCore.Runtime:UI 基础抽象、RawData、UseCase、UIRouterComponentSepCore.Presentation:Controller、Context、View
-
Prefab 脚本一致性
Dialog prefab 上必须挂载插件内SepCore.UIModule.DialogForm。 -
Dialog 事件语义
当前 Dialog 使用单一DialogEventArgs+ButtonId区分按钮,不再使用多个按钮事件类型。 -
Dialog 当前按单实例思路使用
DialogController当前没有额外用sender/serialId做多实例过滤,默认按单实例 Dialog 使用。