更新文档

This commit is contained in:
SepComet
2026-06-03 09:59:40 +08:00
parent 7bba8bbb80
commit 367a3432c0
2 changed files with 327 additions and 29 deletions
+220
View File
@@ -0,0 +1,220 @@
# 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<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 底层事件机制,而是通过以下适配层桥接:
- `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 使用。