文档记录
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,4 +11,3 @@ AGENTS.md
|
||||
QWEN.md
|
||||
Client/.claude/
|
||||
dev-ab-svr/debugconfig.json
|
||||
doc/
|
||||
141
docs/game-core-requirement-tiers.md
Normal file
141
docs/game-core-requirement-tiers.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# 通用游戏 Core 需求分档文档
|
||||
|
||||
## 文档说明
|
||||
|
||||
本文档定义 FlowScope Game Core 的需求分档。P0 的详细需求已经收敛到:
|
||||
|
||||
- `docs/requirements/p0-requirements-set.md`
|
||||
- `docs/requirements/p0-container.md`
|
||||
- `docs/requirements/p0-gameflow.md`
|
||||
- `docs/requirements/p0-feature.md`
|
||||
- `docs/requirements/p0-data-r3.md`
|
||||
- `docs/requirements/p0-configprovider.md`
|
||||
- `docs/requirements/p0-saveservice.md`
|
||||
- `docs/requirements/p0-resourceservice.md`
|
||||
- `docs/requirements/p0-uimanager.md`
|
||||
- `docs/requirements/p0-audioservice.md`
|
||||
|
||||
如本文档与上述 P0 详细文档冲突,以 P0 详细文档为准。
|
||||
|
||||
---
|
||||
|
||||
## 分档原则
|
||||
|
||||
1. 先满足游戏开发闭环,再追求通用框架完整性。
|
||||
2. 显式接口优先,不暗猜业务模型,不预设具体 Data 类。
|
||||
3. P0 必须能支撑一个真实休闲游戏的最小纵向切片。
|
||||
4. P1 扩展常用生产能力,但不破坏 P0 核心接口。
|
||||
5. P2 才考虑包分发、编辑器体验、多后端生态和跨项目复用。
|
||||
6. P3 作为长期演进方向,不进入近期交付承诺。
|
||||
|
||||
---
|
||||
|
||||
## P0:最小可运行核心
|
||||
|
||||
目标:跑通一个休闲/超休闲 Unity 游戏的启动、配置加载、存档加载、进入初始 Feature、打开 MVVM UI、响应 ViewModel/Data 变化、退出并保存。
|
||||
|
||||
P0 包含:
|
||||
|
||||
| 模块 | 决策 |
|
||||
|------|------|
|
||||
| Container | 显式工厂、Attribute + Source Generator、Attribute + 运行时反射均进入 P0 |
|
||||
| GameFlow | 负责创建 Feature scope、FeatureContext,并编排启动/切换/关闭 |
|
||||
| Feature | 只负责业务行为,消费 GameFlow 提供的 FeatureContext |
|
||||
| R3 + Data | 直接使用 R3,不自研响应式系统 |
|
||||
| ConfigProvider | P0 只支持 JSON 强类型读取 |
|
||||
| SaveService | P0 只支持本地轻量存档,File 或 PlayerPrefs |
|
||||
| ResourceService | P0 支持引用计数和 ResourceGroup,默认只支持 Addressables |
|
||||
| UIManager | P0 支持可配置层级和层内 LIFO 栈 |
|
||||
| AudioService | P0 支持 BGM/SFX、音量、静音、简单 SFX 池和轻量 AudioHandle |
|
||||
|
||||
P0 不包含:
|
||||
|
||||
- CSV / Luban / ScriptableObject 配置。
|
||||
- 云存档、加密、压缩、复杂版本迁移。
|
||||
- Resources / AssetBundle / YooAsset 资源后端。
|
||||
- 完整 UI 路由系统。
|
||||
- 全局 EventBus。
|
||||
- AudioMixer、3D 音频、动态音乐。
|
||||
- 编辑器配置检查和资源分析工具。
|
||||
|
||||
P0 完成后应能做到:
|
||||
|
||||
- Unity 启动后进入 `GameBootstrap`。
|
||||
- 注册核心服务和业务 Data。
|
||||
- 加载 JSON 配置。
|
||||
- 加载或创建存档数据。
|
||||
- 进入 `MainMenuFeature`。
|
||||
- 打开 `MainMenuPanel`。
|
||||
- ViewModel 使用 R3 驱动 UI 刷新。
|
||||
- 切换或关闭 Feature 时释放资源和订阅。
|
||||
- 关闭游戏时保存数据。
|
||||
|
||||
---
|
||||
|
||||
## P1:常用生产能力
|
||||
|
||||
目标:在 P0 核心稳定后,扩展常用生产能力,仍然保持 Core 主接口稳定。
|
||||
|
||||
候选能力:
|
||||
|
||||
| 能力 | 建议形式 |
|
||||
|------|----------|
|
||||
| 按模块配置加载 | `IConfigSource` 扩展 |
|
||||
| CSV / Luban 配置 | `IConfigParser` 插件 |
|
||||
| 存档版本迁移 | `ISaveMigration` 插件 |
|
||||
| Resources 后端 | `IResourceBackend` 插件 |
|
||||
| YooAsset 后端 | `IResourceBackend` 插件 |
|
||||
| UI 返回策略 | `IUIScreenNavigator` 扩展 |
|
||||
| Panel 预加载 | UIManager 扩展服务 |
|
||||
| AudioMixer 分组 | AudioService 扩展 |
|
||||
| 3D 音频 | AudioService 扩展 |
|
||||
|
||||
---
|
||||
|
||||
## P2:包分发与生态化
|
||||
|
||||
目标:当多个项目复用需求明确后,把 Game Core 整理为更标准的 Unity Package 和可插拔生态。
|
||||
|
||||
候选能力:
|
||||
|
||||
- `com.flowscope.gamecore` 包结构。
|
||||
- `package.json` / `README.md` / `CHANGELOG.md` / `Samples~`。
|
||||
- 独立 asmdef 拆分。
|
||||
- Editor 导入向导。
|
||||
- 配置校验工具。
|
||||
- 资源扫描和分析工具。
|
||||
- 云存档适配包。
|
||||
- AssetBundle 适配包。
|
||||
- UI 路由包。
|
||||
|
||||
---
|
||||
|
||||
## P3:长期演进方向
|
||||
|
||||
目标:只作为方向保留,不进入近期交付范围。
|
||||
|
||||
候选能力:
|
||||
|
||||
- Source Generator 深度优化。
|
||||
- `[Inject]` 属性/方法注入。
|
||||
- 全局 EventBus 并支持 R3/默认实现切换。
|
||||
- 完整网络长连接、断线重连、心跳机制。
|
||||
- 云存档冲突解决。
|
||||
- 热更新框架。
|
||||
- UI 自动绑定框架。
|
||||
- 大型项目资源预算和内存分析工具。
|
||||
|
||||
---
|
||||
|
||||
## 当前实施建议
|
||||
|
||||
当前只实施 P0。P0 的并行实施计划见:
|
||||
|
||||
- `docs/superpowers/plans/2026-05-15-p0-parallel-implementation-plan.md`
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. 先完成 `p0-contracts`。
|
||||
2. 合并共享契约。
|
||||
3. 再并行执行各模块 worktree。
|
||||
4. 最后由 `p0-integration` 做统一集成和 Sample 验证。
|
||||
487
docs/game-core-requirements.md
Normal file
487
docs/game-core-requirements.md
Normal file
@@ -0,0 +1,487 @@
|
||||
# 通用游戏 Core 需求文档
|
||||
|
||||
## 文档说明
|
||||
|
||||
本文档描述一份**通用的游戏 Core 模块模板**——不绑定具体游戏类型,适用于 RPG、FPS、RTS、解谜等任何品类。每个项目基于此模板补充具体的数值和细节即可。
|
||||
|
||||
---
|
||||
|
||||
## 1. 架构总览
|
||||
|
||||
Core 是游戏的底层基础设施,不包含任何业务逻辑。它的职责是为上层业务提供可复用的系统服务。
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ 业务层 (Game Logic) │
|
||||
│ 各游戏自行定义,不归 Core 管 │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ Core 层 │
|
||||
├──────────┬──────────┬──────────┬─────────────┤
|
||||
│ Game │ State │ Input │ Scene │
|
||||
│ Loop │ Machine │ System │ Manager │
|
||||
├──────────┼──────────┼──────────┼─────────────┤
|
||||
│ Event │ Data │ Audio │ UI │
|
||||
│ System │ Manager │ System │ Framework │
|
||||
├──────────┼──────────┼──────────┼─────────────┤
|
||||
│ Save │ Config │ Time │ Asset │
|
||||
│ System │ System │ System │ Loader │
|
||||
└──────────┴──────────┴──────────┴─────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块依赖与构建顺序
|
||||
|
||||
Core 模块之间存在依赖关系,必须按层构建:
|
||||
|
||||
```
|
||||
第一层(无依赖):Game Loop, Event System, Time System, Asset Loader
|
||||
第二层(依赖一层):Config System, Input System, State Machine
|
||||
第三层(依赖前两层):Data Manager, Scene Manager, Audio System, UI Framework
|
||||
第四层(依赖前三层):Save System
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 通用验收规则
|
||||
|
||||
这些规则适用于 Core 的所有模块,不分优先级:
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 零业务耦合 | Core 不包含任何游戏业务逻辑,只提供服务 |
|
||||
| 接口稳定 | Core API 一旦确定,业务层不因 Core 变动而改动 |
|
||||
| 可独立测试 | 每个模块可在无其他模块的情况下单元测试 |
|
||||
| 日志完备 | 关键操作(加载、切换、错误)有结构化日志 |
|
||||
| 文档自洽 | 每个接口有参数说明和返回值约定 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 模块详细描述
|
||||
|
||||
每个模块按统一模板编写:目标 → 职责 → 接口 → 依赖 → 验收标准。
|
||||
|
||||
---
|
||||
|
||||
### 4.1 Game Loop(主循环)
|
||||
|
||||
#### 目标
|
||||
|
||||
驱动整个游戏帧更新,控制初始化、运行、暂停、退出流程。
|
||||
|
||||
#### 状态
|
||||
|
||||
```
|
||||
Init → Loading → Running → Paused → Quitting
|
||||
```
|
||||
|
||||
#### 职责
|
||||
|
||||
- 固定频率调用各子系统 Tick(逻辑帧)
|
||||
- 渲染帧与逻辑帧解耦(可选)
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `Start()` | 启动游戏循环 |
|
||||
| `Pause()` | 暂停所有子系统 Tick |
|
||||
| `Resume()` | 恢复所有子系统 Tick |
|
||||
| `Quit()` | 退出并释放资源 |
|
||||
| `OnFixedUpdate(dt)` | 逻辑帧回调 |
|
||||
| `OnUpdate(dt)` | 渲染帧回调 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
无(最底层)
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 启动 → Loading → Running 流程正常
|
||||
- [ ] 暂停时所有子系统停止 Tick
|
||||
- [ ] 退出时正确释放资源
|
||||
|
||||
---
|
||||
|
||||
### 4.2 State Machine(状态机)
|
||||
|
||||
#### 目标
|
||||
|
||||
管理游戏全局状态和各模块局部状态的切换。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 状态注册、切换、查询
|
||||
- 状态切换时触发 Enter/Exit 回调
|
||||
- 支持状态栈(push/pop)用于暂停/恢复场景
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `Register(stateId, state)` | 注册一个状态 |
|
||||
| `SwitchTo(stateId)` | 切换到指定状态 |
|
||||
| `Push(stateId)` | 压栈当前状态并切换 |
|
||||
| `Pop()` | 弹栈恢复上一个状态 |
|
||||
| `CurrentState` | 当前状态 ID(只读) |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Event System
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 切换状态时 Enter/Exit 回调正确执行
|
||||
- [ ] Push/Pop 不破坏前一个状态
|
||||
- [ ] 不存在的状态切换抛出明确错误
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Input System(输入系统)
|
||||
|
||||
#### 目标
|
||||
|
||||
统一处理键鼠、手柄、触屏输入,向上层提供抽象的输入动作。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 原始输入 → 逻辑动作映射(Move/Jump/Attack)
|
||||
- 支持运行时切换输入方案
|
||||
- 支持输入录制/回放(用于测试/回放系统)
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `BindAction(actionName, callback)` | 绑定动作回调 |
|
||||
| `EnableScheme(schemeName)` | 启用指定输入方案 |
|
||||
| `GetAxis(axisName) → float` | 获取轴向值 |
|
||||
| `IsPressed(actionName) → bool` | 查询动作是否按下 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Config System(键位配置读取)
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 键鼠和手柄可无缝切换
|
||||
- [ ] 自定义键位后立即生效
|
||||
- [ ] 同一帧多次读取结果一致
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Scene Manager(场景管理)
|
||||
|
||||
#### 目标
|
||||
|
||||
管理场景/关卡的加载、卸载、切换。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 同步/异步加载场景
|
||||
- 切换时显示 Loading 界面
|
||||
- 支持附加式加载(Additive)实现流式开放世界
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `LoadScene(sceneName, additive)` | 加载场景 |
|
||||
| `UnloadScene(sceneName)` | 卸载场景 |
|
||||
| `OnSceneLoaded → callback` | 场景加载完成回调 |
|
||||
| `OnSceneUnloading → callback` | 场景卸载前回调 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Asset Loader, Event System
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 场景切换时无黑屏闪烁
|
||||
- [ ] 异步加载期间有进度反馈
|
||||
- [ ] 卸载场景后内存正确释放
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Event System(事件总线)
|
||||
|
||||
#### 目标
|
||||
|
||||
模块间解耦通信,替代直接引用。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 事件注册、派发、注销
|
||||
- 支持泛型事件(带 payload)
|
||||
- 支持事件优先级
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `Subscribe(eventId, handler)` | 订阅事件 |
|
||||
| `Unsubscribe(eventId, handler)` | 取消订阅 |
|
||||
| `Dispatch(eventId, payload)` | 派发事件 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
无
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] handler 注销后不再收到事件
|
||||
- [ ] 循环派发不会死循环(深度限制或检测)
|
||||
- [ ] 事件丢失时有日志警告
|
||||
|
||||
---
|
||||
|
||||
### 4.6 Data Manager(数据管理)
|
||||
|
||||
#### 目标
|
||||
|
||||
统一管理运行时数据(配置表、存档、缓存)。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 配置表加载与热更新
|
||||
- 运行时数据容器的 CRUD
|
||||
- 数据变更通知
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `LoadConfig(tableName)` | 加载配置表 |
|
||||
| `Get<T>(key) → T` | 按主键获取数据 |
|
||||
| `Set(key, value)` | 写入数据 |
|
||||
| `OnDataChanged(key) → event` | 数据变更通知 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Asset Loader, Event System
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 配置表加载失败使用默认值并报警
|
||||
- [ ] Get 不存在的 key 返回 default(T) 而非报错
|
||||
|
||||
---
|
||||
|
||||
### 4.7 Audio System(音频系统)
|
||||
|
||||
#### 目标
|
||||
|
||||
统一管理 BGM、SFX、环境音的播放控制。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 音频资源池化
|
||||
- 分通道音量控制(Master/BGM/SFX/Voice)
|
||||
- 3D 空间音频
|
||||
- 音频淡入淡出
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `PlayBGM(clipId, fadeIn)` | 播放背景音乐 |
|
||||
| `PlaySFX(clipId, position)` | 播放音效(支持 3D 位置) |
|
||||
| `SetVolume(channel, value)` | 设置通道音量 |
|
||||
| `StopAll()` | 停止所有音频 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Data Manager, Config System
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 同一 SFX 快速重复播放不卡顿
|
||||
- [ ] 场景切换时 BGM 可配置是否延续
|
||||
- [ ] 音量设置持久化到存档
|
||||
|
||||
---
|
||||
|
||||
### 4.8 UI Framework(UI 框架)
|
||||
|
||||
#### 目标
|
||||
|
||||
管理 UI 面板的打开、关闭、层级、动画。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 面板栈管理(打开新面板压栈,关闭弹栈)
|
||||
- 面板缓存与复用
|
||||
- 统一的打开/关闭动画
|
||||
- 遮罩与模态控制
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `Open(panelId, params)` | 打开面板 |
|
||||
| `Close(panelId)` | 关闭面板 |
|
||||
| `CloseAll()` | 关闭所有面板 |
|
||||
| `IsOpen(panelId) → bool` | 查询面板状态 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Event System, Asset Loader
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 打开新面板时下层面板交互自动屏蔽
|
||||
- [ ] 连续打开/关闭 10 次无内存泄漏
|
||||
- [ ] 面板动画未播完时可打断
|
||||
|
||||
---
|
||||
|
||||
### 4.9 Save System(存档系统)
|
||||
|
||||
#### 目标
|
||||
|
||||
游戏数据的持久化存储与读取。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 多存档位管理
|
||||
- 增量存档(只存变更)
|
||||
- 存档版本迁移(旧版存档兼容)
|
||||
- 自动存档触发
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `Save(slotId)` | 保存到指定存档位 |
|
||||
| `Load(slotId)` | 从指定存档位加载 |
|
||||
| `Delete(slotId)` | 删除存档 |
|
||||
| `ListSaves() → saveInfo[]` | 列出所有存档 |
|
||||
| `OnAutoSaveTrigger → event` | 自动存档触发事件 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Data Manager, Event System
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 存档过程中断电,旧存档不被损坏(双写机制)
|
||||
- [ ] 旧版本存档能正确迁移到新版本
|
||||
- [ ] 自动存档不造成可感知的卡顿
|
||||
|
||||
---
|
||||
|
||||
### 4.10 Config System(配置系统)
|
||||
|
||||
#### 目标
|
||||
|
||||
管理游戏全局配置和用户偏好设置。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 分层配置:默认值 → 项目配置 → 用户配置
|
||||
- 运行时修改用户配置并持久化
|
||||
- 配置项变更通知
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `Get(key) → value` | 获取配置值 |
|
||||
| `Set(key, value, persist)` | 设置配置值(可选持久化) |
|
||||
| `ResetToDefault()` | 重置为默认配置 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
Save System
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 用户配置覆盖优先级正确
|
||||
- [ ] 非法配置值回退到默认值
|
||||
- [ ] 配置修改后立即生效
|
||||
|
||||
---
|
||||
|
||||
### 4.11 Time System(时间系统)
|
||||
|
||||
#### 目标
|
||||
|
||||
提供统一的时间源,控制游戏时间流速。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 真实时间 vs 游戏时间分离
|
||||
- 时间缩放(慢动作、加速)
|
||||
- 帧间 deltaTime 提供者
|
||||
- 定时器/冷却器管理
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `deltaTime → float` | 受缩放影响的帧间隔 |
|
||||
| `unscaledDeltaTime → float` | 不受缩放影响的帧间隔 |
|
||||
| `timeScale` | 时间缩放倍率(读写) |
|
||||
| `SetTimer(duration, callback) → timerId` | 设置定时器 |
|
||||
| `CancelTimer(timerId)` | 取消定时器 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
无
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] timeScale = 0 时所有依赖 deltaTime 的逻辑冻结
|
||||
- [ ] 定时器在暂停后恢复时正确续计
|
||||
- [ ] 时间精度不随游戏运行时长漂移
|
||||
|
||||
---
|
||||
|
||||
### 4.12 Asset Loader(资源加载)
|
||||
|
||||
#### 目标
|
||||
|
||||
统一管理资源的加载、缓存、卸载、引用计数。
|
||||
|
||||
#### 职责
|
||||
|
||||
- 同步/异步加载
|
||||
- 引用计数自动回收
|
||||
- 加载优先级
|
||||
- 内存监控与阈值卸载
|
||||
|
||||
#### 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `LoadAsync<T>(assetPath) → Task<T>` | 异步加载资源 |
|
||||
| `LoadSync<T>(assetPath) → T` | 同步加载资源 |
|
||||
| `Release(asset)` | 释放资源引用 |
|
||||
| `ForceUnloadUnused()` | 强制卸载未使用资源 |
|
||||
|
||||
#### 依赖
|
||||
|
||||
无
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [ ] 引用归零的资源在下一帧自动卸载
|
||||
- [ ] 异步加载失败有重试机制
|
||||
- [ ] 同一资源并发加载只触发一次实际 IO
|
||||
|
||||
---
|
||||
|
||||
## 5. 术语表
|
||||
|
||||
| 术语 | 定义 |
|
||||
|------|------|
|
||||
| Game Loop | 游戏主循环,驱动每帧更新的入口 |
|
||||
| Feature | 自包含的业务特性模块,拥有独立的资源和生命周期 |
|
||||
| Scene | 游戏场景,可包含一个或多个 Feature |
|
||||
| Data Class | 按业务域拆分的运行时数据容器,保持纯 C# |
|
||||
| ViewModel | MVVM 模式中的视图模型,连接 Data 和 UI Panel |
|
||||
| Panel | UI 面板,由 UI Framework 管理生命周期 |
|
||||
| Scope | DI 容器的子作用域,随 Feature 创建和销毁 |
|
||||
| Handle | 资源引用句柄,Dispose 时减少引用计数 |
|
||||
| Config Row | 配置表中的单行数据,必须带有 Id 字段 |
|
||||
| Tick | 一次逻辑帧更新调用 |
|
||||
239
docs/guide/unity-package-git-workflow.md
Normal file
239
docs/guide/unity-package-git-workflow.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Unity Package 开发与 Git 分发指南
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述如何将 Unity 代码组织为 Package 结构,并通过 Git 仓库进行版本管理和分发更新。适用于 FlowScope 框架各模块的独立开发与集成。
|
||||
|
||||
## Package 标准结构
|
||||
|
||||
```text
|
||||
com.flowscope.xxx/
|
||||
├── package.json # 必须 — 包的元数据与依赖声明
|
||||
├── README.md # 包说明
|
||||
├── CHANGELOG.md # 版本变更记录
|
||||
├── LICENSE # 许可证
|
||||
├── Runtime/
|
||||
│ ├── ExampleScript.cs
|
||||
│ └── com.flowscope.xxx.Runtime.asmdef
|
||||
├── Editor/
|
||||
│ └── com.flowscope.xxx.Editor.asmdef
|
||||
├── Tests/
|
||||
│ ├── Runtime/
|
||||
│ │ └── com.flowscope.xxx.Tests.Runtime.asmdef
|
||||
│ └── Editor/
|
||||
│ └── com.flowscope.xxx.Tests.Editor.asmdef
|
||||
└── Samples~/ # 可选示例(~ 后缀表示在 Package Manager 中隐藏)
|
||||
└── Demo/
|
||||
└── ...
|
||||
```
|
||||
|
||||
## package.json 说明
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "com.flowscope.xxx",
|
||||
"version": "0.1.0",
|
||||
"displayName": "FlowScope XXX",
|
||||
"description": "模块描述",
|
||||
"unity": "2022.3",
|
||||
"dependencies": {
|
||||
"com.flowscope.core": "0.1.0"
|
||||
},
|
||||
"author": {
|
||||
"name": "FlowScope Team"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/flowscope/xxx.git"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
关键字段:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `name` | 反向域名格式,全局唯一 |
|
||||
| `version` | 语义化版本(SemVer) |
|
||||
| `unity` | 最低兼容 Unity 版本 |
|
||||
| `dependencies` | 该包依赖的其他 Package |
|
||||
|
||||
## 开发工作流
|
||||
|
||||
### 推荐:在 Unity 项目内开发
|
||||
|
||||
直接在目标 Unity 项目的 `Packages/` 目录下创建包文件夹,Unity 自动识别,开发体验与 `Assets/` 中写脚本一致。
|
||||
|
||||
```text
|
||||
MyUnityProject/
|
||||
├── Assets/
|
||||
│ └── Scenes/
|
||||
│ └── Test.unity # 测试场景
|
||||
├── Packages/
|
||||
│ ├── manifest.json
|
||||
│ └── com.flowscope.xxx/ ← 在这里开发
|
||||
│ ├── package.json
|
||||
│ ├── Runtime/
|
||||
│ └── Editor/
|
||||
```
|
||||
|
||||
`manifest.json` 中自动生成引用:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"com.flowscope.xxx": "file:com.flowscope.xxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移已有代码
|
||||
|
||||
如果代码已在 `Assets/` 中:
|
||||
|
||||
1. 在 `Packages/` 下建好包结构和 `package.json`
|
||||
2. 将脚本从 `Assets/` **移动**到 `Packages/com.flowscope.xxx/Runtime/`
|
||||
3. 创建 `.asmdef`(Assembly Definition),Packages 下的脚本不会自动编译到 `Assembly-CSharp.dll`
|
||||
4. Unity 重新编译,确认引用无误
|
||||
|
||||
## Assembly Definition (.asmdef)
|
||||
|
||||
Packages 下的代码必须通过 `.asmdef` 显式声明程序集。
|
||||
|
||||
**Runtime 层:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "FlowScope.XXX.Runtime",
|
||||
"rootNamespace": "FlowScope.XXX",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
```
|
||||
|
||||
**Editor 层:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "FlowScope.XXX.Editor",
|
||||
"rootNamespace": "FlowScope.XXX.Editor",
|
||||
"references": ["FlowScope.XXX.Runtime"],
|
||||
"includePlatforms": ["Editor"],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
```
|
||||
|
||||
## Git 分发与版本管理
|
||||
|
||||
### 推送到 Git 仓库
|
||||
|
||||
```powershell
|
||||
cd Packages/com.flowscope.xxx
|
||||
git init
|
||||
git add .
|
||||
git commit -m "初始化 FlowScope XXX Package"
|
||||
git remote add origin https://github.com/flowscope/xxx.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### 在其他项目中引用
|
||||
|
||||
修改目标项目的 `Packages/manifest.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"com.flowscope.xxx": "https://github.com/flowscope/xxx.git"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 版本引用方式
|
||||
|
||||
| 方式 | 写法 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| 最新 main | `"https://github.com/flowscope/xxx.git"` | 内部开发 |
|
||||
| 指定版本 | `"https://github.com/flowscope/xxx.git#v1.2.0"` | 正式项目 |
|
||||
| 指定分支 | `"https://github.com/flowscope/xxx.git#dev"` | 测试分支 |
|
||||
| 指定 commit | `"https://github.com/flowscope/xxx.git#abc1234"` | 精确锁定 |
|
||||
| 本地路径 | `"file:../../com.flowscope.xxx"` | 本地联调 |
|
||||
|
||||
### 版本发布流程
|
||||
|
||||
```powershell
|
||||
# 开发完成后打 tag
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
其他项目引用指定版本:
|
||||
|
||||
```json
|
||||
"com.flowscope.xxx": "https://github.com/flowscope/xxx.git#v1.0.0"
|
||||
```
|
||||
|
||||
### 更新已安装的 Package
|
||||
|
||||
- Unity 编辑器:Window → Package Manager → 选择对应包 → 点击 Update
|
||||
- 手动:删除 `Library/PackageCache/` 中对应缓存后重启 Unity
|
||||
|
||||
## .gitignore 注意事项
|
||||
|
||||
Package 仓库的 `.gitignore`:
|
||||
|
||||
```gitignore
|
||||
# Unity 生成的 meta 文件不要忽略(Package 需要)
|
||||
# 但 Library/ 下的内容不提交
|
||||
[Ll]ibrary/
|
||||
[Tt]emp/
|
||||
[Oo]bj/
|
||||
[Bb]uild/
|
||||
[Bb]uilds/
|
||||
[Ll]ogs/
|
||||
[Uu]ser[Ss]ettings/
|
||||
|
||||
# IDE
|
||||
.vs/
|
||||
.vscode/
|
||||
.idea/
|
||||
*.csproj
|
||||
*.unityproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.tmp
|
||||
*.user
|
||||
*.pidb
|
||||
*.booproj
|
||||
```
|
||||
|
||||
## FlowScope 模块化策略
|
||||
|
||||
根据 FlowScope Core Kernel 的架构原则,各模块独立为 Package:
|
||||
|
||||
```text
|
||||
FlowScope/
|
||||
├── Core/ → com.flowscope.core (纯 C#,无 Unity 依赖)
|
||||
├── UI/ → com.flowscope.ui (MVVM + uGUI 适配)
|
||||
├── Flow/ → com.flowscope.flow (时间线编排)
|
||||
├── Events/ → com.flowscope.events (事件流)
|
||||
├── Config/ → com.flowscope.config (配置加载隔离)
|
||||
├── Res/ → com.flowscope.res (资源生命周期)
|
||||
└── Modules/ → 各扩展模块
|
||||
```
|
||||
|
||||
每个模块为独立 Git 仓库,通过 `package.json` 的 `dependencies` 声明模块间依赖关系。
|
||||
95
docs/requirements/p0-audioservice.md
Normal file
95
docs/requirements/p0-audioservice.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# P0-9: IAudioService 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 音频系统提供基础 BGM/SFX 能力和轻量 `IAudioHandle`,但不做完整音频框架。
|
||||
|
||||
## 目标
|
||||
|
||||
提供基础音频播放能力,包括 BGM 播放/停止、SFX 播放、音量控制、静音、简单 SFX AudioSource 池和 BGM 淡入淡出。
|
||||
|
||||
## 接口
|
||||
|
||||
```csharp
|
||||
public interface IAudioService
|
||||
{
|
||||
IAudioHandle PlayBgm(string key, bool loop = true, float fadeIn = 0f);
|
||||
IAudioHandle PlaySfx(string key);
|
||||
void StopBgm(float fadeOut = 0f);
|
||||
void SetBgmVolume(float volume);
|
||||
void SetSfxVolume(float volume);
|
||||
void SetMute(bool mute);
|
||||
}
|
||||
|
||||
public interface IAudioHandle
|
||||
{
|
||||
void Stop(float fadeOut = 0f);
|
||||
bool IsPlaying { get; }
|
||||
}
|
||||
```
|
||||
|
||||
## 行为规则
|
||||
|
||||
- 同一时间只有一首 BGM。
|
||||
- 播放新 BGM 时停止旧 BGM。
|
||||
- `fadeIn` 和 `fadeOut` 使用线性插值。
|
||||
- SFX 可同时播放多个。
|
||||
- SFX 使用简单 AudioSource 池。
|
||||
- 音量参数 clamp 到 `[0, 1]`。
|
||||
- 最终音量 = 通道音量 * 静音系数。
|
||||
- 音频资源通过 `IResourceService` 加载。
|
||||
- 播放失败时抛异常并包含 key。
|
||||
|
||||
## SFX 池
|
||||
|
||||
```csharp
|
||||
public sealed class AudioServiceConfig
|
||||
{
|
||||
public int SfxPoolSize = 10;
|
||||
public bool ReuseOldestWhenExhausted = true;
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 池未满时创建或复用 AudioSource。
|
||||
- 池耗尽且允许复用时,复用最早播放完毕或最旧的 AudioSource。
|
||||
- 池耗尽且不允许复用时,记录警告并跳过本次 SFX。
|
||||
|
||||
## 暂不做
|
||||
|
||||
- 单个音频 Pause/Resume。
|
||||
- 单个音频 Volume。
|
||||
- AudioMixer 分组。
|
||||
- 3D 空间音频。
|
||||
- 动态音乐。
|
||||
- 语音/对话系统。
|
||||
- 音频配置表。
|
||||
- 编辑器音频检查工具。
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | BGM 播放 | PlayBgm 后 BGM 正常播放 |
|
||||
| 2 | BGM 切换 | 播放新 BGM 时旧 BGM 停止 |
|
||||
| 3 | BGM 淡入 | fadeIn > 0 时线性渐入 |
|
||||
| 4 | BGM 停止 | StopBgm 后 BGM 停止 |
|
||||
| 5 | BGM 淡出 | fadeOut > 0 时线性渐出后停止 |
|
||||
| 6 | SFX 播放 | PlaySfx 后音效播放 |
|
||||
| 7 | SFX 并发 | 多个 SFX 可同时播放 |
|
||||
| 8 | SFX 池 | 多次播放不无限创建 AudioSource |
|
||||
| 9 | Handle Stop | handle.Stop 后对应音频停止 |
|
||||
| 10 | IsPlaying | 播放中 true,停止后 false |
|
||||
| 11 | 音量 clamp | 超出范围时自动 clamp |
|
||||
| 12 | 静音 | SetMute(true) 后所有音频静音 |
|
||||
| 13 | 加载失败 | 不存在 key 抛异常并包含 key |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
IAudioService
|
||||
├── IResourceService
|
||||
├── Unity AudioSource / AudioClip
|
||||
└── 可选 MonoBehaviour Update/Coroutine 驱动 fade
|
||||
```
|
||||
99
docs/requirements/p0-configprovider.md
Normal file
99
docs/requirements/p0-configprovider.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# P0-5: IConfigProvider 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 配置系统只交付 JSON 强类型读取;CSV、Luban、ScriptableObject、远程配置、热重载和模块化加载全部放到 P1/P2 插件化扩展。
|
||||
|
||||
## 目标
|
||||
|
||||
提供最小可用的 JSON 配置加载和查询接口,支撑 P0 Sample 和休闲游戏基础配置读取。
|
||||
|
||||
## 接口
|
||||
|
||||
```csharp
|
||||
public interface IConfigProvider
|
||||
{
|
||||
Task LoadAllAsync(CancellationToken cancellationToken);
|
||||
T Get<T>(int id) where T : class, IConfigRow;
|
||||
IReadOnlyList<T> GetAll<T>() where T : class, IConfigRow;
|
||||
}
|
||||
|
||||
public interface IConfigRow
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
||||
```
|
||||
|
||||
## 功能需求
|
||||
|
||||
- 启动时通过 `LoadAllAsync` 加载全部 JSON 配置。
|
||||
- 配置行必须实现 `IConfigRow`。
|
||||
- 查询使用泛型类型 + `Id`。
|
||||
- `Get<T>(missingId)` 返回 null。
|
||||
- `GetAll<T>()` 返回只读列表;未加载该类型时返回空列表。
|
||||
- 同一配置类型内重复 Id 必须抛异常。
|
||||
- JSON 格式错误必须抛异常并包含文件名。
|
||||
|
||||
## 配置文件约定
|
||||
|
||||
P0 默认采用约定路径,具体路径可由实现固定,例如:
|
||||
|
||||
```text
|
||||
Assets/Configs/
|
||||
├── weapons.json
|
||||
├── levels.json
|
||||
└── settings.json
|
||||
```
|
||||
|
||||
每个 JSON 文件对应一种配置行类型。类型和文件的映射可以在 GameBootstrap 中显式注册,P0 不要求目录扫描和自动类型发现。
|
||||
|
||||
## 暂不做
|
||||
|
||||
- 字符串模块名查询。
|
||||
- 混合查询模式。
|
||||
- CSV。
|
||||
- Luban。
|
||||
- ScriptableObject 配置。
|
||||
- 按模块加载/卸载。
|
||||
- 远程配置。
|
||||
- 热重载。
|
||||
- 字段范围校验和引用完整性校验。
|
||||
- 配置编辑器工具。
|
||||
|
||||
## 扩展方向
|
||||
|
||||
P1/P2 通过以下接口扩展,不修改业务调用:
|
||||
|
||||
```csharp
|
||||
public interface IConfigParser
|
||||
{
|
||||
string Format { get; }
|
||||
IReadOnlyList<T> Parse<T>(string content) where T : class, IConfigRow;
|
||||
}
|
||||
|
||||
public interface IConfigSource
|
||||
{
|
||||
Task<string> LoadTextAsync(string key, CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | JSON 加载 | `LoadAllAsync` 后配置可查询 |
|
||||
| 2 | 按 Id 查询 | `Get<WeaponConfig>(101)` 返回正确对象 |
|
||||
| 3 | 不存在 Id | 返回 null,不抛异常 |
|
||||
| 4 | 获取列表 | `GetAll<WeaponConfig>()` 返回全部配置 |
|
||||
| 5 | 重复 Id | 加载时抛异常并包含重复 Id |
|
||||
| 6 | 格式错误 | 抛异常并包含文件名 |
|
||||
| 7 | 取消加载 | CancellationToken 取消时抛 `OperationCanceledException` |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
IConfigProvider
|
||||
├── JSON 库
|
||||
├── 可选 IResourceService 或文件读取适配
|
||||
└── 纯 C# 配置行类型
|
||||
```
|
||||
129
docs/requirements/p0-container.md
Normal file
129
docs/requirements/p0-container.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# P0-1: Container 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 允许 DI 自动注入进入首版,但必须保持 Container 核心轻量:核心只负责注册、解析、作用域和释放;Source Generator 与运行时反射只是生成工厂函数的适配层。
|
||||
|
||||
## 目标
|
||||
|
||||
提供 Game Core 的基础依赖容器,支持显式工厂注册、Attribute + Source Generator 自动注入、Attribute + 运行时反射降级、子作用域和可预测释放。
|
||||
|
||||
## P0 功能需求
|
||||
|
||||
### 1. 显式注册
|
||||
|
||||
```csharp
|
||||
public sealed class Container : IDisposable
|
||||
{
|
||||
public void RegisterInstance<T>(T instance);
|
||||
public void RegisterFactory<T>(Func<Container, T> factory);
|
||||
public T Resolve<T>();
|
||||
public bool TryResolve<T>(out T value);
|
||||
public Container CreateScope();
|
||||
public void Dispose();
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- `RegisterInstance<T>` 注册外部实例,Container 不负责释放。
|
||||
- `RegisterFactory<T>` 首次 Resolve 时创建实例,后续返回同一实例。
|
||||
- 工厂创建且实现 `IDisposable` 的实例由当前 scope 释放。
|
||||
- 子 scope 可访问父 scope 注册。
|
||||
- 子 scope 可覆盖父 scope 注册,且不污染父 scope。
|
||||
- 已 Dispose 的 scope 调用 Register/Resolve 必须抛 `ObjectDisposedException`。
|
||||
|
||||
### 2. Attribute 自动注入
|
||||
|
||||
```csharp
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class InjectableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
public sealed class Container : IDisposable
|
||||
{
|
||||
public void RegisterType<TInterface, TImplementation>()
|
||||
where TImplementation : TInterface;
|
||||
|
||||
public void RegisterType<TImplementation>();
|
||||
public void RegisterAssembly();
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 自动注入只支持构造函数注入。
|
||||
- 只允许一个 public 构造函数。
|
||||
- 构造函数参数从当前 Container 递归 Resolve。
|
||||
- 循环依赖必须检测,并输出完整依赖链。
|
||||
- `[Inject]` 属性注入和方法注入不进入 P0。
|
||||
|
||||
### 3. Source Generator 推荐路径
|
||||
|
||||
Source Generator 是 P0 推荐自动注入路径。
|
||||
|
||||
要求:
|
||||
|
||||
- 编译期扫描 `[Injectable]` 类型。
|
||||
- 为每个 Injectable 类型生成等价于手写工厂的静态工厂方法。
|
||||
- 编译期检查构造函数依赖是否可解析,无法确认时发出诊断。
|
||||
- Generator 程序集与 Runtime 程序集分离。
|
||||
- 未启用 Source Generator 时,显式工厂模式仍可独立使用。
|
||||
|
||||
### 4. 运行时反射降级路径
|
||||
|
||||
运行时反射是 P0 可选降级路径。
|
||||
|
||||
要求:
|
||||
|
||||
- 仅在启用反射模式时使用。
|
||||
- 反射只在首次 Resolve 时构造并缓存结果。
|
||||
- 行为必须与 Source Generator 模式一致。
|
||||
- IL2CPP/AOT 风险需要在文档中明确提示。
|
||||
|
||||
### 5. 释放规则
|
||||
|
||||
- 当前 scope Dispose 时按创建逆序释放本 scope 管理的 `IDisposable`。
|
||||
- 父 scope Dispose 时,先释放未释放的子 scope。
|
||||
- `Dispose()` 可重复调用,后续调用静默忽略。
|
||||
- 某个实例 Dispose 抛异常时,记录错误并继续释放其余实例。
|
||||
|
||||
## 暂不做
|
||||
|
||||
- `[Inject]` 属性注入。
|
||||
- `[Inject]` 方法注入。
|
||||
- Transient / Scoped / Singleton 多生命周期模式。
|
||||
- 开放泛型注册。
|
||||
- 装饰器注册。
|
||||
- 条件注册。
|
||||
- 运行时动态切换 DI 模式。
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | 显式实例注册 | `RegisterInstance` 后 `Resolve` 返回同一实例 |
|
||||
| 2 | 显式工厂注册 | `RegisterFactory` 首次 Resolve 创建实例,后续复用 |
|
||||
| 3 | Source Generator 自动注入 | `[Injectable]` 类型可通过 `RegisterType` 正常解析 |
|
||||
| 4 | 运行时反射自动注入 | 反射模式下行为与 Source Generator 一致 |
|
||||
| 5 | 递归解析 | A 依赖 B、B 依赖 C 时 Resolve A 可自动解析整条链 |
|
||||
| 6 | 循环依赖检测 | A -> B -> A 抛异常,错误包含依赖链 |
|
||||
| 7 | 子 scope 读取父级 | 子 scope 可 Resolve 父 scope 注册 |
|
||||
| 8 | 子 scope 覆盖父级 | 子 scope 覆盖后父 scope 不受影响 |
|
||||
| 9 | 逆序释放 | 多个工厂实例按创建逆序 Dispose |
|
||||
| 10 | 外部实例不释放 | `RegisterInstance` 传入的实例不由 Container Dispose |
|
||||
| 11 | 重复 Dispose | 第二次 Dispose 不重复释放、不抛异常 |
|
||||
| 12 | 已释放 scope 防护 | 已 Dispose scope 调用 Register/Resolve 抛 `ObjectDisposedException` |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
Container Runtime
|
||||
├── 纯 C#,不依赖 Unity API
|
||||
├── 不依赖 R3
|
||||
├── Source Generator 适配层依赖 Roslyn
|
||||
└── Reflection 适配层依赖 System.Reflection
|
||||
```
|
||||
88
docs/requirements/p0-data-r3.md
Normal file
88
docs/requirements/p0-data-r3.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# P0-4: R3 + Data 类规范需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 使用 R3 作为响应式基础,不自研响应式系统,不做 UI 自动绑定。
|
||||
|
||||
## 目标
|
||||
|
||||
建立游戏运行时 Data 的组织方式、响应式状态表达、ViewModel 访问规则和存档序列化约定。
|
||||
|
||||
## Data 类规范
|
||||
|
||||
```csharp
|
||||
public sealed class PlayerData
|
||||
{
|
||||
public ReactiveProperty<int> Gold { get; } = new(0);
|
||||
public ReactiveProperty<int> Level { get; } = new(1);
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 一个 Data 类对应一个业务域。
|
||||
- Data 类注册在全局 Container 中。
|
||||
- Data 类保持纯 C#,不依赖 Unity API。
|
||||
- Data 类不持有 View 或 ViewModel 引用。
|
||||
- Data 类不包含复杂业务流程逻辑。
|
||||
- Data 类之间不互相直接引用。
|
||||
- Data 类使用 `ReactiveProperty<T>` 和 `ReactiveCollection<T>` 表达可观察状态。
|
||||
|
||||
## ViewModel 规则
|
||||
|
||||
- ViewModel 可以引用 Data。
|
||||
- ViewModel 负责 UI 表现逻辑和用户操作逻辑。
|
||||
- ViewModel 不持有 MonoBehaviour 或具体 View 引用。
|
||||
- ViewModel 订阅必须加入自己的 `CompositeDisposable`,或加入 `FeatureContext.Disposables`。
|
||||
|
||||
## 序列化规则
|
||||
|
||||
- `ReactiveProperty<T>` 序列化为 `.Value`。
|
||||
- `ReactiveCollection<T>` 序列化为 JSON 数组。
|
||||
- 反序列化到已有 Data 实例时,更新 `.Value` 或集合内容,不替换 Data 实例。
|
||||
- 反序列化类型不匹配时抛异常并包含字段名。
|
||||
|
||||
## 事件流规则
|
||||
|
||||
P0 允许使用 R3 `Subject<T>` 做 Feature 内或明确归属的轻量事件流。
|
||||
|
||||
规则:
|
||||
|
||||
- Subject 生命周期必须有明确所有者。
|
||||
- Feature 内 Subject 随 Feature Dispose 释放。
|
||||
- 跨 Feature 持久状态优先使用 Data。
|
||||
- 不建立全局 Subject 池。
|
||||
- 不实现独立 EventBus。
|
||||
|
||||
## 暂不做
|
||||
|
||||
- 自研响应式系统。
|
||||
- UI 自动绑定框架。
|
||||
- 全局 EventBus。
|
||||
- Data 类 Inspector 可视化。
|
||||
- 订阅泄漏 Analyzer。
|
||||
- Data 类之间的复杂关系建模。
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | Data 创建 | Data 类用 ReactiveProperty 定义字段 |
|
||||
| 2 | 全局共享 | 两个 ViewModel Resolve 到同一个 Data 实例 |
|
||||
| 3 | 响应式同步 | 修改 Data 后订阅者收到通知 |
|
||||
| 4 | ViewModel 隔离 | ViewModel 不引用 MonoBehaviour 或具体 View |
|
||||
| 5 | 序列化 ReactiveProperty | JSON 只包含值,不包含内部状态 |
|
||||
| 6 | 反序列化已有实例 | 已注册 Data 实例被更新而不是替换 |
|
||||
| 7 | ReactiveCollection | 可序列化为数组并恢复 |
|
||||
| 8 | 订阅释放 | Dispose 后订阅不再触发 |
|
||||
| 9 | 纯 C# | Data 类可脱离 Unity 编译 |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
Data / ViewModel
|
||||
├── R3
|
||||
├── Container
|
||||
├── ISaveService
|
||||
└── JSON Converter
|
||||
```
|
||||
128
docs/requirements/p0-feature.md
Normal file
128
docs/requirements/p0-feature.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# P0-3: Feature 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。Feature 不再自己创建根 scope;GameFlow 创建 FeatureContext 并传入 Feature。Feature 负责自身业务行为和引用清理。
|
||||
|
||||
## 目标
|
||||
|
||||
定义业务 Feature 的统一生命周期,让每个玩法、页面流或业务入口可以独立加载、进入、退出和释放。
|
||||
|
||||
## FeatureContext
|
||||
|
||||
```csharp
|
||||
public sealed class FeatureContext
|
||||
{
|
||||
public Container Scope { get; }
|
||||
public IResourceGroup Resources { get; }
|
||||
public CompositeDisposable Disposables { get; }
|
||||
public CancellationToken CancellationToken { get; }
|
||||
}
|
||||
```
|
||||
|
||||
所有权:
|
||||
|
||||
- `Scope` 由 GameFlow 创建和释放。
|
||||
- `Resources` 由 GameFlow 创建和释放。
|
||||
- `Disposables` 由 GameFlow 创建,Feature 可加入订阅,Exit/Dispose 时清理。
|
||||
- Feature 可以在 `Scope` 内注册自己的临时服务。
|
||||
|
||||
## 生命周期接口
|
||||
|
||||
```csharp
|
||||
public interface IFeature : IDisposable
|
||||
{
|
||||
Task LoadAsync(FeatureContext context);
|
||||
Task EnterAsync(FeatureContext context);
|
||||
Task ExitAsync(FeatureContext context);
|
||||
}
|
||||
```
|
||||
|
||||
阶段职责:
|
||||
|
||||
| 方法 | 职责 |
|
||||
|------|------|
|
||||
| `LoadAsync` | 加载资源、创建 ViewModel、准备运行时依赖 |
|
||||
| `EnterAsync` | 打开 UI、订阅 Data 或事件流 |
|
||||
| `ExitAsync` | 关闭 UI、取消业务订阅 |
|
||||
| `Dispose` | 清空自身引用,释放 Feature 自己创建但未交给 context 管理的对象 |
|
||||
|
||||
## 数据传递
|
||||
|
||||
规则:
|
||||
|
||||
- 跨 Feature 持久状态放在 Data 类中,并注册到全局 Container。
|
||||
- 一次性过渡参数由目标 Feature 定义。
|
||||
- Feature 不引用其他 Feature 的内部类。
|
||||
- Feature 间直接通信不进入 P0,使用 Data 或 R3 事件流。
|
||||
|
||||
## 资源与订阅
|
||||
|
||||
规则:
|
||||
|
||||
- Feature 加载资源后必须加入 `context.Resources`,或持有明确的 handle 并在 Dispose 中释放。
|
||||
- Feature 订阅必须加入 `context.Disposables` 或 ViewModel 自己的 `CompositeDisposable`。
|
||||
- `ExitAsync` 负责停止业务订阅和关闭 UI。
|
||||
- `Dispose` 必须可重复调用。
|
||||
|
||||
## FeatureBase 可选基类
|
||||
|
||||
```csharp
|
||||
public abstract class FeatureBase : IFeature
|
||||
{
|
||||
protected FeatureContext Context { get; private set; }
|
||||
|
||||
public virtual Task LoadAsync(FeatureContext context)
|
||||
{
|
||||
Context = context;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task EnterAsync(FeatureContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task ExitAsync(FeatureContext context)
|
||||
{
|
||||
context.Disposables.Clear();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Context = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 暂不做
|
||||
|
||||
- Feature 栈。
|
||||
- 并行 Feature。
|
||||
- Feature 热重载。
|
||||
- Feature 预加载。
|
||||
- Feature 过渡动画。
|
||||
- Feature 间直接调用。
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | Load/Enter/Exit/Dispose 顺序 | GameFlow 严格按顺序调用 |
|
||||
| 2 | 使用 FeatureContext | Feature 可从 context Resolve 服务、加入资源和订阅 |
|
||||
| 3 | 资源释放 | Feature 退出后 context.Resources 已释放 |
|
||||
| 4 | 订阅释放 | Feature 退出后订阅不再触发 |
|
||||
| 5 | Dispose 幂等 | 重复 Dispose 不抛异常 |
|
||||
| 6 | Load 失败清理 | LoadAsync 中途失败后 Dispose 可清理部分状态 |
|
||||
| 7 | Feature 隔离 | Feature 不引用其他 Feature 的内部类 |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
IFeature
|
||||
├── FeatureContext
|
||||
├── Container
|
||||
├── IResourceGroup
|
||||
└── R3 CompositeDisposable
|
||||
```
|
||||
156
docs/requirements/p0-gameflow.md
Normal file
156
docs/requirements/p0-gameflow.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# P0-2: GameFlow 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。GameFlow 是全局生命周期所有者,负责创建 Feature scope、FeatureContext,并编排 Feature 的进入、切换和关闭。
|
||||
|
||||
## 目标
|
||||
|
||||
提供游戏启动、Feature 切换和关闭流程的编排器,保证生命周期顺序、失败清理、取消和非法状态防护。
|
||||
|
||||
## 状态机
|
||||
|
||||
```csharp
|
||||
public enum GameFlowState
|
||||
{
|
||||
Idle,
|
||||
Starting,
|
||||
Running,
|
||||
Switching,
|
||||
NoActiveFeature,
|
||||
ShuttingDown,
|
||||
Disposed
|
||||
}
|
||||
```
|
||||
|
||||
状态转换:
|
||||
|
||||
```text
|
||||
Idle -> Starting -> Running <-> Switching
|
||||
\-> NoActiveFeature
|
||||
Running -> ShuttingDown -> Disposed
|
||||
NoActiveFeature -> Switching -> Running
|
||||
NoActiveFeature -> ShuttingDown -> Disposed
|
||||
```
|
||||
|
||||
非法状态调用抛 `InvalidOperationException`,错误必须包含当前状态和操作名。
|
||||
|
||||
## 接口
|
||||
|
||||
```csharp
|
||||
public sealed class GameFlow
|
||||
{
|
||||
public GameFlowState State { get; }
|
||||
|
||||
public Task StartupAsync<TInitialFeature>(
|
||||
Container root,
|
||||
CancellationToken cancellationToken)
|
||||
where TInitialFeature : IFeature;
|
||||
|
||||
public Task SwitchToAsync<TFeature>(
|
||||
CancellationToken cancellationToken)
|
||||
where TFeature : IFeature;
|
||||
|
||||
public Task ShutdownAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
## 启动流程
|
||||
|
||||
```text
|
||||
StartupAsync:
|
||||
1. State = Starting
|
||||
2. Resolve IConfigProvider
|
||||
3. await config.LoadAllAsync(ct)
|
||||
4. Resolve ISaveService
|
||||
5. 加载或创建 P0 Sample 所需 Data
|
||||
6. 注册 Data 到 root Container
|
||||
7. 创建 TInitialFeature
|
||||
8. 创建 Feature scope
|
||||
9. 创建 FeatureContext
|
||||
10. await feature.LoadAsync(context)
|
||||
11. await feature.EnterAsync(context)
|
||||
12. State = Running
|
||||
```
|
||||
|
||||
失败规则:
|
||||
|
||||
- 配置加载失败:清理已创建对象,State 回到 Idle,抛异常。
|
||||
- 存档加载失败:使用默认 Data,记录警告,继续启动。
|
||||
- Feature Load/Enter 失败:调用 Dispose 清理,State 回到 Idle,抛异常。
|
||||
|
||||
## Feature 切换流程
|
||||
|
||||
```text
|
||||
SwitchToAsync:
|
||||
1. State = Switching
|
||||
2. await current.ExitAsync(currentContext)
|
||||
3. current.Dispose()
|
||||
4. Dispose current FeatureContext / scope / resources
|
||||
5. 创建新 Feature、scope、FeatureContext
|
||||
6. await next.LoadAsync(nextContext)
|
||||
7. await next.EnterAsync(nextContext)
|
||||
8. State = Running
|
||||
```
|
||||
|
||||
失败规则:
|
||||
|
||||
- 旧 Feature `ExitAsync` 失败:记录错误,继续 Dispose。
|
||||
- 旧 Feature `Dispose` 失败:记录错误,继续创建新 Feature。
|
||||
- 新 Feature `LoadAsync` 失败:清理新 Feature,State = NoActiveFeature,抛异常。
|
||||
- 新 Feature `EnterAsync` 失败:调用新 Feature Dispose,State = NoActiveFeature,抛异常。
|
||||
- 切换期间再次调用 `SwitchToAsync`:P0 抛 `InvalidOperationException`,不排队。
|
||||
|
||||
## 关闭流程
|
||||
|
||||
```text
|
||||
ShutdownAsync:
|
||||
1. State = ShuttingDown
|
||||
2. best-effort 调用当前 Feature ExitAsync
|
||||
3. best-effort 调用当前 Feature Dispose
|
||||
4. best-effort 保存必要 Data
|
||||
5. Dispose root Container
|
||||
6. State = Disposed
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- `ShutdownAsync` 一旦进入关闭流程,必须尽量执行到底。
|
||||
- 保存失败记录错误,不阻塞退出。
|
||||
- Feature 退出/释放失败记录错误,不阻塞退出。
|
||||
- Container 释放失败记录错误,不阻塞退出。
|
||||
|
||||
## 暂不做
|
||||
|
||||
- Feature 栈。
|
||||
- 并行 Feature。
|
||||
- Feature 预加载。
|
||||
- 可配置启动任务列表。
|
||||
- 切换请求队列。
|
||||
- Starting/Switching 中断式 Shutdown。
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | 正常启动 | Startup 完成后 State == Running,初始 Feature 已 Load + Enter |
|
||||
| 2 | 启动配置失败 | State 回到 Idle,异常向上传播 |
|
||||
| 3 | 启动存档失败 | 使用默认 Data,记录警告,继续启动 |
|
||||
| 4 | 正常切换 | 旧 Feature 已 Exit/Dispose,新 Feature 已 Load/Enter |
|
||||
| 5 | 新 Feature Load 失败 | State == NoActiveFeature,已清理新 Feature |
|
||||
| 6 | 新 Feature Enter 失败 | State == NoActiveFeature,已 Dispose 新 Feature |
|
||||
| 7 | 关闭 best-effort | 保存或释放失败时仍进入 Disposed |
|
||||
| 8 | 非法状态防护 | 非法调用抛异常并包含当前状态 |
|
||||
| 9 | CancellationToken 传递 | FeatureContext 中可拿到同一个取消信号 |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
GameFlow
|
||||
├── Container
|
||||
├── IFeature
|
||||
├── FeatureContext
|
||||
├── IConfigProvider
|
||||
├── ISaveService
|
||||
└── IResourceService
|
||||
```
|
||||
698
docs/requirements/p0-requirements-set.md
Normal file
698
docs/requirements/p0-requirements-set.md
Normal file
@@ -0,0 +1,698 @@
|
||||
# FlowScope Game Core P0 需求集
|
||||
|
||||
## 文档目的
|
||||
|
||||
本文档定义 FlowScope Game Core 的 P0 范围。P0 的目标不是做一个覆盖所有未来项目的完整框架,而是交付一套能支撑真实休闲/超休闲 Unity 游戏最小纵向切片的 Core:
|
||||
|
||||
1. Unity 启动后进入 GameBootstrap。
|
||||
2. 注册核心服务。
|
||||
3. 加载 JSON 配置。
|
||||
4. 加载或创建本地存档数据。
|
||||
5. 进入初始 Feature。
|
||||
6. 打开一个 MVVM Panel。
|
||||
7. ViewModel/Data 通过 R3 驱动 UI 刷新。
|
||||
8. 切换或关闭 Feature 时释放资源、订阅和作用域。
|
||||
9. 退出时保存必要数据。
|
||||
|
||||
P0 允许包含部分开发体验能力,但这些能力必须服务于最小闭环,不能把 P0 扩张成完整包生态。
|
||||
|
||||
---
|
||||
|
||||
## P0 范围原则
|
||||
|
||||
1. 显式接口优先,业务模型不由 Core 暗猜。
|
||||
2. Core 不包含具体玩法规则、具体 UI 文案、具体数值或业务 Data 类。
|
||||
3. 默认实现只保留一条主路径,扩展实现通过接口或独立包接入。
|
||||
4. 生命周期、资源、订阅、作用域必须有明确所有者。
|
||||
5. 所有异步流程必须定义失败、取消和重复调用行为。
|
||||
6. P0 文档中的每个模块都必须能通过单元测试或最小 Sample 验收。
|
||||
|
||||
---
|
||||
|
||||
## 统一架构决策
|
||||
|
||||
### 1. DI 自动注入进入 P0
|
||||
|
||||
P0 支持三种 DI 注册方式:
|
||||
|
||||
| 模式 | P0 定位 | 说明 |
|
||||
|------|---------|------|
|
||||
| 显式工厂注册 | 必须支持 | 最小稳定路径,所有项目都可用 |
|
||||
| Attribute + Source Generator | P0 推荐路径 | 提供自动构造函数注入、RegisterAssembly、编译期检查 |
|
||||
| Attribute + 运行时反射 | P0 可选降级路径 | 用于开发期、Generator 不可用场景、快速验证 |
|
||||
|
||||
约束:
|
||||
|
||||
- Container 的核心运行时只关心工厂函数,不直接依赖 Source Generator 或反射实现。
|
||||
- Source Generator 和 Reflection 必须放在独立程序集或独立适配层中。
|
||||
- 没有开启自动注入模式时,`RegisterType` / `RegisterAssembly` 必须给出明确错误,引导使用 `RegisterFactory`。
|
||||
- 自动递归解析依赖链必须检测循环依赖,并输出完整依赖路径。
|
||||
- Source Generator 的编译期检查作为 P0 推荐能力,但不能阻塞显式工厂模式使用。
|
||||
|
||||
### 2. UI 层级改为可配置
|
||||
|
||||
P0 不再强制固定 `Bottom / Middle / Top` 三层。UIManager 支持运行时注册层级:
|
||||
|
||||
```csharp
|
||||
RegisterLayer(string name, Canvas canvas, int sortOrder, PanelStrategy strategy);
|
||||
```
|
||||
|
||||
P0 仍需提供一个推荐默认预设,方便最小项目快速接入:
|
||||
|
||||
```text
|
||||
background / hud / popup / dialog / system
|
||||
```
|
||||
|
||||
项目可以只注册其中一部分,也可以自定义层级名。
|
||||
|
||||
### 3. UI 栈式面板进入 P0,但只做层内 LIFO
|
||||
|
||||
P0 支持同一层级内的栈式管理:
|
||||
|
||||
- 后打开的 Panel 位于同层级顶部。
|
||||
- 默认只能关闭栈顶 Panel。
|
||||
- `CloseLayer(layerName)` 关闭指定层。
|
||||
- `CloseAll()` 关闭全部层级。
|
||||
|
||||
P0 暂不做完整导航路由、跨 Feature 页面恢复、URL/路由式导航、复杂返回策略。这些放到 P1/P2。
|
||||
|
||||
### 4. 配置系统 P0 只支持 JSON
|
||||
|
||||
P0 只交付 JSON 配置读取:
|
||||
|
||||
- `LoadAllAsync()`
|
||||
- `Get<T>(int id)`
|
||||
- `GetAll<T>()`
|
||||
- 配置行实现 `IConfigRow`
|
||||
|
||||
CSV、Luban、ScriptableObject、远程配置、热重载、字段校验工具不进入 P0。后续通过 `IConfigSource` / `IConfigParser` 扩展包接入,而不是塞进核心实现。
|
||||
|
||||
### 5. 存档系统 P0 只保留本地轻量实现
|
||||
|
||||
P0 提供:
|
||||
|
||||
- `SaveAsync<T>(string key, T data)`
|
||||
- `LoadAsync<T>(string key, T defaultValue)`
|
||||
- `Delete(string key)`
|
||||
|
||||
默认实现二选一:
|
||||
|
||||
- `FileSaveStorage`
|
||||
- `PlayerPrefsSaveStorage`
|
||||
|
||||
`Exists` 可作为轻量辅助进入 P0;`ListKeys`、云存档、加密、压缩、版本迁移、冲突解决不进入 P0。后续通过 `ISaveStorage` / `ISaveSerializer` 扩展。
|
||||
|
||||
### 6. 音频系统 P0 保留基础播放,不做完整音频框架
|
||||
|
||||
P0 提供:
|
||||
|
||||
- 播放/停止 BGM
|
||||
- 播放 SFX
|
||||
- 设置 BGM/SFX 音量
|
||||
- 全局静音
|
||||
- 简单 SFX AudioSource 池
|
||||
- BGM 淡入淡出
|
||||
|
||||
`IAudioHandle` 可进入 P0,但只作为播放结果的轻量控制句柄:
|
||||
|
||||
- `Stop(float fadeOut = 0)`
|
||||
- `IsPlaying`
|
||||
|
||||
单个音频暂停/恢复、单个音量、AudioMixer 路由、3D 音频、语音系统、动态音乐、音频配置表放到 P1/P2。
|
||||
|
||||
### 7. 资源系统 P0 支持引用计数,但只支持一个默认后端
|
||||
|
||||
P0 支持:
|
||||
|
||||
- `LoadAsync<T>(string key)`
|
||||
- `IResourceHandle<T>`
|
||||
- `IResourceGroup`
|
||||
- 同 key 并发加载合并
|
||||
- 引用计数
|
||||
- Group 释放
|
||||
|
||||
P0 默认后端只支持 Addressables。
|
||||
|
||||
Resources / AssetBundle / YooAsset / 远程下载进度 / 资源分析工具不进入 P0。后续通过 `IResourceBackend` 插件化接入。
|
||||
|
||||
---
|
||||
|
||||
## 统一生命周期所有权
|
||||
|
||||
### GameFlow / AppFlow 负责
|
||||
|
||||
GameFlow 是全局生命周期编排者,负责:
|
||||
|
||||
- 创建 Feature 实例。
|
||||
- 创建 Feature Scope。
|
||||
- 创建 FeatureContext。
|
||||
- 调用 Feature 生命周期。
|
||||
- 在切换和关闭时保证释放顺序。
|
||||
- 处理失败、取消和重复调用。
|
||||
|
||||
### Feature 负责
|
||||
|
||||
Feature 只负责自身业务行为:
|
||||
|
||||
- 在 `LoadAsync` 中加载自身资源、创建 ViewModel、准备运行时依赖。
|
||||
- 在 `EnterAsync` 中打开 UI、订阅数据或事件流。
|
||||
- 在 `ExitAsync` 中关闭 UI、取消业务订阅。
|
||||
- 在 `Dispose` 中清空自身引用。
|
||||
|
||||
Feature 不直接创建根 scope。Feature 可以使用 GameFlow 提供的 feature scope 注册临时服务。
|
||||
|
||||
### FeatureContext
|
||||
|
||||
```csharp
|
||||
public sealed class FeatureContext
|
||||
{
|
||||
public Container Scope { get; }
|
||||
public IResourceGroup Resources { get; }
|
||||
public CompositeDisposable Disposables { get; }
|
||||
public CancellationToken CancellationToken { get; }
|
||||
}
|
||||
```
|
||||
|
||||
Feature 生命周期接口:
|
||||
|
||||
```csharp
|
||||
public interface IFeature : IDisposable
|
||||
{
|
||||
Task LoadAsync(FeatureContext context);
|
||||
Task EnterAsync(FeatureContext context);
|
||||
Task ExitAsync(FeatureContext context);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 统一异步策略
|
||||
|
||||
P0 接口统一使用 `Task` 和 `CancellationToken`。
|
||||
|
||||
规则:
|
||||
|
||||
- `StartupAsync`、`SwitchToAsync`、`ShutdownAsync` 必须接受 `CancellationToken`。
|
||||
- Feature 生命周期通过 `FeatureContext.CancellationToken` 获取取消信号。
|
||||
- 切换期间再次调用 `SwitchToAsync` 抛 `InvalidOperationException`,P0 不做排队。
|
||||
- `ShutdownAsync` 在 `Starting` 或 `Switching` 中调用时,P0 抛 `InvalidOperationException`,P1 可考虑中断式关闭。
|
||||
- `ShutdownAsync` 一旦进入关闭流程,必须 best-effort 执行到底。
|
||||
|
||||
失败处理:
|
||||
|
||||
| 场景 | P0 行为 |
|
||||
|------|---------|
|
||||
| Startup 配置加载失败 | 回到 Idle,抛异常 |
|
||||
| Startup 存档加载失败 | 使用默认数据并记录警告 |
|
||||
| 初始 Feature Load 失败 | 清理已创建资源,回到 Idle,抛异常 |
|
||||
| Switch 旧 Feature Exit 失败 | 记录错误,继续 Dispose |
|
||||
| Switch 旧 Feature Dispose 失败 | 记录错误,继续创建新 Feature |
|
||||
| Switch 新 Feature Load 失败 | 清理新 Feature,进入 NoActiveFeature 状态,抛异常 |
|
||||
| Switch 新 Feature Enter 失败 | 调用新 Feature Dispose,进入 NoActiveFeature 状态,抛异常 |
|
||||
| Shutdown 保存失败 | 记录错误,不阻塞退出 |
|
||||
| 取消发生 | 抛 `OperationCanceledException`,并清理已创建资源 |
|
||||
|
||||
---
|
||||
|
||||
## P0 模块需求
|
||||
|
||||
### P0-1 Container
|
||||
|
||||
目标:提供轻量 DI 容器,支持显式注册、自动注入、作用域和释放。
|
||||
|
||||
P0 必须支持:
|
||||
|
||||
- `RegisterInstance<T>(T instance)`
|
||||
- `RegisterFactory<T>(Func<Container, T> factory)`
|
||||
- `RegisterType<TInterface, TImplementation>()`
|
||||
- `RegisterType<TImplementation>()`
|
||||
- `RegisterAssembly()`
|
||||
- `Resolve<T>()`
|
||||
- `TryResolve<T>(out T value)`
|
||||
- `CreateScope()`
|
||||
- `Dispose()`
|
||||
|
||||
自动注入规则:
|
||||
|
||||
- 只允许一个 public 构造函数。
|
||||
- 构造函数参数从 Container 递归解析。
|
||||
- 循环依赖必须检测。
|
||||
- Source Generator 模式提供编译期依赖检查。
|
||||
- Reflection 模式提供运行时降级。
|
||||
|
||||
暂不做:
|
||||
|
||||
- `[Inject]` 属性/方法注入。
|
||||
- 多生命周期模式。
|
||||
- 开放泛型注册。
|
||||
- 装饰器注册。
|
||||
- 运行时动态切换 DI 模式。
|
||||
|
||||
验收:
|
||||
|
||||
- 显式工厂注册可解析。
|
||||
- Source Generator 自动注入可解析。
|
||||
- Reflection 自动注入可解析。
|
||||
- 子 scope 可访问父 scope。
|
||||
- 子 scope 覆盖父 scope 不污染父 scope。
|
||||
- Dispose 按逆序释放本 scope 创建的 `IDisposable`。
|
||||
- 循环依赖抛出包含依赖链的异常。
|
||||
|
||||
### P0-2 GameFlow
|
||||
|
||||
目标:编排游戏启动、Feature 切换和关闭。
|
||||
|
||||
状态:
|
||||
|
||||
```text
|
||||
Idle -> Starting -> Running <-> Switching -> ShuttingDown -> Disposed
|
||||
```
|
||||
|
||||
接口:
|
||||
|
||||
```csharp
|
||||
Task StartupAsync<TInitialFeature>(Container root, CancellationToken ct);
|
||||
Task SwitchToAsync<TFeature>(CancellationToken ct);
|
||||
Task ShutdownAsync(CancellationToken ct);
|
||||
```
|
||||
|
||||
职责:
|
||||
|
||||
- 启动时加载配置、加载存档、注册 Data、进入初始 Feature。
|
||||
- 切换时按 `Exit -> Dispose -> CreateScope -> Load -> Enter` 执行。
|
||||
- 关闭时按 `Exit -> Dispose -> Save -> DisposeRootContainer` 执行。
|
||||
- 创建并传递 `FeatureContext`。
|
||||
- 防止非法状态调用。
|
||||
|
||||
暂不做:
|
||||
|
||||
- Feature 栈。
|
||||
- 并行 Feature。
|
||||
- 可配置启动任务列表。
|
||||
- Feature 预加载。
|
||||
|
||||
### P0-3 Feature
|
||||
|
||||
目标:定义业务特性的统一生命周期。
|
||||
|
||||
接口:
|
||||
|
||||
```csharp
|
||||
public interface IFeature : IDisposable
|
||||
{
|
||||
Task LoadAsync(FeatureContext context);
|
||||
Task EnterAsync(FeatureContext context);
|
||||
Task ExitAsync(FeatureContext context);
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- Feature 不依赖其他 Feature 的内部状态。
|
||||
- 持久跨 Feature 状态放入 Data 类。
|
||||
- 一次性过渡参数由目标 Feature 定义。
|
||||
- 订阅必须进入 `FeatureContext.Disposables` 或 ViewModel 自己的 disposable 集合。
|
||||
- 资源必须进入 `FeatureContext.Resources` 或由明确 handle 管理。
|
||||
|
||||
暂不做:
|
||||
|
||||
- Feature 间直接通信。
|
||||
- Feature 热重载。
|
||||
- Feature 过渡动画。
|
||||
- 多 Feature 同时运行。
|
||||
|
||||
### P0-4 R3 + Data
|
||||
|
||||
目标:建立运行时数据和响应式绑定基础。
|
||||
|
||||
规则:
|
||||
|
||||
- 一个 Data 类对应一个业务域。
|
||||
- Data 类是纯 C#,不依赖 Unity API。
|
||||
- Data 类不引用 View / ViewModel。
|
||||
- Data 类使用 `ReactiveProperty<T>` 和 `ReactiveCollection<T>` 表达可观察状态。
|
||||
- ViewModel 负责业务表现逻辑,可以修改 Data。
|
||||
- ViewModel 订阅必须释放。
|
||||
|
||||
P0 支持:
|
||||
|
||||
- `ReactiveProperty<T>` 序列化为 `.Value`。
|
||||
- `ReactiveCollection<T>` 序列化为数组。
|
||||
- 存档加载后恢复到 Data 实例。
|
||||
|
||||
暂不做:
|
||||
|
||||
- 自研响应式系统。
|
||||
- UI 自动绑定。
|
||||
- 全局 EventBus。
|
||||
- Data Inspector 可视化。
|
||||
|
||||
### P0-5 ConfigProvider
|
||||
|
||||
目标:提供 JSON 配置读取。
|
||||
|
||||
接口:
|
||||
|
||||
```csharp
|
||||
public interface IConfigProvider
|
||||
{
|
||||
Task LoadAllAsync(CancellationToken ct);
|
||||
T Get<T>(int id) where T : class, IConfigRow;
|
||||
IReadOnlyList<T> GetAll<T>() where T : class, IConfigRow;
|
||||
}
|
||||
|
||||
public interface IConfigRow
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- P0 默认 JSON。
|
||||
- 查询不存在 ID 返回 null。
|
||||
- 重复 ID 抛异常。
|
||||
- 配置格式错误抛异常并包含文件名。
|
||||
|
||||
暂不做:
|
||||
|
||||
- CSV / Luban / ScriptableObject。
|
||||
- 按模块加载和卸载。
|
||||
- 热重载。
|
||||
- 配置编辑器校验。
|
||||
- 远程配置。
|
||||
|
||||
扩展方向:
|
||||
|
||||
- P1/P2 通过 `IConfigParser` 和 `IConfigSource` 插件化接入,不修改业务调用。
|
||||
|
||||
### P0-6 SaveService
|
||||
|
||||
目标:提供本地轻量存档。
|
||||
|
||||
接口:
|
||||
|
||||
```csharp
|
||||
public interface ISaveService
|
||||
{
|
||||
Task SaveAsync<T>(string key, T data, CancellationToken ct);
|
||||
Task<T> LoadAsync<T>(string key, T defaultValue, CancellationToken ct);
|
||||
void Delete(string key);
|
||||
bool Exists(string key);
|
||||
}
|
||||
```
|
||||
|
||||
默认实现:
|
||||
|
||||
- `FileSaveStorage` 或 `PlayerPrefsSaveStorage` 二选一。
|
||||
- 默认序列化为 JSON。
|
||||
|
||||
规则:
|
||||
|
||||
- 不存在 key 返回 defaultValue。
|
||||
- 反序列化失败返回 defaultValue 并记录警告。
|
||||
- 写入失败抛异常。
|
||||
- 删除不存在 key 静默忽略。
|
||||
|
||||
暂不做:
|
||||
|
||||
- `ListKeys`。
|
||||
- 多存档槽管理。
|
||||
- 云存档。
|
||||
- 加密。
|
||||
- 压缩。
|
||||
- 版本迁移。
|
||||
- 自动存档。
|
||||
|
||||
扩展方向:
|
||||
|
||||
- P1/P2 通过 `ISaveStorage` / `ISaveSerializer` 扩展。
|
||||
|
||||
### P0-7 ResourceService
|
||||
|
||||
目标:提供资源异步加载、引用计数和分组释放。
|
||||
|
||||
接口:
|
||||
|
||||
```csharp
|
||||
public interface IResourceService
|
||||
{
|
||||
Task<IResourceHandle<T>> LoadAsync<T>(string key, CancellationToken ct) where T : class;
|
||||
IResourceGroup CreateGroup();
|
||||
}
|
||||
|
||||
public interface IResourceHandle<T> : IDisposable where T : class
|
||||
{
|
||||
string Key { get; }
|
||||
T Asset { get; }
|
||||
bool IsDisposed { get; }
|
||||
}
|
||||
|
||||
public interface IResourceGroup : IDisposable
|
||||
{
|
||||
void Add<T>(IResourceHandle<T> handle) where T : class;
|
||||
int Count { get; }
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 同 key 并发加载只触发一次底层加载。
|
||||
- 每次加载返回独立 handle。
|
||||
- 最后一个 handle Dispose 时释放底层资源。
|
||||
- Group Dispose 释放组内所有 handle。
|
||||
- 重复 Dispose 不抛异常。
|
||||
- Dispose 后访问 Asset 返回 null 或抛明确异常,二选一需在实现前固定。
|
||||
|
||||
P0 默认:
|
||||
|
||||
- Addressables 后端。
|
||||
|
||||
暂不做:
|
||||
|
||||
- Resources 后端。
|
||||
- AssetBundle 后端。
|
||||
- YooAsset 后端。
|
||||
- 下载进度。
|
||||
- 资源预热。
|
||||
- 资源分析工具。
|
||||
|
||||
扩展方向:
|
||||
|
||||
- P1/P2 通过 `IResourceBackend` 插件化接入其他资源系统。
|
||||
|
||||
### P0-8 UIManager
|
||||
|
||||
目标:提供 MVVM Panel 生命周期管理、可配置层级和层内栈。
|
||||
|
||||
接口:
|
||||
|
||||
```csharp
|
||||
public sealed class UIManager
|
||||
{
|
||||
void RegisterLayer(string name, Canvas canvas, int sortOrder, PanelStrategy strategy);
|
||||
Task<TPanel> OpenAsync<TPanel, TViewModel>(TViewModel vm, CancellationToken ct)
|
||||
where TPanel : UIPanelBase<TViewModel>;
|
||||
void Close<TPanel>();
|
||||
void CloseLayer(string layerName);
|
||||
void CloseAll();
|
||||
bool IsOpen<TPanel>();
|
||||
}
|
||||
```
|
||||
|
||||
Panel Attribute:
|
||||
|
||||
```csharp
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class UIPanelAttribute : Attribute
|
||||
{
|
||||
public string Layer { get; }
|
||||
public string Path { get; }
|
||||
public PanelStrategy? OverrideStrategy { get; }
|
||||
|
||||
public UIPanelAttribute(
|
||||
string layer,
|
||||
string path = null,
|
||||
PanelStrategy? overrideStrategy = null)
|
||||
{
|
||||
Layer = layer;
|
||||
Path = path;
|
||||
OverrideStrategy = overrideStrategy;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Panel 基类:
|
||||
|
||||
```csharp
|
||||
public abstract class UIPanelBase<TViewModel> : MonoBehaviour
|
||||
{
|
||||
protected TViewModel ViewModel { get; private set; }
|
||||
protected CompositeDisposable Disposables { get; } = new();
|
||||
|
||||
public void Bind(TViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
OnBind(viewModel);
|
||||
}
|
||||
|
||||
public virtual void Unbind()
|
||||
{
|
||||
Disposables.Clear();
|
||||
}
|
||||
|
||||
protected abstract void OnBind(TViewModel viewModel);
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- Panel 必须声明 `UIPanelAttribute`。
|
||||
- Attribute 未指定 path 时,按命名约定推导资源路径。
|
||||
- 同层级采用 LIFO 栈。
|
||||
- 关闭非栈顶 Panel 抛异常。
|
||||
- Destroy 策略关闭时释放资源 handle。
|
||||
- Cache 策略关闭时隐藏对象,复用时重新 Bind。
|
||||
|
||||
暂不做:
|
||||
|
||||
- 自动 UI 绑定。
|
||||
- 导航路由系统。
|
||||
- Panel 预加载。
|
||||
- SafeArea。
|
||||
- 多语言切换。
|
||||
- 红点系统。
|
||||
- 复杂转场动画。
|
||||
|
||||
### P0-9 AudioService
|
||||
|
||||
目标:提供基础音频播放能力。
|
||||
|
||||
接口:
|
||||
|
||||
```csharp
|
||||
public interface IAudioService
|
||||
{
|
||||
IAudioHandle PlayBgm(string key, bool loop = true, float fadeIn = 0f);
|
||||
IAudioHandle PlaySfx(string key);
|
||||
void StopBgm(float fadeOut = 0f);
|
||||
void SetBgmVolume(float volume);
|
||||
void SetSfxVolume(float volume);
|
||||
void SetMute(bool mute);
|
||||
}
|
||||
|
||||
public interface IAudioHandle
|
||||
{
|
||||
void Stop(float fadeOut = 0f);
|
||||
bool IsPlaying { get; }
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 同一时间只有一首 BGM。
|
||||
- 播放新 BGM 自动停止旧 BGM。
|
||||
- SFX 使用简单 AudioSource 池。
|
||||
- 音量参数 clamp 到 `[0, 1]`。
|
||||
- 资源通过 `IResourceService` 加载。
|
||||
|
||||
暂不做:
|
||||
|
||||
- 单个音频 Pause/Resume。
|
||||
- 单个音频 Volume。
|
||||
- AudioMixer 分组。
|
||||
- 3D 音频。
|
||||
- 动态音乐。
|
||||
- 音频配置表。
|
||||
|
||||
---
|
||||
|
||||
## P0 最小 Sample 验收
|
||||
|
||||
P0 完成时必须提供一个最小 Sample:
|
||||
|
||||
```text
|
||||
GameBootstrap
|
||||
-> 创建根 Container
|
||||
-> 注册 Container / Config / Save / Resource / UI / Audio
|
||||
-> 注册 PlayerData
|
||||
-> GameFlow.StartupAsync<MainMenuFeature>()
|
||||
|
||||
MainMenuFeature
|
||||
-> LoadAsync: 加载 MainMenuPanel 资源,创建 MainMenuViewModel
|
||||
-> EnterAsync: UIManager.OpenAsync<MainMenuPanel, MainMenuViewModel>()
|
||||
-> 点击按钮修改 PlayerData.Gold
|
||||
-> UI 文本响应刷新
|
||||
-> ShutdownAsync 保存 PlayerData
|
||||
```
|
||||
|
||||
验收标准:
|
||||
|
||||
- Sample 可在 Unity 中运行。
|
||||
- 首次启动创建默认 PlayerData。
|
||||
- 修改 Gold 后 UI 立即刷新。
|
||||
- 退出后保存 PlayerData。
|
||||
- 重新启动后恢复 PlayerData。
|
||||
- Feature 退出后资源组释放。
|
||||
- Feature 退出后订阅不再触发。
|
||||
- 连续打开/关闭 Panel 不残留订阅。
|
||||
|
||||
---
|
||||
|
||||
## P0 测试矩阵
|
||||
|
||||
| 模块 | 必测内容 |
|
||||
|------|----------|
|
||||
| Container | 显式工厂、自动注入、scope 覆盖、Dispose、循环依赖 |
|
||||
| GameFlow | 启动顺序、切换顺序、失败清理、非法状态调用、关闭 best-effort |
|
||||
| Feature | Load/Enter/Exit/Dispose 顺序、资源和订阅释放 |
|
||||
| Data/R3 | ReactiveProperty 同步、序列化/反序列化、订阅释放 |
|
||||
| Config | JSON 解析、重复 Id、不存在 Id、格式错误 |
|
||||
| Save | 保存/加载、默认值、删除、反序列化失败 |
|
||||
| Resource | 同 key 并发、引用计数、Group 释放、加载失败 |
|
||||
| UI | 层级注册、Open/Close、LIFO、Bind/Unbind、Cache/Destroy |
|
||||
| Audio | BGM 播放/停止、SFX 池、音量 clamp、静音、资源加载失败 |
|
||||
|
||||
---
|
||||
|
||||
## P1/P2 扩展方向
|
||||
|
||||
后续能力优先做成可插拔扩展,而不是污染 P0 核心:
|
||||
|
||||
| 能力 | 建议阶段 | 形式 |
|
||||
|------|----------|------|
|
||||
| CSV / Luban 配置 | P1/P2 | `IConfigParser` 插件 |
|
||||
| 按模块配置加载 | P1 | `IConfigSource` 扩展 |
|
||||
| 存档版本迁移 | P1/P2 | `ISaveMigration` 插件 |
|
||||
| 云存档 | P2 | `ISaveStorage` 插件 |
|
||||
| Resources / YooAsset / AssetBundle | P1/P2 | `IResourceBackend` 插件 |
|
||||
| UI 路由 / 返回栈策略 | P1 | `IUIScreenNavigator` 扩展 |
|
||||
| Panel 预加载 | P1 | UIManager 扩展服务 |
|
||||
| AudioMixer / 3D 音频 | P1/P2 | `IAudioBackend` 或 AudioService 扩展 |
|
||||
| 编辑器导入向导 | P2 | Editor 包 |
|
||||
| 配置/资源检查工具 | P2 | Editor 工具包 |
|
||||
|
||||
---
|
||||
|
||||
## 需要同步修正的旧文档
|
||||
|
||||
以下旧文档需要按本文档重新收敛:
|
||||
|
||||
- `docs/game-core-requirement-tiers.md`
|
||||
- `docs/requirements/p0-container.md`
|
||||
- `docs/requirements/p0-gameflow.md`
|
||||
- `docs/requirements/p0-feature.md`
|
||||
- `docs/requirements/p0-configprovider.md`
|
||||
- `docs/requirements/p0-saveservice.md`
|
||||
- `docs/requirements/p0-resourceservice.md`
|
||||
- `docs/requirements/p0-uimanager.md`
|
||||
- `docs/requirements/p0-audioservice.md`
|
||||
|
||||
其中主文档必须更新的决策:
|
||||
|
||||
- DI 自动注入进入 P0。
|
||||
- UI 层级从固定三层改为可配置层级。
|
||||
- UI 层内 LIFO 栈进入 P0。
|
||||
- JSON 是 P0 唯一默认配置格式。
|
||||
- 资源 P0 只保留 Addressables 默认后端,其他后端插件化。
|
||||
- 存档 P0 只保留本地轻量实现,云存档插件化。
|
||||
104
docs/requirements/p0-resourceservice.md
Normal file
104
docs/requirements/p0-resourceservice.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# P0-7: IResourceService 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 资源系统包含引用计数和资源组,因为它直接影响 Feature 释放正确性;但 P0 只实现 Addressables 默认后端,其他后端通过插件扩展。
|
||||
|
||||
## 目标
|
||||
|
||||
提供异步资源加载、同 key 并发合并、引用计数和资源组释放能力。
|
||||
|
||||
## 接口
|
||||
|
||||
```csharp
|
||||
public interface IResourceService
|
||||
{
|
||||
Task<IResourceHandle<T>> LoadAsync<T>(
|
||||
string key,
|
||||
CancellationToken cancellationToken)
|
||||
where T : class;
|
||||
|
||||
IResourceGroup CreateGroup();
|
||||
}
|
||||
|
||||
public interface IResourceHandle<T> : IDisposable where T : class
|
||||
{
|
||||
string Key { get; }
|
||||
T Asset { get; }
|
||||
bool IsDisposed { get; }
|
||||
}
|
||||
|
||||
public interface IResourceGroup : IDisposable
|
||||
{
|
||||
void Add<T>(IResourceHandle<T> handle) where T : class;
|
||||
int Count { get; }
|
||||
}
|
||||
```
|
||||
|
||||
## 行为规则
|
||||
|
||||
- `LoadAsync<T>` 加载失败时抛异常,异常包含 key 和后端类型。
|
||||
- 同 key 同类型并发加载只触发一次底层加载。
|
||||
- 每次 `LoadAsync` 返回独立 handle。
|
||||
- 每个 handle Dispose 时引用计数减一。
|
||||
- 最后一个 handle Dispose 时释放底层资源。
|
||||
- `IResourceGroup.Dispose` 释放组内所有 handle。
|
||||
- 重复 Dispose 不抛异常。
|
||||
- Group 重复 Add 同一个 handle 时忽略。
|
||||
- Dispose 后访问 `Asset` 的行为必须固定为返回 null。
|
||||
|
||||
## 默认后端
|
||||
|
||||
P0 默认只支持 Addressables。
|
||||
|
||||
```text
|
||||
IResourceService
|
||||
└── AddressablesResourceBackend
|
||||
```
|
||||
|
||||
## 暂不做
|
||||
|
||||
- Resources 后端。
|
||||
- AssetBundle 后端。
|
||||
- YooAsset 后端。
|
||||
- 资源预热。
|
||||
- 下载进度回调。
|
||||
- 资源优先级。
|
||||
- 资源版本管理。
|
||||
- 编辑器资源扫描和分析工具。
|
||||
|
||||
## 扩展方向
|
||||
|
||||
P1/P2 通过后端接口扩展:
|
||||
|
||||
```csharp
|
||||
public interface IResourceBackend
|
||||
{
|
||||
Task<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
|
||||
where T : class;
|
||||
|
||||
void Release(string key, object asset);
|
||||
}
|
||||
```
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | 正常加载 | LoadAsync 返回 handle,Asset 可用 |
|
||||
| 2 | 加载失败 | 不存在 key 抛异常并包含 key |
|
||||
| 3 | 引用计数 | 同 key 加载两次,最后一个 handle Dispose 后才释放底层资源 |
|
||||
| 4 | 并发合并 | 同 key 并发 LoadAsync 只触发一次底层加载 |
|
||||
| 5 | Group 释放 | Group Dispose 后组内 handle 全部释放 |
|
||||
| 6 | 重复 Dispose | handle/group 重复 Dispose 不抛异常 |
|
||||
| 7 | Dispose 后 Asset | 返回 null |
|
||||
| 8 | 取消加载 | CancellationToken 取消时抛 `OperationCanceledException` |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
IResourceService
|
||||
├── Addressables
|
||||
├── CancellationToken
|
||||
└── 被 Feature / UIManager / AudioService 依赖
|
||||
```
|
||||
96
docs/requirements/p0-saveservice.md
Normal file
96
docs/requirements/p0-saveservice.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# P0-6: ISaveService 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 存档系统只提供本地轻量存取;云存档、加密、压缩、复杂迁移和冲突解决通过后续扩展接入。
|
||||
|
||||
## 目标
|
||||
|
||||
提供本地保存、读取、删除和存在性检查能力,支撑 P0 Sample 的 Data 持久化。
|
||||
|
||||
## 接口
|
||||
|
||||
```csharp
|
||||
public interface ISaveService
|
||||
{
|
||||
Task SaveAsync<T>(string key, T data, CancellationToken cancellationToken);
|
||||
Task<T> LoadAsync<T>(string key, T defaultValue, CancellationToken cancellationToken);
|
||||
void Delete(string key);
|
||||
bool Exists(string key);
|
||||
}
|
||||
```
|
||||
|
||||
## 默认实现
|
||||
|
||||
P0 默认实现二选一:
|
||||
|
||||
| 实现 | 说明 |
|
||||
|------|------|
|
||||
| `FileSaveStorage` | 使用本地文件,适合普通存档 |
|
||||
| `PlayerPrefsSaveStorage` | 使用 PlayerPrefs,适合小型数据 |
|
||||
|
||||
P0 默认序列化为 JSON,并支持 R3 Data 中的 `ReactiveProperty<T>` / `ReactiveCollection<T>` 转换。
|
||||
|
||||
## 行为规则
|
||||
|
||||
- `LoadAsync` 读取不存在 key 时返回 `defaultValue`。
|
||||
- 反序列化失败返回 `defaultValue` 并记录警告。
|
||||
- 存储读取失败返回 `defaultValue` 并记录警告。
|
||||
- 写入失败抛异常,异常包含 key。
|
||||
- `Delete` 删除不存在 key 时静默忽略。
|
||||
- `Exists` 只检查当前默认存储后端。
|
||||
|
||||
## 暂不做
|
||||
|
||||
- `ListKeys`。
|
||||
- 多存档槽管理。
|
||||
- 自动存档。
|
||||
- 云存档完整实现。
|
||||
- 本地 + 云双写。
|
||||
- 加密。
|
||||
- 压缩。
|
||||
- 存档版本迁移。
|
||||
- 冲突解决。
|
||||
|
||||
## 扩展方向
|
||||
|
||||
后续通过以下接口扩展:
|
||||
|
||||
```csharp
|
||||
public interface ISaveStorage
|
||||
{
|
||||
Task WriteAsync(string key, byte[] data, CancellationToken cancellationToken);
|
||||
Task<byte[]> ReadAsync(string key, CancellationToken cancellationToken);
|
||||
void Delete(string key);
|
||||
bool Exists(string key);
|
||||
}
|
||||
|
||||
public interface ISaveSerializer
|
||||
{
|
||||
byte[] Serialize<T>(T data);
|
||||
T Deserialize<T>(byte[] bytes);
|
||||
}
|
||||
```
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | 保存和读取 | Save 后 Load 返回等价数据 |
|
||||
| 2 | 默认值 | 不存在 key 时返回传入 defaultValue |
|
||||
| 3 | 删除 | Delete 后 Exists 为 false,Load 返回 defaultValue |
|
||||
| 4 | Exists | 已保存 key 返回 true,未保存 key 返回 false |
|
||||
| 5 | ReactiveProperty 序列化 | 保存 JSON 不包含 ReactiveProperty 内部状态 |
|
||||
| 6 | 反序列化失败 | 返回 defaultValue 并记录警告 |
|
||||
| 7 | 写入失败 | 抛异常并包含 key |
|
||||
| 8 | 取消保存/加载 | CancellationToken 取消时抛 `OperationCanceledException` |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
ISaveService
|
||||
├── ISaveStorage
|
||||
├── ISaveSerializer
|
||||
├── JSON 库
|
||||
└── R3 JSON Converter
|
||||
```
|
||||
170
docs/requirements/p0-uimanager.md
Normal file
170
docs/requirements/p0-uimanager.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# P0-8: UIManager 需求详细文档
|
||||
|
||||
## 对齐说明
|
||||
|
||||
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 UIManager 支持可配置层级和层内 LIFO 栈,但不做完整导航路由系统。`UIPanelAttribute` 明确包含 `path` 参数。
|
||||
|
||||
## 目标
|
||||
|
||||
提供 MVVM Panel 生命周期管理、运行时层级注册、层内栈式打开关闭、手写 Bind/Unbind 和基础缓存/销毁策略。
|
||||
|
||||
## 层级注册
|
||||
|
||||
```csharp
|
||||
public sealed class UIManager
|
||||
{
|
||||
public void RegisterLayer(
|
||||
string name,
|
||||
Canvas canvas,
|
||||
int sortOrder,
|
||||
PanelStrategy strategy = PanelStrategy.Destroy);
|
||||
}
|
||||
|
||||
public enum PanelStrategy
|
||||
{
|
||||
Destroy,
|
||||
Cache
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- P0 不强制固定 `Bottom/Middle/Top`。
|
||||
- 推荐默认层级为 `background / hud / popup / dialog / system`。
|
||||
- 项目可只注册部分层级。
|
||||
- 未注册层级打开 Panel 时抛异常。
|
||||
|
||||
## Panel Attribute
|
||||
|
||||
```csharp
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class UIPanelAttribute : Attribute
|
||||
{
|
||||
public string Layer { get; }
|
||||
public string Path { get; }
|
||||
public PanelStrategy? OverrideStrategy { get; }
|
||||
|
||||
public UIPanelAttribute(
|
||||
string layer,
|
||||
string path = null,
|
||||
PanelStrategy? overrideStrategy = null)
|
||||
{
|
||||
Layer = layer;
|
||||
Path = path;
|
||||
OverrideStrategy = overrideStrategy;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- `Layer` 必填。
|
||||
- `Path` 可选;未填时按命名约定推导。
|
||||
- `OverrideStrategy` 可选,优先于层级默认策略。
|
||||
|
||||
## Panel 基类
|
||||
|
||||
```csharp
|
||||
public abstract class UIPanelBase<TViewModel> : MonoBehaviour
|
||||
{
|
||||
protected TViewModel ViewModel { get; private set; }
|
||||
protected CompositeDisposable Disposables { get; } = new();
|
||||
|
||||
public void Bind(TViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
OnBind(viewModel);
|
||||
}
|
||||
|
||||
public virtual void Unbind()
|
||||
{
|
||||
Disposables.Clear();
|
||||
}
|
||||
|
||||
protected abstract void OnBind(TViewModel viewModel);
|
||||
}
|
||||
```
|
||||
|
||||
## 打开与关闭接口
|
||||
|
||||
```csharp
|
||||
public sealed class UIManager
|
||||
{
|
||||
public Task<TPanel> OpenAsync<TPanel, TViewModel>(
|
||||
TViewModel viewModel,
|
||||
CancellationToken cancellationToken)
|
||||
where TPanel : UIPanelBase<TViewModel>;
|
||||
|
||||
public void Close<TPanel>();
|
||||
public void CloseLayer(string layerName);
|
||||
public void CloseAll();
|
||||
public bool IsOpen<TPanel>();
|
||||
}
|
||||
```
|
||||
|
||||
## 生命周期
|
||||
|
||||
```text
|
||||
OpenAsync:
|
||||
1. 读取 UIPanelAttribute
|
||||
2. 校验层级已注册
|
||||
3. 解析资源路径
|
||||
4. IResourceService.LoadAsync<GameObject>
|
||||
5. Instantiate 到目标 Canvas
|
||||
6. Bind(viewModel)
|
||||
7. SetActive(true)
|
||||
8. 压入层内栈
|
||||
|
||||
Close:
|
||||
1. 校验目标 Panel 是所在层级栈顶
|
||||
2. 从栈弹出
|
||||
3. Unbind()
|
||||
4. Destroy 策略:Destroy GameObject 并释放资源 handle
|
||||
5. Cache 策略:SetActive(false),保留实例和资源 handle
|
||||
```
|
||||
|
||||
路径约定:
|
||||
|
||||
```text
|
||||
ShopPanel -> UI/Shop/Prefab
|
||||
ConfirmPanel -> UI/Confirm/Prefab
|
||||
MainHudPanel -> UI/MainHud/Prefab
|
||||
```
|
||||
|
||||
## 暂不做
|
||||
|
||||
- 自动 UI 绑定。
|
||||
- 导航路由系统。
|
||||
- 跨 Feature 页面恢复。
|
||||
- Panel 预加载。
|
||||
- SafeArea。
|
||||
- 多语言 UI 切换。
|
||||
- 红点/通知系统。
|
||||
- 复杂转场动画。
|
||||
|
||||
## 验收标准
|
||||
|
||||
| # | 标准 | 通过条件 |
|
||||
|---|------|---------|
|
||||
| 1 | 注册层级 | RegisterLayer 后 Panel 可挂载到对应 Canvas |
|
||||
| 2 | Attribute 层级 | Panel 通过 UIPanelAttribute 找到层级 |
|
||||
| 3 | path 参数 | Attribute 指定 path 时按指定路径加载 |
|
||||
| 4 | 路径约定 | 未指定 path 时按命名约定加载 |
|
||||
| 5 | Open | 异步加载 Prefab、实例化、Bind、入栈 |
|
||||
| 6 | Close | 栈顶 Panel Unbind 并按策略关闭 |
|
||||
| 7 | LIFO | 关闭非栈顶 Panel 抛异常 |
|
||||
| 8 | Cache | Cache Panel 再次打开时复用并重新 Bind |
|
||||
| 9 | Destroy | Destroy Panel 关闭后释放资源 handle |
|
||||
| 10 | CloseLayer | 只关闭指定层级 |
|
||||
| 11 | CloseAll | 关闭所有层级 |
|
||||
| 12 | Bind 失败 | 清理已创建对象和资源 handle 后抛异常 |
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```text
|
||||
UIManager
|
||||
├── IResourceService
|
||||
├── R3
|
||||
├── Unity Canvas / GameObject / MonoBehaviour
|
||||
└── CancellationToken
|
||||
```
|
||||
@@ -0,0 +1,603 @@
|
||||
# FlowScope Game Core P0 Parallel Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Implement FlowScope Game Core P0 as a minimal Unity-ready vertical slice with stable shared contracts, isolated services, Feature lifecycle, MVVM UI, and a runnable MainMenu sample.
|
||||
|
||||
**Architecture:** Work starts with a single contracts branch/worktree that defines shared interfaces and project layout. After contracts are merged, independent worktrees implement Container, data/config/save, resources, UI, audio, GameFlow/Feature, and sample integration in parallel. Final integration happens in a dedicated worktree that owns cross-module fixes and sample validation.
|
||||
|
||||
**Tech Stack:** Unity C#, .NET Task/CancellationToken, R3, Addressables, Newtonsoft.Json or System.Text.Json, Unity Test Framework.
|
||||
|
||||
---
|
||||
|
||||
## Source Documents
|
||||
|
||||
- `docs/requirements/p0-requirements-set.md`
|
||||
- `docs/requirements/p0-container.md`
|
||||
- `docs/requirements/p0-gameflow.md`
|
||||
- `docs/requirements/p0-feature.md`
|
||||
- `docs/requirements/p0-data-r3.md`
|
||||
- `docs/requirements/p0-configprovider.md`
|
||||
- `docs/requirements/p0-saveservice.md`
|
||||
- `docs/requirements/p0-resourceservice.md`
|
||||
- `docs/requirements/p0-uimanager.md`
|
||||
- `docs/requirements/p0-audioservice.md`
|
||||
|
||||
## Worktree Strategy
|
||||
|
||||
Do not open all implementation worktrees before contracts are stable.
|
||||
|
||||
1. Create `p0-contracts` first.
|
||||
2. Merge `p0-contracts`.
|
||||
3. Create parallel worktrees from the merged contracts branch.
|
||||
4. Keep each worker inside its owned file set.
|
||||
5. Merge service worktrees before sample integration.
|
||||
6. Use a final `p0-integration` worktree to resolve seams and verify the sample.
|
||||
|
||||
Recommended worktrees:
|
||||
|
||||
| Worktree | Purpose |
|
||||
|----------|---------|
|
||||
| `p0-contracts` | Shared asmdefs, interfaces, base types, package skeleton |
|
||||
| `p0-container` | Container runtime, SourceGen adapter, reflection adapter |
|
||||
| `p0-data-config-save` | Data/R3 converters, JSON config, local save |
|
||||
| `p0-resource` | Addressables-backed resource service |
|
||||
| `p0-ui` | UIManager, UIPanelAttribute, UIPanelBase |
|
||||
| `p0-audio` | AudioService and audio handles |
|
||||
| `p0-gameflow-feature` | FeatureContext, GameFlow, FeatureBase |
|
||||
| `p0-sample-integration` | MainMenu sample scene, sample data/config/UI |
|
||||
| `p0-integration` | Final merge, compile, test, documentation fixes |
|
||||
|
||||
## Proposed Runtime Layout
|
||||
|
||||
Create the Core package under the Unity project:
|
||||
|
||||
```text
|
||||
My project/Assets/FlowScope/
|
||||
├── Runtime/
|
||||
│ ├── FlowScope.Runtime.asmdef
|
||||
│ ├── Common/
|
||||
│ ├── Container/
|
||||
│ ├── Flow/
|
||||
│ ├── Data/
|
||||
│ ├── Config/
|
||||
│ ├── Save/
|
||||
│ ├── Resources/
|
||||
│ ├── UI/
|
||||
│ └── Audio/
|
||||
├── Editor/
|
||||
│ └── FlowScope.Editor.asmdef
|
||||
├── Tests/
|
||||
│ ├── EditMode/
|
||||
│ │ └── FlowScope.Tests.EditMode.asmdef
|
||||
│ └── PlayMode/
|
||||
│ └── FlowScope.Tests.PlayMode.asmdef
|
||||
└── Samples/
|
||||
└── MainMenuP0/
|
||||
```
|
||||
|
||||
If the repo already introduces a different package layout before implementation starts, update this section first and keep all worker plans aligned.
|
||||
|
||||
---
|
||||
|
||||
## Task 0: Contracts Worktree
|
||||
|
||||
**Worktree:** `p0-contracts`
|
||||
|
||||
**Files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/FlowScope.Runtime.asmdef`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Common/ResultLog.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/InjectableAttribute.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/FeatureContext.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/IFeature.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/GameFlowState.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Config/IConfigProvider.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Config/IConfigRow.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/ISaveService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/IResourceService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/IResourceHandle.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/IResourceGroup.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/PanelStrategy.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIPanelAttribute.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIPanelBase.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/IAudioService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/IAudioHandle.cs`
|
||||
|
||||
- [ ] **Step 1: Create the runtime asmdef**
|
||||
|
||||
Create `My project/Assets/FlowScope/Runtime/FlowScope.Runtime.asmdef`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "FlowScope.Runtime",
|
||||
"rootNamespace": "FlowScope",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create Container contract skeleton**
|
||||
|
||||
Create `My project/Assets/FlowScope/Runtime/Container/Container.cs` with method signatures from `p0-container.md`:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
|
||||
namespace FlowScope.Container
|
||||
{
|
||||
public sealed class Container : IDisposable
|
||||
{
|
||||
public void RegisterInstance<T>(T instance) => throw new NotImplementedException();
|
||||
public void RegisterFactory<T>(Func<Container, T> factory) => throw new NotImplementedException();
|
||||
public void RegisterType<TInterface, TImplementation>() where TImplementation : TInterface => throw new NotImplementedException();
|
||||
public void RegisterType<TImplementation>() => throw new NotImplementedException();
|
||||
public void RegisterAssembly() => throw new NotImplementedException();
|
||||
public T Resolve<T>() => throw new NotImplementedException();
|
||||
public bool TryResolve<T>(out T value)
|
||||
{
|
||||
value = default;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public Container CreateScope() => throw new NotImplementedException();
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create Feature contracts**
|
||||
|
||||
Create `FeatureContext`, `IFeature`, and `GameFlowState` exactly matching `p0-feature.md` and `p0-gameflow.md`.
|
||||
|
||||
- [ ] **Step 4: Create service contracts**
|
||||
|
||||
Create the config, save, resource, UI, and audio interfaces exactly matching `p0-requirements-set.md`.
|
||||
|
||||
- [ ] **Step 5: Compile contracts**
|
||||
|
||||
Run Unity compile validation if available. If not available, open the project in Unity and verify there are no C# compile errors.
|
||||
|
||||
Expected: contracts compile with `NotImplementedException` bodies where concrete implementations are not yet owned by this task.
|
||||
|
||||
- [ ] **Step 6: Commit contracts**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime" docs/requirements
|
||||
git commit -m "定义 Game Core P0 共享契约"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task A: Container Implementation
|
||||
|
||||
**Worktree:** `p0-container`
|
||||
|
||||
**Depends on:** `p0-contracts`
|
||||
|
||||
**Owned files:**
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/ReflectionFactoryBuilder.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/GeneratedFactories.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Container/ContainerTests.cs`
|
||||
|
||||
**Do not modify:** Flow, UI, Resource, Save, Config, Audio implementation files.
|
||||
|
||||
- [ ] **Step 1: Write Container tests first**
|
||||
|
||||
Cover explicit factory, instance registration, child scope lookup, child override, duplicate root registration, dispose order, repeated dispose, and circular dependency.
|
||||
|
||||
- [ ] **Step 2: Implement explicit registration and Resolve**
|
||||
|
||||
Implement dictionary-backed registrations keyed by `(Type type, object key)` if key support is retained. If key support is deferred, do not add key overloads.
|
||||
|
||||
- [ ] **Step 3: Implement scope lookup and disposal**
|
||||
|
||||
Child scope lookup checks local registrations first, then parent. Dispose releases local factory-created instances in reverse creation order.
|
||||
|
||||
- [ ] **Step 4: Implement reflection adapter**
|
||||
|
||||
Only reflection adapter owns constructor inspection. Core Container receives a factory.
|
||||
|
||||
- [ ] **Step 5: Stub Source Generator integration**
|
||||
|
||||
Create `GeneratedFactories` as the stable handoff point. If real Source Generator is not implemented in this task, tests must prove explicit and reflection paths work, and Generator work must be tracked as a follow-up inside the same worktree before merge.
|
||||
|
||||
- [ ] **Step 6: Run Container tests**
|
||||
|
||||
Expected: all Container edit mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Container" "My project/Assets/FlowScope/Tests/EditMode/Container"
|
||||
git commit -m "实现 P0 容器核心能力"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task B: Data, Config, and Save
|
||||
|
||||
**Worktree:** `p0-data-config-save`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-container`
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Data/ReactivePropertyJsonConverter.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Config/JsonConfigProvider.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/JsonSaveSerializer.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/FileSaveStorage.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/PlayerPrefsSaveStorage.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/SaveService.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Data/DataSerializationTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Config/JsonConfigProviderTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Save/SaveServiceTests.cs`
|
||||
|
||||
**Do not modify:** Container internals, UIManager, AudioService, GameFlow.
|
||||
|
||||
- [ ] **Step 1: Write Data serialization tests**
|
||||
|
||||
Test `ReactiveProperty<int>` serializes as value and deserializes back into a Data instance.
|
||||
|
||||
- [ ] **Step 2: Implement reactive JSON converters**
|
||||
|
||||
Support P0 primitives: `int`, `float`, `string`, `bool`, and serializable structs.
|
||||
|
||||
- [ ] **Step 3: Write Config tests**
|
||||
|
||||
Use an in-memory JSON text source where possible. Test normal load, missing id, duplicate id, malformed JSON.
|
||||
|
||||
- [ ] **Step 4: Implement `JsonConfigProvider`**
|
||||
|
||||
Implement `LoadAllAsync`, `Get<T>`, and `GetAll<T>` for registered config types.
|
||||
|
||||
- [ ] **Step 5: Write Save tests**
|
||||
|
||||
Test save/load, missing key default, delete, exists, deserialization failure default.
|
||||
|
||||
- [ ] **Step 6: Implement SaveService**
|
||||
|
||||
Keep storage and serializer separable. Do not add `ListKeys`, cloud save, compression, or migration.
|
||||
|
||||
- [ ] **Step 7: Run tests**
|
||||
|
||||
Expected: Data, Config, and Save edit mode tests pass.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Data" "My project/Assets/FlowScope/Runtime/Config" "My project/Assets/FlowScope/Runtime/Save" "My project/Assets/FlowScope/Tests/EditMode/Data" "My project/Assets/FlowScope/Tests/EditMode/Config" "My project/Assets/FlowScope/Tests/EditMode/Save"
|
||||
git commit -m "实现 P0 数据配置和存档"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task C: ResourceService
|
||||
|
||||
**Worktree:** `p0-resource`
|
||||
|
||||
**Depends on:** `p0-contracts`
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceHandle.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceGroup.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceEntry.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Resources/ResourceGroupTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceServiceTests.cs`
|
||||
|
||||
**Do not modify:** UIManager or AudioService except through agreed interfaces.
|
||||
|
||||
- [ ] **Step 1: Write handle/group tests**
|
||||
|
||||
Test group add, group dispose, duplicate add, repeated dispose, disposed handle asset behavior.
|
||||
|
||||
- [ ] **Step 2: Implement handle and group**
|
||||
|
||||
`ResourceHandle<T>.Asset` returns null after Dispose, matching the P0 document.
|
||||
|
||||
- [ ] **Step 3: Write concurrent load test**
|
||||
|
||||
Use a fake backend if Addressables is hard to exercise in edit mode. Test that concurrent same-key loads share one backend load.
|
||||
|
||||
- [ ] **Step 4: Implement Addressables service**
|
||||
|
||||
Default backend is Addressables only. Do not implement Resources, AssetBundle, or YooAsset.
|
||||
|
||||
- [ ] **Step 5: Run resource tests**
|
||||
|
||||
Expected: edit mode tests pass; play mode Addressables test passes if sample assets are available.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Resources" "My project/Assets/FlowScope/Tests/EditMode/Resources" "My project/Assets/FlowScope/Tests/PlayMode/Resources"
|
||||
git commit -m "实现 P0 资源加载和引用计数"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task D: UIManager
|
||||
|
||||
**Worktree:** `p0-ui`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-resource`
|
||||
|
||||
**Owned files:**
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/UI/UIPanelAttribute.cs`
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/UI/UIPanelBase.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIManager.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UILayer.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIPanelRecord.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/UI/UIManagerTests.cs`
|
||||
|
||||
**Do not modify:** ResourceService internals.
|
||||
|
||||
- [ ] **Step 1: Write UI lifecycle tests**
|
||||
|
||||
Test layer registration, open, bind, close, unbind, LIFO error, cache reuse, destroy release.
|
||||
|
||||
- [ ] **Step 2: Implement `UIPanelAttribute` path support**
|
||||
|
||||
Constructor must include `string path = null` and `PanelStrategy? overrideStrategy = null`.
|
||||
|
||||
- [ ] **Step 3: Implement `UIPanelBase<TViewModel>`**
|
||||
|
||||
Keep only one `CompositeDisposable` property. No duplicate property declarations.
|
||||
|
||||
- [ ] **Step 4: Implement layer and stack management**
|
||||
|
||||
Close non-top panel throws `InvalidOperationException`.
|
||||
|
||||
- [ ] **Step 5: Implement path resolution**
|
||||
|
||||
Attribute path wins. Otherwise use `PanelName` without `Panel` suffix: `UI/{Name}/Prefab`.
|
||||
|
||||
- [ ] **Step 6: Run UI tests**
|
||||
|
||||
Expected: all UI play mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/UI" "My project/Assets/FlowScope/Tests/PlayMode/UI"
|
||||
git commit -m "实现 P0 UI 管理器"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task E: AudioService
|
||||
|
||||
**Worktree:** `p0-audio`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-resource`
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/AudioService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/AudioHandle.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/AudioServiceConfig.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/Audio/AudioServiceTests.cs`
|
||||
|
||||
**Do not modify:** ResourceService internals.
|
||||
|
||||
- [ ] **Step 1: Write audio tests**
|
||||
|
||||
Test BGM play/stop, BGM switch, SFX pool cap, mute, volume clamp, handle stop.
|
||||
|
||||
- [ ] **Step 2: Implement audio handle**
|
||||
|
||||
P0 handle supports `Stop(fadeOut)` and `IsPlaying`. Do not add per-handle `Volume`.
|
||||
|
||||
- [ ] **Step 3: Implement BGM channel**
|
||||
|
||||
One active BGM. New BGM stops old BGM.
|
||||
|
||||
- [ ] **Step 4: Implement SFX pool**
|
||||
|
||||
Default pool size is 10. Pool exhaustion behavior follows `AudioServiceConfig`.
|
||||
|
||||
- [ ] **Step 5: Implement fade**
|
||||
|
||||
Use local coroutine or update driver. Do not introduce a Timer service.
|
||||
|
||||
- [ ] **Step 6: Run audio tests**
|
||||
|
||||
Expected: all audio play mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Audio" "My project/Assets/FlowScope/Tests/PlayMode/Audio"
|
||||
git commit -m "实现 P0 音频服务"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task F: GameFlow and Feature
|
||||
|
||||
**Worktree:** `p0-gameflow-feature`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-container`, `p0-resource`
|
||||
|
||||
**Owned files:**
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/Flow/FeatureContext.cs`
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/Flow/IFeature.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/FeatureBase.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/GameFlow.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Flow/GameFlowTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Flow/FeatureBaseTests.cs`
|
||||
|
||||
**Do not modify:** Concrete UI, Audio, Save, Config, or Resource implementations.
|
||||
|
||||
- [ ] **Step 1: Write GameFlow state tests**
|
||||
|
||||
Test startup, switch, shutdown, illegal calls, NoActiveFeature behavior.
|
||||
|
||||
- [ ] **Step 2: Write failure cleanup tests**
|
||||
|
||||
Use fake Feature classes that fail in Load/Enter/Exit/Dispose.
|
||||
|
||||
- [ ] **Step 3: Implement FeatureContext**
|
||||
|
||||
It contains Scope, Resources, Disposables, and CancellationToken.
|
||||
|
||||
- [ ] **Step 4: Implement FeatureBase**
|
||||
|
||||
FeatureBase stores context, clears disposables on Exit, clears references on Dispose.
|
||||
|
||||
- [ ] **Step 5: Implement GameFlow**
|
||||
|
||||
GameFlow creates scope/resources/context and owns their final disposal.
|
||||
|
||||
- [ ] **Step 6: Run flow tests**
|
||||
|
||||
Expected: all flow edit mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Flow" "My project/Assets/FlowScope/Tests/EditMode/Flow"
|
||||
git commit -m "实现 P0 生命周期编排"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task G: MainMenu P0 Sample
|
||||
|
||||
**Worktree:** `p0-sample-integration`
|
||||
|
||||
**Depends on:** all service worktrees merged
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/GameBootstrap.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuFeature.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuViewModel.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/PlayerData.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuPanel.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Configs/player_start.json`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Prefabs/MainMenuPanel.prefab`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scenes/MainMenuP0.unity`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs`
|
||||
|
||||
- [ ] **Step 1: Create sample data**
|
||||
|
||||
`PlayerData` has at least `ReactiveProperty<int> Gold`.
|
||||
|
||||
- [ ] **Step 2: Create sample ViewModel**
|
||||
|
||||
ViewModel increments Gold through a public command method.
|
||||
|
||||
- [ ] **Step 3: Create sample Panel**
|
||||
|
||||
Panel binds Gold text and button click manually.
|
||||
|
||||
- [ ] **Step 4: Create MainMenuFeature**
|
||||
|
||||
Load creates ViewModel. Enter opens `MainMenuPanel`. Exit closes panel.
|
||||
|
||||
- [ ] **Step 5: Create GameBootstrap**
|
||||
|
||||
Bootstrap registers Container, Config, Save, Resource, UI, Audio, PlayerData, then calls `GameFlow.StartupAsync<MainMenuFeature>`.
|
||||
|
||||
- [ ] **Step 6: Write sample play mode test**
|
||||
|
||||
Test startup, click increment, UI refresh, shutdown save, restart restore.
|
||||
|
||||
- [ ] **Step 7: Run sample**
|
||||
|
||||
Expected: sample scene runs in Unity with no console errors.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Samples/MainMenuP0" "My project/Assets/FlowScope/Tests/PlayMode/Samples"
|
||||
git commit -m "添加 P0 主菜单纵向切片示例"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task H: Final Integration
|
||||
|
||||
**Worktree:** `p0-integration`
|
||||
|
||||
**Depends on:** all P0 implementation branches merged or available as PRs
|
||||
|
||||
**Owned files:**
|
||||
- Modify only files needed to resolve integration seams.
|
||||
- Modify docs only for verified behavior changes.
|
||||
|
||||
- [ ] **Step 1: Merge implementation branches one by one**
|
||||
|
||||
Recommended order:
|
||||
|
||||
```text
|
||||
p0-contracts
|
||||
p0-container
|
||||
p0-data-config-save
|
||||
p0-resource
|
||||
p0-ui
|
||||
p0-audio
|
||||
p0-gameflow-feature
|
||||
p0-sample-integration
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run full compile**
|
||||
|
||||
Use Unity compile validation. Expected: zero C# compile errors.
|
||||
|
||||
- [ ] **Step 3: Run edit mode tests**
|
||||
|
||||
Expected: Container, Data, Config, Save, Resource group, GameFlow tests pass.
|
||||
|
||||
- [ ] **Step 4: Run play mode tests**
|
||||
|
||||
Expected: UI, Audio, Addressables Resource, MainMenu sample tests pass.
|
||||
|
||||
- [ ] **Step 5: Manual sample validation**
|
||||
|
||||
Open `MainMenuP0.unity`. Verify:
|
||||
|
||||
- UI opens.
|
||||
- Gold increments on click.
|
||||
- UI updates immediately.
|
||||
- Exit saves data.
|
||||
- Restart restores data.
|
||||
- Closing Feature releases resources and subscriptions.
|
||||
|
||||
- [ ] **Step 6: Update docs**
|
||||
|
||||
Update `docs/requirements/p0-requirements-set.md` only if implementation discovered a necessary contract adjustment. Do not silently drift code away from docs.
|
||||
|
||||
- [ ] **Step 7: Commit integration**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope" docs/requirements
|
||||
git commit -m "集成 Game Core P0 纵向切片"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Merge Rules for Agent Workers
|
||||
|
||||
- Do not change shared contracts after `p0-contracts` merges unless the integration owner approves the contract change.
|
||||
- If a worker needs a new method on a shared interface, stop and write a short contract-change note before editing.
|
||||
- Do not introduce CSV, Luban, Resources backend, AssetBundle backend, cloud save, UI router, or AudioMixer in P0.
|
||||
- Do not add global EventBus in P0.
|
||||
- Do not make Feature create root scope; GameFlow owns FeatureContext creation.
|
||||
- Commit messages must be Chinese.
|
||||
|
||||
## Self-Review Checklist
|
||||
|
||||
- P0 contracts cover all modules in `p0-requirements-set.md`.
|
||||
- Every worker has an owned file set.
|
||||
- Parallel workers do not write the same implementation files.
|
||||
- Extension-only features are not assigned to P0 workers.
|
||||
- Final integration owns cross-module validation.
|
||||
447
docs/superpowers/specs/2026-05-14-game-core-design.md
Normal file
447
docs/superpowers/specs/2026-05-14-game-core-design.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# FlowScope Game Core 设计文档
|
||||
|
||||
## 定位
|
||||
|
||||
面向休闲/超休闲游戏的 Unity 客户端通用框架。业务逻辑层保持纯 C#,基础设施层直接使用 Unity API。
|
||||
|
||||
## 整体架构
|
||||
|
||||
```
|
||||
FlowScope Game Core
|
||||
│
|
||||
├── 基础设施层
|
||||
│ ├── Container — 自研轻量 DI(scope/resolve/dispose,无反射)
|
||||
│ └── R3 — ReactiveProperty、事件流、集合订阅
|
||||
│
|
||||
├── 核心服务层(全部接口化,实现可替换)
|
||||
│ ├── IResourceService — 资源加载/引用计数/分组释放
|
||||
│ ├── ISaveService — 存档/读档持久化
|
||||
│ ├── IConfigProvider — 配置表加载/查询
|
||||
│ ├── IAudioService — BGM/SFX 池化/音量控制
|
||||
│ ├── INetworkService — 网络请求/推送
|
||||
│ └── UIManager — MVVM Panel 生命周期/页面栈/层级
|
||||
│
|
||||
├── 数据层(按业务域拆分,ReactiveProperty 驱动)
|
||||
│ └── 各游戏按需定义自己的 Data 类
|
||||
│
|
||||
├── 业务特性层(自包含,独立生命周期)
|
||||
│ └── 各 Feature: Load → Enter → Exit → Dispose
|
||||
│
|
||||
└── 生命周期编排
|
||||
└── GameFlow — 启动/切换 Feature/关闭的全局时间线
|
||||
```
|
||||
|
||||
## 设计原则
|
||||
|
||||
1. 接口化:核心服务全部走接口,实现可替换
|
||||
2. 显式优于隐式:DI 不做反射,手动注册手动传参
|
||||
3. 按域拆分:数据类按业务域拆分,不建大 Data 类
|
||||
4. 特性自包含:每个 Feature 自带资源组、订阅、ViewModel,切换时干净释放
|
||||
5. 框架只提供工具:不预设具体 Data 类,游戏自己定义
|
||||
|
||||
## 依赖方向(单向,不反向)
|
||||
|
||||
```
|
||||
Feature → Core Services → Interfaces → Infrastructure (Container + R3)
|
||||
↓
|
||||
Data Classes(纯 C#,无依赖)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基础设施层
|
||||
|
||||
### Container — 自研轻量 DI
|
||||
|
||||
不做反射、不做构造函数自动注入、不做标签注入。显式注册,显式解析。
|
||||
|
||||
```csharp
|
||||
public class Container : IDisposable
|
||||
{
|
||||
void RegisterInstance<T>(T instance);
|
||||
void RegisterFactory<T>(Func<T> factory);
|
||||
T Resolve<T>();
|
||||
bool TryResolve<T>(out T value);
|
||||
Container CreateScope(); // 子 scope 可访问父级,可覆盖父级
|
||||
void Dispose(); // 释放 scope 内的 IDisposable
|
||||
}
|
||||
```
|
||||
|
||||
注册方式:
|
||||
|
||||
```csharp
|
||||
container.RegisterInstance<IConfig>(config);
|
||||
container.RegisterFactory<ISaveService>(() => new FileSaveService());
|
||||
```
|
||||
|
||||
### R3
|
||||
|
||||
直接使用 R3 库提供:
|
||||
|
||||
- ReactiveProperty — 可观察属性,MVVM 绑定基石
|
||||
- ReactiveCollection — 可观察列表
|
||||
- Subject — 事件流
|
||||
- Observable.Timer / EveryUpdate — 计时和帧调度
|
||||
- CompositeDisposable — 批量释放订阅
|
||||
|
||||
---
|
||||
|
||||
## 核心服务层
|
||||
|
||||
### IResourceService — 资源管理
|
||||
|
||||
```csharp
|
||||
public interface IResourceService
|
||||
{
|
||||
Task<IResourceHandle<T>> LoadAsync<T>(string key) where T : class;
|
||||
IResourceGroup CreateGroup();
|
||||
}
|
||||
|
||||
public interface IResourceHandle<T> : IDisposable
|
||||
{
|
||||
T Asset { get; }
|
||||
}
|
||||
|
||||
public interface IResourceGroup : IDisposable
|
||||
{
|
||||
void Add<T>(IResourceHandle<T> handle);
|
||||
}
|
||||
```
|
||||
|
||||
引用计数机制:同一 key 多次 Load 只加载一次,最后一个 handle Dispose 时才真正卸载。
|
||||
|
||||
默认实现:AddressablesResourceService。
|
||||
|
||||
### ISaveService — 持久化
|
||||
|
||||
```csharp
|
||||
public interface ISaveService
|
||||
{
|
||||
Task SaveAsync<T>(string key, T data);
|
||||
Task<T> LoadAsync<T>(string key, T defaultValue = default);
|
||||
void Delete(string key);
|
||||
}
|
||||
```
|
||||
|
||||
默认实现可选 FileSaveService(JSON 文件)或 PlayerPrefsSaveService。
|
||||
|
||||
### IConfigProvider — 配置管理
|
||||
|
||||
```csharp
|
||||
public interface IConfigRow
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
||||
|
||||
public interface IConfigProvider
|
||||
{
|
||||
Task LoadAllAsync();
|
||||
T Get<T>(int id) where T : IConfigRow;
|
||||
IReadOnlyList<T> GetAll<T>() where T : IConfigRow;
|
||||
}
|
||||
```
|
||||
|
||||
不绑定数据源(JSON / CSV / Luban),由实现决定。默认 JsonConfigProvider。
|
||||
|
||||
### IAudioService — 音频管理
|
||||
|
||||
```csharp
|
||||
public interface IAudioService
|
||||
{
|
||||
void PlayBgm(string key, bool loop = true);
|
||||
void StopBgm(float fadeOut = 0.5f);
|
||||
void PlaySfx(string key);
|
||||
void SetBgmVolume(float volume);
|
||||
void SetSfxVolume(float volume);
|
||||
void SetMute(bool mute);
|
||||
}
|
||||
```
|
||||
|
||||
内部:AudioSource 对象池,BGM 单独一个,SFX 池化复用。
|
||||
|
||||
### INetworkService — 网络管理
|
||||
|
||||
```csharp
|
||||
public interface INetworkService
|
||||
{
|
||||
Task<TResponse> SendAsync<TRequest, TResponse>(string path, TRequest data);
|
||||
void On<TEvent>(Action<TEvent> handler);
|
||||
}
|
||||
```
|
||||
|
||||
初期可能用不上,但留好接口。默认 HttpNetworkService。
|
||||
|
||||
### UIManager — UI 管理
|
||||
|
||||
MVVM 模式的 Panel 生命周期管理。
|
||||
|
||||
```csharp
|
||||
public class UIManager
|
||||
{
|
||||
Task OpenAsync<TPanel, TViewModel>(TViewModel viewModel) where TPanel : IPanel;
|
||||
void Close<TPanel>();
|
||||
void CloseAll();
|
||||
}
|
||||
|
||||
public interface IPanel
|
||||
{
|
||||
void Bind(object viewModel);
|
||||
void Unbind();
|
||||
}
|
||||
```
|
||||
|
||||
Panel 生命周期:Instantiate → Bind → Show → ... → Hide → Unbind → Destroy。
|
||||
|
||||
层级管理:Bottom(基础层)、Middle(弹窗层)、Top(提示层)。
|
||||
|
||||
ViewModel 示例(纯 C#):
|
||||
|
||||
```csharp
|
||||
public class ShopViewModel
|
||||
{
|
||||
public ReactiveProperty<string> Title { get; } = new("商店");
|
||||
public ReactiveCollection<ShopItemViewModel> Items { get; } = new();
|
||||
|
||||
private readonly PlayerData _player;
|
||||
private readonly IConfigProvider _config;
|
||||
|
||||
public void BuyItem(int itemId)
|
||||
{
|
||||
var config = _config.Get<ItemConfig>(itemId);
|
||||
if (_player.Gold.Value < config.Price) return;
|
||||
_player.Gold.Value -= config.Price;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Panel 示例(Unity MonoBehaviour):
|
||||
|
||||
```csharp
|
||||
public class ShopPanel : MonoBehaviour, IPanel
|
||||
{
|
||||
[SerializeField] private Text titleText;
|
||||
[SerializeField] private Transform itemListRoot;
|
||||
|
||||
private ShopViewModel _vm;
|
||||
private CompositeDisposable _disposables = new();
|
||||
|
||||
public void Bind(object viewModel)
|
||||
{
|
||||
_vm = (ShopViewModel)viewModel;
|
||||
_vm.Title.Subscribe(t => titleText.text = t).AddTo(_disposables);
|
||||
_vm.Items.ObserveAdd().Subscribe(e => CreateItemCell(e.Value)).AddTo(_disposables);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
_disposables.Clear();
|
||||
_vm = null;
|
||||
}
|
||||
|
||||
private void OnDestroy() => Unbind();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据层
|
||||
|
||||
框架不提供任何具体 Data 类,只提供 ReactiveProperty / ReactiveCollection 工具。
|
||||
|
||||
每个游戏按业务域自己定义:
|
||||
|
||||
```csharp
|
||||
// 游戏需要金币?建一个
|
||||
public class PlayerData
|
||||
{
|
||||
public ReactiveProperty<int> Level { get; } = new(1);
|
||||
public ReactiveProperty<int> Gold { get; } = new(0);
|
||||
}
|
||||
|
||||
// 不需要背包?就不建 InventoryData
|
||||
// 需要设置?按需加
|
||||
public class SettingsData
|
||||
{
|
||||
public ReactiveProperty<float> BgmVolume { get; } = new(1f);
|
||||
public ReactiveProperty<string> Language { get; } = new("zh");
|
||||
}
|
||||
```
|
||||
|
||||
原则:一个 Data 类 = 一个业务域,新业务加新类。
|
||||
|
||||
---
|
||||
|
||||
## 业务特性层
|
||||
|
||||
### IFeature
|
||||
|
||||
```csharp
|
||||
public interface IFeature : IDisposable
|
||||
{
|
||||
Task LoadAsync(); // 加载资源、创建 ViewModel
|
||||
Task EnterAsync(); // 显示 UI、订阅事件
|
||||
Task ExitAsync(); // 隐藏 UI、取消订阅
|
||||
void Dispose(); // 释放资源
|
||||
}
|
||||
```
|
||||
|
||||
每个 Feature 自包含:自己的资源组、订阅、ViewModel。切换时干净释放。
|
||||
|
||||
Feature 示例:
|
||||
|
||||
```csharp
|
||||
public class ShopFeature : IFeature
|
||||
{
|
||||
private readonly IResourceService _resource;
|
||||
private readonly UIManager _uiManager;
|
||||
private readonly PlayerData _player;
|
||||
private readonly IConfigProvider _config;
|
||||
|
||||
private IResourceGroup _resources;
|
||||
private CompositeDisposable _disposables;
|
||||
private ShopViewModel _viewModel;
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
_resources = _resource.CreateGroup();
|
||||
var bgm = await _resource.LoadHandleAsync<AudioClip>("audio/bgm_shop");
|
||||
_resources.Add(bgm);
|
||||
_viewModel = new ShopViewModel(_player, _config);
|
||||
}
|
||||
|
||||
public async Task EnterAsync()
|
||||
{
|
||||
_disposables = new CompositeDisposable();
|
||||
await _uiManager.OpenAsync<ShopPanel, ShopViewModel>(_viewModel);
|
||||
}
|
||||
|
||||
public async Task ExitAsync()
|
||||
{
|
||||
_uiManager.Close<ShopPanel>();
|
||||
_disposables?.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_resources?.Dispose();
|
||||
_viewModel = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 生命周期编排
|
||||
|
||||
### GameFlow
|
||||
|
||||
```csharp
|
||||
public class GameFlow
|
||||
{
|
||||
private readonly Container _container;
|
||||
private IFeature _currentFeature;
|
||||
|
||||
public async Task StartupAsync()
|
||||
{
|
||||
var config = _container.Resolve<IConfigProvider>();
|
||||
await config.LoadAllAsync();
|
||||
|
||||
var save = _container.Resolve<ISaveService>();
|
||||
var player = await save.LoadAsync<PlayerData>("player", new PlayerData());
|
||||
_container.RegisterInstance(player);
|
||||
|
||||
await SwitchToAsync<MainMenuFeature>();
|
||||
}
|
||||
|
||||
public async Task SwitchToAsync<TFeature>() where TFeature : IFeature, new()
|
||||
{
|
||||
if (_currentFeature != null)
|
||||
{
|
||||
await _currentFeature.ExitAsync();
|
||||
_currentFeature.Dispose();
|
||||
}
|
||||
|
||||
var feature = new TFeature();
|
||||
await feature.LoadAsync();
|
||||
await feature.EnterAsync();
|
||||
_currentFeature = feature;
|
||||
}
|
||||
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
if (_currentFeature != null)
|
||||
{
|
||||
await _currentFeature.ExitAsync();
|
||||
_currentFeature.Dispose();
|
||||
}
|
||||
|
||||
var save = _container.Resolve<ISaveService>();
|
||||
await save.SaveAsync("player", _container.Resolve<PlayerData>());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 启动入口
|
||||
|
||||
```csharp
|
||||
public class GameBootstrap : MonoBehaviour
|
||||
{
|
||||
private void Awake()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
// 核心服务
|
||||
container.RegisterInstance<IResourceService>(new AddressablesResourceService());
|
||||
container.RegisterInstance<ISaveService>(new FileSaveService());
|
||||
container.RegisterInstance<IConfigProvider>(new JsonConfigProvider());
|
||||
container.RegisterInstance<IAudioService>(new UnityAudioService());
|
||||
container.RegisterInstance<UIManager>(new UIManager());
|
||||
|
||||
// 游戏数据
|
||||
container.RegisterInstance(new PlayerData());
|
||||
container.RegisterInstance(new SettingsData());
|
||||
|
||||
var flow = new GameFlow(container);
|
||||
flow.StartupAsync().Forget();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块总表
|
||||
|
||||
| 模块 | 职责 | 类型 |
|
||||
|------|------|------|
|
||||
| Container | 轻量 DI,scope/resolve/dispose | 自研 |
|
||||
| R3 | ReactiveProperty、事件流、调度 | 第三方库 |
|
||||
| IResourceService | 资源加载、引用计数、分组释放 | 接口 |
|
||||
| ISaveService | 存档/读档持久化 | 接口 |
|
||||
| IConfigProvider | 配置表加载/查询 | 接口 |
|
||||
| IAudioService | BGM/SFX 池化、音量控制 | 接口 |
|
||||
| INetworkService | 网络请求/推送 | 接口 |
|
||||
| UIManager | MVVM Panel 生命周期、页面栈、层级 | 类 |
|
||||
| Data 类 | 按业务域拆分的运行时状态 | 游戏自定义 |
|
||||
| IFeature | 自包含特性模块,独立生命周期 | 接口 |
|
||||
| GameFlow | 全局启动/切换/关闭时间线 | 类 |
|
||||
|
||||
## 技术选型
|
||||
|
||||
| 选型 | 决定 | 理由 |
|
||||
|------|------|------|
|
||||
| DI 容器 | 自研 | 休闲游戏只需显式注册/解析/scope,不需要反射注入 |
|
||||
| 响应式 | R3 | 成熟的 Unity 响应式库,自带 ObservableProperty/Collection |
|
||||
| 异步 | C# Task + async/await | 休闲游戏的 GC 开销可忽略,不需要 UniTask 的零 GC |
|
||||
| 资源加载 | Addressables | Unity 标准,通过 IResourceService 接口可替换 |
|
||||
| 配置数据 | JSON(默认) | 通过 IConfigProvider 接口可换 Luban |
|
||||
| UI | uGUI + MVVM | BindableProperty 驱动,ViewModel 纯 C# |
|
||||
|
||||
## 不做的事
|
||||
|
||||
- 不做反射式 DI / 自动注入
|
||||
- 不做完整的 ORM
|
||||
- 不做 UI 自动绑定
|
||||
- 不做热更新框架
|
||||
- 不做编辑器工具
|
||||
- 不预设具体业务 Data 类
|
||||
- 不依赖 UniTask(C# Task 够用)
|
||||
@@ -0,0 +1,89 @@
|
||||
# FlowScope Game Core 需求分档
|
||||
|
||||
## 背景
|
||||
|
||||
FlowScope Game Core 的 P0 需求已经从早期“大而全框架蓝图”收敛为一套可执行的最小纵向切片。旧讨论中的部分能力已经重新分档:
|
||||
|
||||
- DI 自动注入进入 P0,但 Container 核心保持轻量。
|
||||
- UI 层级从固定三层改为可配置层级。
|
||||
- UI 层内 LIFO 栈进入 P0,完整导航路由延后。
|
||||
- 配置 P0 只支持 JSON。
|
||||
- 存档 P0 只支持本地轻量实现。
|
||||
- 资源 P0 支持引用计数,但只保留 Addressables 默认后端。
|
||||
- 音频 P0 只保留基础 BGM/SFX 能力和轻量 AudioHandle。
|
||||
|
||||
## 当前 P0 单一事实来源
|
||||
|
||||
P0 详细需求以以下文档为准:
|
||||
|
||||
- `docs/requirements/p0-requirements-set.md`
|
||||
- `docs/requirements/p0-container.md`
|
||||
- `docs/requirements/p0-gameflow.md`
|
||||
- `docs/requirements/p0-feature.md`
|
||||
- `docs/requirements/p0-data-r3.md`
|
||||
- `docs/requirements/p0-configprovider.md`
|
||||
- `docs/requirements/p0-saveservice.md`
|
||||
- `docs/requirements/p0-resourceservice.md`
|
||||
- `docs/requirements/p0-uimanager.md`
|
||||
- `docs/requirements/p0-audioservice.md`
|
||||
|
||||
并行实施计划:
|
||||
|
||||
- `docs/superpowers/plans/2026-05-15-p0-parallel-implementation-plan.md`
|
||||
|
||||
---
|
||||
|
||||
## P0:最小可运行核心
|
||||
|
||||
目标:跑通一个真实休闲/超休闲 Unity 游戏的启动、配置加载、存档加载、Feature 生命周期、MVVM UI、资源释放、音频播放和退出保存。
|
||||
|
||||
P0 包含:
|
||||
|
||||
| 模块 | P0 决策 |
|
||||
|------|---------|
|
||||
| Container | 显式工厂、Source Generator 自动注入、运行时反射降级 |
|
||||
| GameFlow | 全局生命周期编排和 FeatureContext 所有权 |
|
||||
| Feature | 消费 FeatureContext,负责业务行为和引用清理 |
|
||||
| R3 + Data | 直接使用 R3,Data 保持纯 C# |
|
||||
| ConfigProvider | JSON-only 强类型配置 |
|
||||
| SaveService | 本地轻量存档 |
|
||||
| ResourceService | Addressables-only 默认后端,支持引用计数 |
|
||||
| UIManager | 可配置层级,层内 LIFO |
|
||||
| AudioService | BGM/SFX、音量、静音、简单 SFX 池 |
|
||||
|
||||
---
|
||||
|
||||
## P1:生产能力扩展
|
||||
|
||||
P1 只在 P0 接口稳定后推进,优先做成扩展而不是污染核心。
|
||||
|
||||
- CSV / Luban 配置插件。
|
||||
- 按模块配置加载。
|
||||
- 存档版本迁移。
|
||||
- Resources / YooAsset 后端。
|
||||
- UI 返回策略。
|
||||
- Panel 预加载。
|
||||
- AudioMixer / 3D 音频。
|
||||
|
||||
---
|
||||
|
||||
## P2:包分发与编辑器体验
|
||||
|
||||
- Unity Package 分发。
|
||||
- Samples~。
|
||||
- Editor 导入向导。
|
||||
- 配置检查工具。
|
||||
- 资源扫描工具。
|
||||
- 云存档适配。
|
||||
- AssetBundle 适配。
|
||||
|
||||
---
|
||||
|
||||
## P3:长期演进
|
||||
|
||||
- `[Inject]` 属性/方法注入。
|
||||
- 全局 EventBus。
|
||||
- 网络长连接。
|
||||
- 热更新框架。
|
||||
- UI 自动绑定。
|
||||
- 大型项目资源预算和内存分析。
|
||||
452
docs/superpowers/specs/2026-05-14-gamecore-design.md
Normal file
452
docs/superpowers/specs/2026-05-14-gamecore-design.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# GameCore 游戏框架设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
通用 Unity 游戏框架,以 Unity Package 形式分发,基于自研轻量 DI 容器,采用核心扩展式架构。目标:跨项目可复用 + 开箱即用的常用系统。
|
||||
|
||||
- **目标平台:** Unity 2022 LTS
|
||||
- **架构模式:** 依赖注入 (DI)
|
||||
- **分发形式:** Unity Package
|
||||
- **DI 容器:** 自研轻量实现(反射,预留 Source Generator 优化接口)
|
||||
|
||||
---
|
||||
|
||||
## 架构:核心扩展式
|
||||
|
||||
```
|
||||
GameCore (核心 asmdef)
|
||||
├── DI 容器
|
||||
├── 所有模块接口定义
|
||||
└── 核心模块:事件系统、对象池、游戏流程控制、计时器
|
||||
|
||||
扩展包 (各自独立 asmdef,只依赖核心)
|
||||
├── GameCore.UI
|
||||
├── GameCore.Audio
|
||||
├── GameCore.Resource
|
||||
├── GameCore.Data
|
||||
├── GameCore.Save
|
||||
└── GameCore.Network
|
||||
|
||||
可替换实现 (各自独立 asmdef)
|
||||
├── GameCore.Defaults — 默认 EventBus + Timer
|
||||
├── GameCore.R3 — R3 实现(可选替换 Defaults)
|
||||
├── GameCore.Resource.Resources
|
||||
├── GameCore.Resource.Addressables
|
||||
├── GameCore.Save.FileStorage
|
||||
└── GameCore.Save.PlayerPrefsStorage
|
||||
```
|
||||
|
||||
**设计原则:**
|
||||
- 核心包定义接口,不包含具体实现(DI、Pool、Lifecycle 除外)
|
||||
- 扩展包只依赖核心,互不依赖
|
||||
- 可替换实现通过 DI Installer 注册,换实现不改上层代码
|
||||
|
||||
---
|
||||
|
||||
## DI 容器
|
||||
|
||||
### 能力
|
||||
|
||||
- 接口→实现绑定 (`Bind<IAudioManager>().To<AudioManager>()`)
|
||||
- 实例绑定 (`ToValue(instance)`)
|
||||
- 生命周期:`AsSingleton()` / `AsTransient()`
|
||||
- 子容器(场景级作用域,子容器销毁时自动释放其单例)
|
||||
- 构造函数注入(普通 C# 类)
|
||||
- 属性注入 `[Inject]`(MonoBehaviour)
|
||||
- 方法注入 `[Inject]`(初始化/PostConstruct)
|
||||
- 自动递归解析依赖链
|
||||
- 反射实现,预留 Source Generator 优化接口
|
||||
|
||||
### 子容器
|
||||
|
||||
```csharp
|
||||
// 全局容器(跨场景)
|
||||
var root = new GameContainer();
|
||||
|
||||
// 场景容器(继承全局,场景销毁时一起释放)
|
||||
var sceneContainer = root.CreateChild();
|
||||
|
||||
// 子容器可访问父容器绑定
|
||||
sceneContainer.Resolve<IAudioManager>(); // 从父容器拿
|
||||
|
||||
// 子容器同名绑定覆盖父容器
|
||||
sceneContainer.Bind<IGameManager>().To<GameManager>().AsSingleton();
|
||||
```
|
||||
|
||||
### 注入方式
|
||||
|
||||
```csharp
|
||||
// 构造函数注入 — 普通 C# 类
|
||||
public class AudioManager : IAudioManager
|
||||
{
|
||||
private readonly IEventBus _eventBus;
|
||||
public AudioManager(IEventBus eventBus) { _eventBus = eventBus; }
|
||||
}
|
||||
|
||||
// 属性注入 — MonoBehaviour
|
||||
public class GameCoreEntry : MonoBehaviour
|
||||
{
|
||||
[Inject] private IEventBus _eventBus;
|
||||
}
|
||||
|
||||
// 方法注入 — 初始化/PostConstruct
|
||||
public class GameManager : IGameManager
|
||||
{
|
||||
[Inject]
|
||||
public void Init(IEventBus eventBus, IDataManager data)
|
||||
{
|
||||
// 所有依赖就绪后执行
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心模块
|
||||
|
||||
### 事件总线 (IEventBus)
|
||||
|
||||
接口定义在核心包,实现可替换。
|
||||
|
||||
```csharp
|
||||
public interface IEventBus
|
||||
{
|
||||
void Subscribe<T>(Action<T> handler) where T : IEvent;
|
||||
void Unsubscribe<T>(Action<T> handler) where T : IEvent;
|
||||
void Publish<T>(T evt) where T : IEvent;
|
||||
}
|
||||
```
|
||||
|
||||
- 事件用 `struct`,避免 GC 压力
|
||||
- 泛型事件分发,每个事件类型独立维护订阅者列表
|
||||
|
||||
默认实现:`GameCore.Defaults.DefaultEventBus`
|
||||
R3 实现:`GameCore.R3.R3EventBus`
|
||||
|
||||
### 计时器/调度器 (ITimerManager)
|
||||
|
||||
接口定义在核心包,实现可替换。
|
||||
|
||||
```csharp
|
||||
public interface ITimerManager
|
||||
{
|
||||
ITimerHandle Delay(float seconds, Action callback);
|
||||
ITimerHandle Repeat(float interval, Action callback);
|
||||
void Tick(float deltaTime);
|
||||
}
|
||||
|
||||
public interface ITimerHandle
|
||||
{
|
||||
void Cancel();
|
||||
bool IsDone { get; }
|
||||
}
|
||||
```
|
||||
|
||||
- 不依赖 MonoBehaviour Coroutine,纯 Tick 驱动
|
||||
- 内部用对象池管理 Timer 对象
|
||||
|
||||
默认实现:`GameCore.Defaults.DefaultTimerManager`
|
||||
R3 实现:`GameCore.R3.R3TimerManager`
|
||||
|
||||
### 对象池 (IPoolManager)
|
||||
|
||||
```csharp
|
||||
public interface IPoolManager
|
||||
{
|
||||
IPool<T> CreatePool<T>(Func<T> factory, int prewarm = 0);
|
||||
IGameObjectPool CreateGameObjectPool(string prefabPath, int prewarm = 0);
|
||||
}
|
||||
|
||||
public interface IPool<T>
|
||||
{
|
||||
T Get();
|
||||
void Release(T item);
|
||||
}
|
||||
```
|
||||
|
||||
- 泛型对象池,支持任意类型
|
||||
- 预加载容量,支持预热
|
||||
- GameObject 池的创建/销毁由 Resource 扩展包实现,核心只定义接口
|
||||
|
||||
### 游戏流程控制 (GameLifecycle)
|
||||
|
||||
```csharp
|
||||
public static class GameCore
|
||||
{
|
||||
public static void Launch(StartupConfig config);
|
||||
public static void Tick();
|
||||
public static void Shutdown();
|
||||
}
|
||||
|
||||
public class StartupConfig
|
||||
{
|
||||
public IInstaller[] Installers;
|
||||
public IStartupTask[] StartupTasks;
|
||||
}
|
||||
|
||||
public interface IInstaller
|
||||
{
|
||||
void Install(IGameContainer container);
|
||||
}
|
||||
|
||||
public interface IStartupTask
|
||||
{
|
||||
Task Execute();
|
||||
}
|
||||
```
|
||||
|
||||
生命周期阶段:
|
||||
```
|
||||
Launch → Installers 注册绑定 → ResolveAll 创建单例 → StartupTasks 按序执行 → Running
|
||||
↓
|
||||
场景切换时
|
||||
子容器销毁
|
||||
↓
|
||||
Shutdown
|
||||
```
|
||||
|
||||
- `IInstaller` 统一模块注册入口
|
||||
- `IStartupTask` 控制启动顺序(支持异步)
|
||||
- 场景切换时自动销毁场景级子容器
|
||||
- Shutdown 时按反序释放所有模块
|
||||
|
||||
---
|
||||
|
||||
## 扩展包
|
||||
|
||||
### GameCore.Resource — 资源加载
|
||||
|
||||
```csharp
|
||||
public interface IResourceLoader
|
||||
{
|
||||
T Load<T>(string path) where T : Object;
|
||||
Task<T> LoadAsync<T>(string address) where T : Object;
|
||||
void Release<T>(T asset);
|
||||
void ReleaseAll();
|
||||
}
|
||||
```
|
||||
|
||||
- 统一封装,支持引用计数,同一资源多次加载不重复
|
||||
- 存储实现可替换:Resources / Addressables / AssetBundle / 自定义
|
||||
|
||||
### GameCore.UI — UI 管理
|
||||
|
||||
```csharp
|
||||
public interface IUIManager
|
||||
{
|
||||
T Open<T>(string layer = null) where T : UIPanelBase;
|
||||
void Close<T>() where T : UIPanelBase;
|
||||
void CloseAll();
|
||||
void CloseLayer(string layer);
|
||||
void RegisterLayer(string name, Canvas canvas, int sortOrder);
|
||||
}
|
||||
|
||||
public abstract class UIPanelBase : MonoBehaviour
|
||||
{
|
||||
[Inject] protected IEventBus _eventBus;
|
||||
[Inject] protected IResourceLoader _loader;
|
||||
public virtual void OnOpen() { }
|
||||
public virtual void OnClose() { }
|
||||
}
|
||||
```
|
||||
|
||||
- 层级由用户自定义注册,不预设固定层级
|
||||
- 面板栈管理,支持返回上一级
|
||||
- 面板复用(配合对象池)
|
||||
|
||||
### GameCore.Audio — 音频管理
|
||||
|
||||
```csharp
|
||||
public interface IAudioManager
|
||||
{
|
||||
IAudioHandle PlayBGM(string name, bool loop = true, float fadeIn = 0f);
|
||||
void StopBGM(float fadeOut = 0f);
|
||||
IAudioHandle PlaySFX(string name);
|
||||
void Stop(IAudioHandle handle);
|
||||
void Pause(IAudioHandle handle);
|
||||
void Resume(IAudioHandle handle);
|
||||
void SetBGMVolume(float volume);
|
||||
void SetSFXVolume(float volume);
|
||||
void MuteAll();
|
||||
void UnmuteAll();
|
||||
}
|
||||
|
||||
public interface IAudioHandle
|
||||
{
|
||||
float Volume { get; set; }
|
||||
bool IsPlaying { get; }
|
||||
void Stop(float fadeOut = 0f);
|
||||
}
|
||||
```
|
||||
|
||||
- fadeIn/fadeOut 处理音频过渡,具体曲线在实现层
|
||||
- IAudioHandle 可停止单个音频
|
||||
- SFX 用对象池管理 AudioSource
|
||||
|
||||
### GameCore.Data — 数据/配置管理
|
||||
|
||||
```csharp
|
||||
public interface IDataManager
|
||||
{
|
||||
void RegisterParser<TParser>(string format) where TParser : IDataParser;
|
||||
void LoadModule(string moduleName);
|
||||
void UnloadModule(string moduleName);
|
||||
bool IsModuleLoaded(string moduleName);
|
||||
T GetConfig<T>(string key) where T : IConfig;
|
||||
IReadOnlyList<T> GetConfigs<T>() where T : IConfig;
|
||||
}
|
||||
|
||||
public interface IDataParser
|
||||
{
|
||||
IEnumerable<IConfig> Parse(string content);
|
||||
}
|
||||
```
|
||||
|
||||
- 格式自定义:注册 IDataParser 实现(JSON/CSV/ScriptableObject/自定义)
|
||||
- 分模块按需加载和卸载
|
||||
|
||||
### GameCore.Save — 存档系统
|
||||
|
||||
```csharp
|
||||
public interface ISaveManager
|
||||
{
|
||||
Task Save(string slotName, object data);
|
||||
Task<T> Load<T>(string slotName);
|
||||
void Delete(string slotName);
|
||||
bool Exists(string slotName);
|
||||
string[] ListSaves();
|
||||
}
|
||||
|
||||
public interface ISaveStorage
|
||||
{
|
||||
Task Write(string key, byte[] data);
|
||||
Task<byte[]> Read(string key);
|
||||
void Delete(string key);
|
||||
bool Exists(string key);
|
||||
}
|
||||
```
|
||||
|
||||
- ISaveManager 负责序列化和业务逻辑
|
||||
- ISaveStorage 负责底层读写,可替换:File / PlayerPrefs / 云存档 / 自定义
|
||||
- 支持多存档位
|
||||
|
||||
### GameCore.Network — 网络层
|
||||
|
||||
```csharp
|
||||
public interface INetworkService
|
||||
{
|
||||
Task<T> Request<T>(string url, object body);
|
||||
Task Connect(string url);
|
||||
void Disconnect();
|
||||
void Send<T>(T message);
|
||||
void OnReceive<T>(Action<T> handler);
|
||||
}
|
||||
```
|
||||
|
||||
- HTTP 请求封装(GET/POST)
|
||||
- WebSocket 长连接
|
||||
- 断线重连、超时处理、心跳机制
|
||||
|
||||
---
|
||||
|
||||
## 包结构
|
||||
|
||||
```
|
||||
com.yourname.gamecore/
|
||||
├── package.json
|
||||
├── README.md
|
||||
├── CHANGELOG.md
|
||||
├── LICENSE
|
||||
├── Runtime/
|
||||
│ ├── GameCore/ # 核心 asmdef
|
||||
│ │ ├── DI/
|
||||
│ │ ├── Events/IEventBus.cs
|
||||
│ │ ├── Timer/ITimerManager.cs
|
||||
│ │ ├── Pool/
|
||||
│ │ ├── Lifecycle/
|
||||
│ │ └── Interfaces/
|
||||
│ │
|
||||
│ ├── GameCore.Defaults/ # 默认 EventBus + Timer
|
||||
│ ├── GameCore.R3/ # R3 实现(可选)
|
||||
│ │
|
||||
│ ├── GameCore.UI/
|
||||
│ ├── GameCore.Audio/
|
||||
│ │
|
||||
│ ├── GameCore.Resource/ # 接口
|
||||
│ ├── GameCore.Resource.Resources/ # Resources 实现
|
||||
│ ├── GameCore.Resource.Addressables/ # Addressables 实现
|
||||
│ │
|
||||
│ ├── GameCore.Data/
|
||||
│ │
|
||||
│ ├── GameCore.Save/ # 接口 + 序列化
|
||||
│ ├── GameCore.Save.FileStorage/
|
||||
│ ├── GameCore.Save.PlayerPrefsStorage/
|
||||
│ │
|
||||
│ └── GameCore.Network/
|
||||
│
|
||||
├── Editor/
|
||||
│ └── GameCore.Editor/
|
||||
└── Samples~/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整使用示例
|
||||
|
||||
```csharp
|
||||
// 入口
|
||||
public class GameBootstrap : MonoBehaviour
|
||||
{
|
||||
void Awake()
|
||||
{
|
||||
GameCore.Launch(new StartupConfig
|
||||
{
|
||||
Installers = new IInstaller[]
|
||||
{
|
||||
new CoreInstaller(),
|
||||
new TimerInstaller(),
|
||||
new ResourceInstaller<AddressablesResourceLoader>(),
|
||||
new AudioInstaller(),
|
||||
new UIInstaller(),
|
||||
new DataInstaller(),
|
||||
new SaveInstaller<FileSaveStorage>(),
|
||||
},
|
||||
StartupTasks = new IStartupTask[]
|
||||
{
|
||||
new LoadConfigTask(),
|
||||
new ShowMainMenuTask(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Update() => GameCore.Tick();
|
||||
void OnDestroy() => GameCore.Shutdown();
|
||||
}
|
||||
|
||||
// 使用模块
|
||||
public class MainMenuPanel : UIPanelBase
|
||||
{
|
||||
[Inject] private IAudioManager _audio;
|
||||
[Inject] private IEventBus _eventBus;
|
||||
[Inject] private IDataManager _data;
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
_audio.PlayBGM("main_menu", fadeIn: 1f);
|
||||
var weapons = _data.GetConfigs<WeaponConfig>();
|
||||
RenderWeaponList(weapons);
|
||||
}
|
||||
|
||||
public void OnStartGameClick()
|
||||
{
|
||||
_audio.PlaySFX("click");
|
||||
_eventBus.Publish(new StartGameEvent { Level = 1 });
|
||||
}
|
||||
}
|
||||
|
||||
// 事件定义
|
||||
public struct StartGameEvent : IEvent
|
||||
{
|
||||
public int Level;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user