From 019703d2a8befcf95e4d08c168ac96eb503371f9 Mon Sep 17 00:00:00 2001 From: "JSD\\13999" <1399945104@qq.com> Date: Fri, 15 May 2026 15:38:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - docs/game-core-requirement-tiers.md | 141 ++++ docs/game-core-requirements.md | 487 ++++++++++++ docs/guide/unity-package-git-workflow.md | 239 ++++++ docs/requirements/p0-audioservice.md | 95 +++ docs/requirements/p0-configprovider.md | 99 +++ docs/requirements/p0-container.md | 129 ++++ docs/requirements/p0-data-r3.md | 88 +++ docs/requirements/p0-feature.md | 128 ++++ docs/requirements/p0-gameflow.md | 156 ++++ docs/requirements/p0-requirements-set.md | 698 ++++++++++++++++++ docs/requirements/p0-resourceservice.md | 104 +++ docs/requirements/p0-saveservice.md | 96 +++ docs/requirements/p0-uimanager.md | 170 +++++ ...6-05-15-p0-parallel-implementation-plan.md | 603 +++++++++++++++ .../specs/2026-05-14-game-core-design.md | 447 +++++++++++ .../2026-05-14-game-core-requirement-tiers.md | 89 +++ .../specs/2026-05-14-gamecore-design.md | 452 ++++++++++++ 18 files changed, 4221 insertions(+), 1 deletion(-) create mode 100644 docs/game-core-requirement-tiers.md create mode 100644 docs/game-core-requirements.md create mode 100644 docs/guide/unity-package-git-workflow.md create mode 100644 docs/requirements/p0-audioservice.md create mode 100644 docs/requirements/p0-configprovider.md create mode 100644 docs/requirements/p0-container.md create mode 100644 docs/requirements/p0-data-r3.md create mode 100644 docs/requirements/p0-feature.md create mode 100644 docs/requirements/p0-gameflow.md create mode 100644 docs/requirements/p0-requirements-set.md create mode 100644 docs/requirements/p0-resourceservice.md create mode 100644 docs/requirements/p0-saveservice.md create mode 100644 docs/requirements/p0-uimanager.md create mode 100644 docs/superpowers/plans/2026-05-15-p0-parallel-implementation-plan.md create mode 100644 docs/superpowers/specs/2026-05-14-game-core-design.md create mode 100644 docs/superpowers/specs/2026-05-14-game-core-requirement-tiers.md create mode 100644 docs/superpowers/specs/2026-05-14-gamecore-design.md diff --git a/.gitignore b/.gitignore index 2e5bc90..2bd063f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,3 @@ AGENTS.md QWEN.md Client/.claude/ dev-ab-svr/debugconfig.json -doc/ \ No newline at end of file diff --git a/docs/game-core-requirement-tiers.md b/docs/game-core-requirement-tiers.md new file mode 100644 index 0000000..fdccf28 --- /dev/null +++ b/docs/game-core-requirement-tiers.md @@ -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 验证。 diff --git a/docs/game-core-requirements.md b/docs/game-core-requirements.md new file mode 100644 index 0000000..bcdb90e --- /dev/null +++ b/docs/game-core-requirements.md @@ -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(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(assetPath) → Task` | 异步加载资源 | +| `LoadSync(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 | 一次逻辑帧更新调用 | diff --git a/docs/guide/unity-package-git-workflow.md b/docs/guide/unity-package-git-workflow.md new file mode 100644 index 0000000..6a5a2cc --- /dev/null +++ b/docs/guide/unity-package-git-workflow.md @@ -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` 声明模块间依赖关系。 diff --git a/docs/requirements/p0-audioservice.md b/docs/requirements/p0-audioservice.md new file mode 100644 index 0000000..37fbeee --- /dev/null +++ b/docs/requirements/p0-audioservice.md @@ -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 +``` diff --git a/docs/requirements/p0-configprovider.md b/docs/requirements/p0-configprovider.md new file mode 100644 index 0000000..00a250f --- /dev/null +++ b/docs/requirements/p0-configprovider.md @@ -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(int id) where T : class, IConfigRow; + IReadOnlyList GetAll() where T : class, IConfigRow; +} + +public interface IConfigRow +{ + int Id { get; } +} +``` + +## 功能需求 + +- 启动时通过 `LoadAllAsync` 加载全部 JSON 配置。 +- 配置行必须实现 `IConfigRow`。 +- 查询使用泛型类型 + `Id`。 +- `Get(missingId)` 返回 null。 +- `GetAll()` 返回只读列表;未加载该类型时返回空列表。 +- 同一配置类型内重复 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 Parse(string content) where T : class, IConfigRow; +} + +public interface IConfigSource +{ + Task LoadTextAsync(string key, CancellationToken cancellationToken); +} +``` + +## 验收标准 + +| # | 标准 | 通过条件 | +|---|------|---------| +| 1 | JSON 加载 | `LoadAllAsync` 后配置可查询 | +| 2 | 按 Id 查询 | `Get(101)` 返回正确对象 | +| 3 | 不存在 Id | 返回 null,不抛异常 | +| 4 | 获取列表 | `GetAll()` 返回全部配置 | +| 5 | 重复 Id | 加载时抛异常并包含重复 Id | +| 6 | 格式错误 | 抛异常并包含文件名 | +| 7 | 取消加载 | CancellationToken 取消时抛 `OperationCanceledException` | + +## 依赖关系 + +```text +IConfigProvider +├── JSON 库 +├── 可选 IResourceService 或文件读取适配 +└── 纯 C# 配置行类型 +``` diff --git a/docs/requirements/p0-container.md b/docs/requirements/p0-container.md new file mode 100644 index 0000000..abdf23f --- /dev/null +++ b/docs/requirements/p0-container.md @@ -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 instance); + public void RegisterFactory(Func factory); + public T Resolve(); + public bool TryResolve(out T value); + public Container CreateScope(); + public void Dispose(); +} +``` + +规则: + +- `RegisterInstance` 注册外部实例,Container 不负责释放。 +- `RegisterFactory` 首次 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() + where TImplementation : TInterface; + + public void RegisterType(); + 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 +``` diff --git a/docs/requirements/p0-data-r3.md b/docs/requirements/p0-data-r3.md new file mode 100644 index 0000000..f9b6ce7 --- /dev/null +++ b/docs/requirements/p0-data-r3.md @@ -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 Gold { get; } = new(0); + public ReactiveProperty Level { get; } = new(1); +} +``` + +规则: + +- 一个 Data 类对应一个业务域。 +- Data 类注册在全局 Container 中。 +- Data 类保持纯 C#,不依赖 Unity API。 +- Data 类不持有 View 或 ViewModel 引用。 +- Data 类不包含复杂业务流程逻辑。 +- Data 类之间不互相直接引用。 +- Data 类使用 `ReactiveProperty` 和 `ReactiveCollection` 表达可观察状态。 + +## ViewModel 规则 + +- ViewModel 可以引用 Data。 +- ViewModel 负责 UI 表现逻辑和用户操作逻辑。 +- ViewModel 不持有 MonoBehaviour 或具体 View 引用。 +- ViewModel 订阅必须加入自己的 `CompositeDisposable`,或加入 `FeatureContext.Disposables`。 + +## 序列化规则 + +- `ReactiveProperty` 序列化为 `.Value`。 +- `ReactiveCollection` 序列化为 JSON 数组。 +- 反序列化到已有 Data 实例时,更新 `.Value` 或集合内容,不替换 Data 实例。 +- 反序列化类型不匹配时抛异常并包含字段名。 + +## 事件流规则 + +P0 允许使用 R3 `Subject` 做 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 +``` diff --git a/docs/requirements/p0-feature.md b/docs/requirements/p0-feature.md new file mode 100644 index 0000000..3630434 --- /dev/null +++ b/docs/requirements/p0-feature.md @@ -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 +``` diff --git a/docs/requirements/p0-gameflow.md b/docs/requirements/p0-gameflow.md new file mode 100644 index 0000000..a9dada1 --- /dev/null +++ b/docs/requirements/p0-gameflow.md @@ -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( + Container root, + CancellationToken cancellationToken) + where TInitialFeature : IFeature; + + public Task SwitchToAsync( + 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 +``` diff --git a/docs/requirements/p0-requirements-set.md b/docs/requirements/p0-requirements-set.md new file mode 100644 index 0000000..0f74efa --- /dev/null +++ b/docs/requirements/p0-requirements-set.md @@ -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(int id)` +- `GetAll()` +- 配置行实现 `IConfigRow` + +CSV、Luban、ScriptableObject、远程配置、热重载、字段校验工具不进入 P0。后续通过 `IConfigSource` / `IConfigParser` 扩展包接入,而不是塞进核心实现。 + +### 5. 存档系统 P0 只保留本地轻量实现 + +P0 提供: + +- `SaveAsync(string key, T data)` +- `LoadAsync(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(string key)` +- `IResourceHandle` +- `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 instance)` +- `RegisterFactory(Func factory)` +- `RegisterType()` +- `RegisterType()` +- `RegisterAssembly()` +- `Resolve()` +- `TryResolve(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(Container root, CancellationToken ct); +Task SwitchToAsync(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` 和 `ReactiveCollection` 表达可观察状态。 +- ViewModel 负责业务表现逻辑,可以修改 Data。 +- ViewModel 订阅必须释放。 + +P0 支持: + +- `ReactiveProperty` 序列化为 `.Value`。 +- `ReactiveCollection` 序列化为数组。 +- 存档加载后恢复到 Data 实例。 + +暂不做: + +- 自研响应式系统。 +- UI 自动绑定。 +- 全局 EventBus。 +- Data Inspector 可视化。 + +### P0-5 ConfigProvider + +目标:提供 JSON 配置读取。 + +接口: + +```csharp +public interface IConfigProvider +{ + Task LoadAllAsync(CancellationToken ct); + T Get(int id) where T : class, IConfigRow; + IReadOnlyList GetAll() 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(string key, T data, CancellationToken ct); + Task LoadAsync(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> LoadAsync(string key, CancellationToken ct) where T : class; + IResourceGroup CreateGroup(); +} + +public interface IResourceHandle : IDisposable where T : class +{ + string Key { get; } + T Asset { get; } + bool IsDisposed { get; } +} + +public interface IResourceGroup : IDisposable +{ + void Add(IResourceHandle 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 OpenAsync(TViewModel vm, CancellationToken ct) + where TPanel : UIPanelBase; + void Close(); + void CloseLayer(string layerName); + void CloseAll(); + bool IsOpen(); +} +``` + +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 : 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 + -> LoadAsync: 加载 MainMenuPanel 资源,创建 MainMenuViewModel + -> EnterAsync: UIManager.OpenAsync() + -> 点击按钮修改 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 只保留本地轻量实现,云存档插件化。 diff --git a/docs/requirements/p0-resourceservice.md b/docs/requirements/p0-resourceservice.md new file mode 100644 index 0000000..816475b --- /dev/null +++ b/docs/requirements/p0-resourceservice.md @@ -0,0 +1,104 @@ +# P0-7: IResourceService 需求详细文档 + +## 对齐说明 + +本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 资源系统包含引用计数和资源组,因为它直接影响 Feature 释放正确性;但 P0 只实现 Addressables 默认后端,其他后端通过插件扩展。 + +## 目标 + +提供异步资源加载、同 key 并发合并、引用计数和资源组释放能力。 + +## 接口 + +```csharp +public interface IResourceService +{ + Task> LoadAsync( + string key, + CancellationToken cancellationToken) + where T : class; + + IResourceGroup CreateGroup(); +} + +public interface IResourceHandle : IDisposable where T : class +{ + string Key { get; } + T Asset { get; } + bool IsDisposed { get; } +} + +public interface IResourceGroup : IDisposable +{ + void Add(IResourceHandle handle) where T : class; + int Count { get; } +} +``` + +## 行为规则 + +- `LoadAsync` 加载失败时抛异常,异常包含 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 LoadAsync(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 依赖 +``` diff --git a/docs/requirements/p0-saveservice.md b/docs/requirements/p0-saveservice.md new file mode 100644 index 0000000..695f4d8 --- /dev/null +++ b/docs/requirements/p0-saveservice.md @@ -0,0 +1,96 @@ +# P0-6: ISaveService 需求详细文档 + +## 对齐说明 + +本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 存档系统只提供本地轻量存取;云存档、加密、压缩、复杂迁移和冲突解决通过后续扩展接入。 + +## 目标 + +提供本地保存、读取、删除和存在性检查能力,支撑 P0 Sample 的 Data 持久化。 + +## 接口 + +```csharp +public interface ISaveService +{ + Task SaveAsync(string key, T data, CancellationToken cancellationToken); + Task LoadAsync(string key, T defaultValue, CancellationToken cancellationToken); + void Delete(string key); + bool Exists(string key); +} +``` + +## 默认实现 + +P0 默认实现二选一: + +| 实现 | 说明 | +|------|------| +| `FileSaveStorage` | 使用本地文件,适合普通存档 | +| `PlayerPrefsSaveStorage` | 使用 PlayerPrefs,适合小型数据 | + +P0 默认序列化为 JSON,并支持 R3 Data 中的 `ReactiveProperty` / `ReactiveCollection` 转换。 + +## 行为规则 + +- `LoadAsync` 读取不存在 key 时返回 `defaultValue`。 +- 反序列化失败返回 `defaultValue` 并记录警告。 +- 存储读取失败返回 `defaultValue` 并记录警告。 +- 写入失败抛异常,异常包含 key。 +- `Delete` 删除不存在 key 时静默忽略。 +- `Exists` 只检查当前默认存储后端。 + +## 暂不做 + +- `ListKeys`。 +- 多存档槽管理。 +- 自动存档。 +- 云存档完整实现。 +- 本地 + 云双写。 +- 加密。 +- 压缩。 +- 存档版本迁移。 +- 冲突解决。 + +## 扩展方向 + +后续通过以下接口扩展: + +```csharp +public interface ISaveStorage +{ + Task WriteAsync(string key, byte[] data, CancellationToken cancellationToken); + Task ReadAsync(string key, CancellationToken cancellationToken); + void Delete(string key); + bool Exists(string key); +} + +public interface ISaveSerializer +{ + byte[] Serialize(T data); + T Deserialize(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 +``` diff --git a/docs/requirements/p0-uimanager.md b/docs/requirements/p0-uimanager.md new file mode 100644 index 0000000..e82f446 --- /dev/null +++ b/docs/requirements/p0-uimanager.md @@ -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 : 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 OpenAsync( + TViewModel viewModel, + CancellationToken cancellationToken) + where TPanel : UIPanelBase; + + public void Close(); + public void CloseLayer(string layerName); + public void CloseAll(); + public bool IsOpen(); +} +``` + +## 生命周期 + +```text +OpenAsync: + 1. 读取 UIPanelAttribute + 2. 校验层级已注册 + 3. 解析资源路径 + 4. IResourceService.LoadAsync + 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 +``` diff --git a/docs/superpowers/plans/2026-05-15-p0-parallel-implementation-plan.md b/docs/superpowers/plans/2026-05-15-p0-parallel-implementation-plan.md new file mode 100644 index 0000000..74a4a87 --- /dev/null +++ b/docs/superpowers/plans/2026-05-15-p0-parallel-implementation-plan.md @@ -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 instance) => throw new NotImplementedException(); + public void RegisterFactory(Func factory) => throw new NotImplementedException(); + public void RegisterType() where TImplementation : TInterface => throw new NotImplementedException(); + public void RegisterType() => throw new NotImplementedException(); + public void RegisterAssembly() => throw new NotImplementedException(); + public T Resolve() => throw new NotImplementedException(); + public bool TryResolve(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` 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`, and `GetAll` 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.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`** + +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 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`. + +- [ ] **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. diff --git a/docs/superpowers/specs/2026-05-14-game-core-design.md b/docs/superpowers/specs/2026-05-14-game-core-design.md new file mode 100644 index 0000000..5ea0cc1 --- /dev/null +++ b/docs/superpowers/specs/2026-05-14-game-core-design.md @@ -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 instance); + void RegisterFactory(Func factory); + T Resolve(); + bool TryResolve(out T value); + Container CreateScope(); // 子 scope 可访问父级,可覆盖父级 + void Dispose(); // 释放 scope 内的 IDisposable +} +``` + +注册方式: + +```csharp +container.RegisterInstance(config); +container.RegisterFactory(() => new FileSaveService()); +``` + +### R3 + +直接使用 R3 库提供: + +- ReactiveProperty — 可观察属性,MVVM 绑定基石 +- ReactiveCollection — 可观察列表 +- Subject — 事件流 +- Observable.Timer / EveryUpdate — 计时和帧调度 +- CompositeDisposable — 批量释放订阅 + +--- + +## 核心服务层 + +### IResourceService — 资源管理 + +```csharp +public interface IResourceService +{ + Task> LoadAsync(string key) where T : class; + IResourceGroup CreateGroup(); +} + +public interface IResourceHandle : IDisposable +{ + T Asset { get; } +} + +public interface IResourceGroup : IDisposable +{ + void Add(IResourceHandle handle); +} +``` + +引用计数机制:同一 key 多次 Load 只加载一次,最后一个 handle Dispose 时才真正卸载。 + +默认实现:AddressablesResourceService。 + +### ISaveService — 持久化 + +```csharp +public interface ISaveService +{ + Task SaveAsync(string key, T data); + Task LoadAsync(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(int id) where T : IConfigRow; + IReadOnlyList GetAll() 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 SendAsync(string path, TRequest data); + void On(Action handler); +} +``` + +初期可能用不上,但留好接口。默认 HttpNetworkService。 + +### UIManager — UI 管理 + +MVVM 模式的 Panel 生命周期管理。 + +```csharp +public class UIManager +{ + Task OpenAsync(TViewModel viewModel) where TPanel : IPanel; + void Close(); + 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 Title { get; } = new("商店"); + public ReactiveCollection Items { get; } = new(); + + private readonly PlayerData _player; + private readonly IConfigProvider _config; + + public void BuyItem(int itemId) + { + var config = _config.Get(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 Level { get; } = new(1); + public ReactiveProperty Gold { get; } = new(0); +} + +// 不需要背包?就不建 InventoryData +// 需要设置?按需加 +public class SettingsData +{ + public ReactiveProperty BgmVolume { get; } = new(1f); + public ReactiveProperty 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("audio/bgm_shop"); + _resources.Add(bgm); + _viewModel = new ShopViewModel(_player, _config); + } + + public async Task EnterAsync() + { + _disposables = new CompositeDisposable(); + await _uiManager.OpenAsync(_viewModel); + } + + public async Task ExitAsync() + { + _uiManager.Close(); + _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(); + await config.LoadAllAsync(); + + var save = _container.Resolve(); + var player = await save.LoadAsync("player", new PlayerData()); + _container.RegisterInstance(player); + + await SwitchToAsync(); + } + + public async Task SwitchToAsync() 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(); + await save.SaveAsync("player", _container.Resolve()); + } +} +``` + +### 启动入口 + +```csharp +public class GameBootstrap : MonoBehaviour +{ + private void Awake() + { + var container = new Container(); + + // 核心服务 + container.RegisterInstance(new AddressablesResourceService()); + container.RegisterInstance(new FileSaveService()); + container.RegisterInstance(new JsonConfigProvider()); + container.RegisterInstance(new UnityAudioService()); + container.RegisterInstance(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 够用) diff --git a/docs/superpowers/specs/2026-05-14-game-core-requirement-tiers.md b/docs/superpowers/specs/2026-05-14-game-core-requirement-tiers.md new file mode 100644 index 0000000..720ae91 --- /dev/null +++ b/docs/superpowers/specs/2026-05-14-game-core-requirement-tiers.md @@ -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 自动绑定。 +- 大型项目资源预算和内存分析。 diff --git a/docs/superpowers/specs/2026-05-14-gamecore-design.md b/docs/superpowers/specs/2026-05-14-gamecore-design.md new file mode 100644 index 0000000..ff91d20 --- /dev/null +++ b/docs/superpowers/specs/2026-05-14-gamecore-design.md @@ -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().To()`) +- 实例绑定 (`ToValue(instance)`) +- 生命周期:`AsSingleton()` / `AsTransient()` +- 子容器(场景级作用域,子容器销毁时自动释放其单例) +- 构造函数注入(普通 C# 类) +- 属性注入 `[Inject]`(MonoBehaviour) +- 方法注入 `[Inject]`(初始化/PostConstruct) +- 自动递归解析依赖链 +- 反射实现,预留 Source Generator 优化接口 + +### 子容器 + +```csharp +// 全局容器(跨场景) +var root = new GameContainer(); + +// 场景容器(继承全局,场景销毁时一起释放) +var sceneContainer = root.CreateChild(); + +// 子容器可访问父容器绑定 +sceneContainer.Resolve(); // 从父容器拿 + +// 子容器同名绑定覆盖父容器 +sceneContainer.Bind().To().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(Action handler) where T : IEvent; + void Unsubscribe(Action handler) where T : IEvent; + void Publish(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 CreatePool(Func factory, int prewarm = 0); + IGameObjectPool CreateGameObjectPool(string prefabPath, int prewarm = 0); +} + +public interface IPool +{ + 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(string path) where T : Object; + Task LoadAsync(string address) where T : Object; + void Release(T asset); + void ReleaseAll(); +} +``` + +- 统一封装,支持引用计数,同一资源多次加载不重复 +- 存储实现可替换:Resources / Addressables / AssetBundle / 自定义 + +### GameCore.UI — UI 管理 + +```csharp +public interface IUIManager +{ + T Open(string layer = null) where T : UIPanelBase; + void Close() 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(string format) where TParser : IDataParser; + void LoadModule(string moduleName); + void UnloadModule(string moduleName); + bool IsModuleLoaded(string moduleName); + T GetConfig(string key) where T : IConfig; + IReadOnlyList GetConfigs() where T : IConfig; +} + +public interface IDataParser +{ + IEnumerable Parse(string content); +} +``` + +- 格式自定义:注册 IDataParser 实现(JSON/CSV/ScriptableObject/自定义) +- 分模块按需加载和卸载 + +### GameCore.Save — 存档系统 + +```csharp +public interface ISaveManager +{ + Task Save(string slotName, object data); + Task Load(string slotName); + void Delete(string slotName); + bool Exists(string slotName); + string[] ListSaves(); +} + +public interface ISaveStorage +{ + Task Write(string key, byte[] data); + Task Read(string key); + void Delete(string key); + bool Exists(string key); +} +``` + +- ISaveManager 负责序列化和业务逻辑 +- ISaveStorage 负责底层读写,可替换:File / PlayerPrefs / 云存档 / 自定义 +- 支持多存档位 + +### GameCore.Network — 网络层 + +```csharp +public interface INetworkService +{ + Task Request(string url, object body); + Task Connect(string url); + void Disconnect(); + void Send(T message); + void OnReceive(Action 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(), + new AudioInstaller(), + new UIInstaller(), + new DataInstaller(), + new SaveInstaller(), + }, + 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(); + RenderWeaponList(weapons); + } + + public void OnStartGameClick() + { + _audio.PlaySFX("click"); + _eventBus.Publish(new StartGameEvent { Level = 1 }); + } +} + +// 事件定义 +public struct StartGameEvent : IEvent +{ + public int Level; +} +```