vampire-like/docs/UIModule-使用说明.md

221 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 使用。