# 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); public UIPanelAttribute(string layer, string path); public UIPanelAttribute(string layer, PanelStrategy overrideStrategy); public UIPanelAttribute(string layer, string path, PanelStrategy 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 时,按命名约定推导资源路径。 - C# Attribute 构造参数不支持 `Nullable`,因此 `OverrideStrategy` 属性保持 nullable,但构造函数通过重载表达“未指定”和“指定覆盖策略”。 - 同层级采用 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 核心: P1 生产化承接边界:P1 可以新增小接口、适配层和样例验收能力,但不得重写 P0 的核心生命周期、容器、UI 层内 LIFO、JSON 默认配置、本地存档和资源所有权语义。任何涉及 Addressables、配置来源、存档迁移、UI 导航或预加载的能力,都应先通过 P1 契约进入,再由独立实现或适配程序集承接。 | 能力 | 建议阶段 | 形式 | |------|----------|------| | 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 只保留本地轻量实现,云存档插件化。