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