221 lines
6.6 KiB
Markdown
221 lines
6.6 KiB
Markdown
# 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 使用。
|