文档记录

This commit is contained in:
JSD\13999
2026-05-15 15:38:40 +08:00
parent d7b09891dc
commit 019703d2a8
18 changed files with 4221 additions and 1 deletions

View File

@@ -0,0 +1,698 @@
# FlowScope Game Core P0 需求集
## 文档目的
本文档定义 FlowScope Game Core 的 P0 范围。P0 的目标不是做一个覆盖所有未来项目的完整框架,而是交付一套能支撑真实休闲/超休闲 Unity 游戏最小纵向切片的 Core
1. Unity 启动后进入 GameBootstrap。
2. 注册核心服务。
3. 加载 JSON 配置。
4. 加载或创建本地存档数据。
5. 进入初始 Feature。
6. 打开一个 MVVM Panel。
7. ViewModel/Data 通过 R3 驱动 UI 刷新。
8. 切换或关闭 Feature 时释放资源、订阅和作用域。
9. 退出时保存必要数据。
P0 允许包含部分开发体验能力,但这些能力必须服务于最小闭环,不能把 P0 扩张成完整包生态。
---
## P0 范围原则
1. 显式接口优先,业务模型不由 Core 暗猜。
2. Core 不包含具体玩法规则、具体 UI 文案、具体数值或业务 Data 类。
3. 默认实现只保留一条主路径,扩展实现通过接口或独立包接入。
4. 生命周期、资源、订阅、作用域必须有明确所有者。
5. 所有异步流程必须定义失败、取消和重复调用行为。
6. P0 文档中的每个模块都必须能通过单元测试或最小 Sample 验收。
---
## 统一架构决策
### 1. DI 自动注入进入 P0
P0 支持三种 DI 注册方式:
| 模式 | P0 定位 | 说明 |
|------|---------|------|
| 显式工厂注册 | 必须支持 | 最小稳定路径,所有项目都可用 |
| Attribute + Source Generator | P0 推荐路径 | 提供自动构造函数注入、RegisterAssembly、编译期检查 |
| Attribute + 运行时反射 | P0 可选降级路径 | 用于开发期、Generator 不可用场景、快速验证 |
约束:
- Container 的核心运行时只关心工厂函数,不直接依赖 Source Generator 或反射实现。
- Source Generator 和 Reflection 必须放在独立程序集或独立适配层中。
- 没有开启自动注入模式时,`RegisterType` / `RegisterAssembly` 必须给出明确错误,引导使用 `RegisterFactory`
- 自动递归解析依赖链必须检测循环依赖,并输出完整依赖路径。
- Source Generator 的编译期检查作为 P0 推荐能力,但不能阻塞显式工厂模式使用。
### 2. UI 层级改为可配置
P0 不再强制固定 `Bottom / Middle / Top` 三层。UIManager 支持运行时注册层级:
```csharp
RegisterLayer(string name, Canvas canvas, int sortOrder, PanelStrategy strategy);
```
P0 仍需提供一个推荐默认预设,方便最小项目快速接入:
```text
background / hud / popup / dialog / system
```
项目可以只注册其中一部分,也可以自定义层级名。
### 3. UI 栈式面板进入 P0但只做层内 LIFO
P0 支持同一层级内的栈式管理:
- 后打开的 Panel 位于同层级顶部。
- 默认只能关闭栈顶 Panel。
- `CloseLayer(layerName)` 关闭指定层。
- `CloseAll()` 关闭全部层级。
P0 暂不做完整导航路由、跨 Feature 页面恢复、URL/路由式导航、复杂返回策略。这些放到 P1/P2。
### 4. 配置系统 P0 只支持 JSON
P0 只交付 JSON 配置读取:
- `LoadAllAsync()`
- `Get<T>(int id)`
- `GetAll<T>()`
- 配置行实现 `IConfigRow`
CSV、Luban、ScriptableObject、远程配置、热重载、字段校验工具不进入 P0。后续通过 `IConfigSource` / `IConfigParser` 扩展包接入,而不是塞进核心实现。
### 5. 存档系统 P0 只保留本地轻量实现
P0 提供:
- `SaveAsync<T>(string key, T data)`
- `LoadAsync<T>(string key, T defaultValue)`
- `Delete(string key)`
默认实现二选一:
- `FileSaveStorage`
- `PlayerPrefsSaveStorage`
`Exists` 可作为轻量辅助进入 P0`ListKeys`、云存档、加密、压缩、版本迁移、冲突解决不进入 P0。后续通过 `ISaveStorage` / `ISaveSerializer` 扩展。
### 6. 音频系统 P0 保留基础播放,不做完整音频框架
P0 提供:
- 播放/停止 BGM
- 播放 SFX
- 设置 BGM/SFX 音量
- 全局静音
- 简单 SFX AudioSource 池
- BGM 淡入淡出
`IAudioHandle` 可进入 P0但只作为播放结果的轻量控制句柄
- `Stop(float fadeOut = 0)`
- `IsPlaying`
单个音频暂停/恢复、单个音量、AudioMixer 路由、3D 音频、语音系统、动态音乐、音频配置表放到 P1/P2。
### 7. 资源系统 P0 支持引用计数,但只支持一个默认后端
P0 支持:
- `LoadAsync<T>(string key)`
- `IResourceHandle<T>`
- `IResourceGroup`
- 同 key 并发加载合并
- 引用计数
- Group 释放
P0 默认后端只支持 Addressables。
Resources / AssetBundle / YooAsset / 远程下载进度 / 资源分析工具不进入 P0。后续通过 `IResourceBackend` 插件化接入。
---
## 统一生命周期所有权
### GameFlow / AppFlow 负责
GameFlow 是全局生命周期编排者,负责:
- 创建 Feature 实例。
- 创建 Feature Scope。
- 创建 FeatureContext。
- 调用 Feature 生命周期。
- 在切换和关闭时保证释放顺序。
- 处理失败、取消和重复调用。
### Feature 负责
Feature 只负责自身业务行为:
-`LoadAsync` 中加载自身资源、创建 ViewModel、准备运行时依赖。
-`EnterAsync` 中打开 UI、订阅数据或事件流。
-`ExitAsync` 中关闭 UI、取消业务订阅。
-`Dispose` 中清空自身引用。
Feature 不直接创建根 scope。Feature 可以使用 GameFlow 提供的 feature scope 注册临时服务。
### FeatureContext
```csharp
public sealed class FeatureContext
{
public Container Scope { get; }
public IResourceGroup Resources { get; }
public CompositeDisposable Disposables { get; }
public CancellationToken CancellationToken { get; }
}
```
Feature 生命周期接口:
```csharp
public interface IFeature : IDisposable
{
Task LoadAsync(FeatureContext context);
Task EnterAsync(FeatureContext context);
Task ExitAsync(FeatureContext context);
}
```
---
## 统一异步策略
P0 接口统一使用 `Task``CancellationToken`
规则:
- `StartupAsync``SwitchToAsync``ShutdownAsync` 必须接受 `CancellationToken`
- Feature 生命周期通过 `FeatureContext.CancellationToken` 获取取消信号。
- 切换期间再次调用 `SwitchToAsync``InvalidOperationException`P0 不做排队。
- `ShutdownAsync``Starting``Switching` 中调用时P0 抛 `InvalidOperationException`P1 可考虑中断式关闭。
- `ShutdownAsync` 一旦进入关闭流程,必须 best-effort 执行到底。
失败处理:
| 场景 | P0 行为 |
|------|---------|
| Startup 配置加载失败 | 回到 Idle抛异常 |
| Startup 存档加载失败 | 使用默认数据并记录警告 |
| 初始 Feature Load 失败 | 清理已创建资源,回到 Idle抛异常 |
| Switch 旧 Feature Exit 失败 | 记录错误,继续 Dispose |
| Switch 旧 Feature Dispose 失败 | 记录错误,继续创建新 Feature |
| Switch 新 Feature Load 失败 | 清理新 Feature进入 NoActiveFeature 状态,抛异常 |
| Switch 新 Feature Enter 失败 | 调用新 Feature Dispose进入 NoActiveFeature 状态,抛异常 |
| Shutdown 保存失败 | 记录错误,不阻塞退出 |
| 取消发生 | 抛 `OperationCanceledException`,并清理已创建资源 |
---
## P0 模块需求
### P0-1 Container
目标:提供轻量 DI 容器,支持显式注册、自动注入、作用域和释放。
P0 必须支持:
- `RegisterInstance<T>(T instance)`
- `RegisterFactory<T>(Func<Container, T> factory)`
- `RegisterType<TInterface, TImplementation>()`
- `RegisterType<TImplementation>()`
- `RegisterAssembly()`
- `Resolve<T>()`
- `TryResolve<T>(out T value)`
- `CreateScope()`
- `Dispose()`
自动注入规则:
- 只允许一个 public 构造函数。
- 构造函数参数从 Container 递归解析。
- 循环依赖必须检测。
- Source Generator 模式提供编译期依赖检查。
- Reflection 模式提供运行时降级。
暂不做:
- `[Inject]` 属性/方法注入。
- 多生命周期模式。
- 开放泛型注册。
- 装饰器注册。
- 运行时动态切换 DI 模式。
验收:
- 显式工厂注册可解析。
- Source Generator 自动注入可解析。
- Reflection 自动注入可解析。
- 子 scope 可访问父 scope。
- 子 scope 覆盖父 scope 不污染父 scope。
- Dispose 按逆序释放本 scope 创建的 `IDisposable`
- 循环依赖抛出包含依赖链的异常。
### P0-2 GameFlow
目标编排游戏启动、Feature 切换和关闭。
状态:
```text
Idle -> Starting -> Running <-> Switching -> ShuttingDown -> Disposed
```
接口:
```csharp
Task StartupAsync<TInitialFeature>(Container root, CancellationToken ct);
Task SwitchToAsync<TFeature>(CancellationToken ct);
Task ShutdownAsync(CancellationToken ct);
```
职责:
- 启动时加载配置、加载存档、注册 Data、进入初始 Feature。
- 切换时按 `Exit -> Dispose -> CreateScope -> Load -> Enter` 执行。
- 关闭时按 `Exit -> Dispose -> Save -> DisposeRootContainer` 执行。
- 创建并传递 `FeatureContext`
- 防止非法状态调用。
暂不做:
- Feature 栈。
- 并行 Feature。
- 可配置启动任务列表。
- Feature 预加载。
### P0-3 Feature
目标:定义业务特性的统一生命周期。
接口:
```csharp
public interface IFeature : IDisposable
{
Task LoadAsync(FeatureContext context);
Task EnterAsync(FeatureContext context);
Task ExitAsync(FeatureContext context);
}
```
规则:
- Feature 不依赖其他 Feature 的内部状态。
- 持久跨 Feature 状态放入 Data 类。
- 一次性过渡参数由目标 Feature 定义。
- 订阅必须进入 `FeatureContext.Disposables` 或 ViewModel 自己的 disposable 集合。
- 资源必须进入 `FeatureContext.Resources` 或由明确 handle 管理。
暂不做:
- Feature 间直接通信。
- Feature 热重载。
- Feature 过渡动画。
- 多 Feature 同时运行。
### P0-4 R3 + Data
目标:建立运行时数据和响应式绑定基础。
规则:
- 一个 Data 类对应一个业务域。
- Data 类是纯 C#,不依赖 Unity API。
- Data 类不引用 View / ViewModel。
- Data 类使用 `ReactiveProperty<T>``ReactiveCollection<T>` 表达可观察状态。
- ViewModel 负责业务表现逻辑,可以修改 Data。
- ViewModel 订阅必须释放。
P0 支持:
- `ReactiveProperty<T>` 序列化为 `.Value`
- `ReactiveCollection<T>` 序列化为数组。
- 存档加载后恢复到 Data 实例。
暂不做:
- 自研响应式系统。
- UI 自动绑定。
- 全局 EventBus。
- Data Inspector 可视化。
### P0-5 ConfigProvider
目标:提供 JSON 配置读取。
接口:
```csharp
public interface IConfigProvider
{
Task LoadAllAsync(CancellationToken ct);
T Get<T>(int id) where T : class, IConfigRow;
IReadOnlyList<T> GetAll<T>() where T : class, IConfigRow;
}
public interface IConfigRow
{
int Id { get; }
}
```
规则:
- P0 默认 JSON。
- 查询不存在 ID 返回 null。
- 重复 ID 抛异常。
- 配置格式错误抛异常并包含文件名。
暂不做:
- CSV / Luban / ScriptableObject。
- 按模块加载和卸载。
- 热重载。
- 配置编辑器校验。
- 远程配置。
扩展方向:
- P1/P2 通过 `IConfigParser``IConfigSource` 插件化接入,不修改业务调用。
### P0-6 SaveService
目标:提供本地轻量存档。
接口:
```csharp
public interface ISaveService
{
Task SaveAsync<T>(string key, T data, CancellationToken ct);
Task<T> LoadAsync<T>(string key, T defaultValue, CancellationToken ct);
void Delete(string key);
bool Exists(string key);
}
```
默认实现:
- `FileSaveStorage``PlayerPrefsSaveStorage` 二选一。
- 默认序列化为 JSON。
规则:
- 不存在 key 返回 defaultValue。
- 反序列化失败返回 defaultValue 并记录警告。
- 写入失败抛异常。
- 删除不存在 key 静默忽略。
暂不做:
- `ListKeys`
- 多存档槽管理。
- 云存档。
- 加密。
- 压缩。
- 版本迁移。
- 自动存档。
扩展方向:
- P1/P2 通过 `ISaveStorage` / `ISaveSerializer` 扩展。
### P0-7 ResourceService
目标:提供资源异步加载、引用计数和分组释放。
接口:
```csharp
public interface IResourceService
{
Task<IResourceHandle<T>> LoadAsync<T>(string key, CancellationToken ct) where T : class;
IResourceGroup CreateGroup();
}
public interface IResourceHandle<T> : IDisposable where T : class
{
string Key { get; }
T Asset { get; }
bool IsDisposed { get; }
}
public interface IResourceGroup : IDisposable
{
void Add<T>(IResourceHandle<T> handle) where T : class;
int Count { get; }
}
```
规则:
- 同 key 并发加载只触发一次底层加载。
- 每次加载返回独立 handle。
- 最后一个 handle Dispose 时释放底层资源。
- Group Dispose 释放组内所有 handle。
- 重复 Dispose 不抛异常。
- Dispose 后访问 Asset 返回 null 或抛明确异常,二选一需在实现前固定。
P0 默认:
- Addressables 后端。
暂不做:
- Resources 后端。
- AssetBundle 后端。
- YooAsset 后端。
- 下载进度。
- 资源预热。
- 资源分析工具。
扩展方向:
- P1/P2 通过 `IResourceBackend` 插件化接入其他资源系统。
### P0-8 UIManager
目标:提供 MVVM Panel 生命周期管理、可配置层级和层内栈。
接口:
```csharp
public sealed class UIManager
{
void RegisterLayer(string name, Canvas canvas, int sortOrder, PanelStrategy strategy);
Task<TPanel> OpenAsync<TPanel, TViewModel>(TViewModel vm, CancellationToken ct)
where TPanel : UIPanelBase<TViewModel>;
void Close<TPanel>();
void CloseLayer(string layerName);
void CloseAll();
bool IsOpen<TPanel>();
}
```
Panel Attribute
```csharp
[AttributeUsage(AttributeTargets.Class)]
public sealed class UIPanelAttribute : Attribute
{
public string Layer { get; }
public string Path { get; }
public PanelStrategy? OverrideStrategy { get; }
public UIPanelAttribute(
string layer,
string path = null,
PanelStrategy? overrideStrategy = null)
{
Layer = layer;
Path = path;
OverrideStrategy = overrideStrategy;
}
}
```
Panel 基类:
```csharp
public abstract class UIPanelBase<TViewModel> : MonoBehaviour
{
protected TViewModel ViewModel { get; private set; }
protected CompositeDisposable Disposables { get; } = new();
public void Bind(TViewModel viewModel)
{
ViewModel = viewModel;
OnBind(viewModel);
}
public virtual void Unbind()
{
Disposables.Clear();
}
protected abstract void OnBind(TViewModel viewModel);
}
```
规则:
- Panel 必须声明 `UIPanelAttribute`
- Attribute 未指定 path 时,按命名约定推导资源路径。
- 同层级采用 LIFO 栈。
- 关闭非栈顶 Panel 抛异常。
- Destroy 策略关闭时释放资源 handle。
- Cache 策略关闭时隐藏对象,复用时重新 Bind。
暂不做:
- 自动 UI 绑定。
- 导航路由系统。
- Panel 预加载。
- SafeArea。
- 多语言切换。
- 红点系统。
- 复杂转场动画。
### P0-9 AudioService
目标:提供基础音频播放能力。
接口:
```csharp
public interface IAudioService
{
IAudioHandle PlayBgm(string key, bool loop = true, float fadeIn = 0f);
IAudioHandle PlaySfx(string key);
void StopBgm(float fadeOut = 0f);
void SetBgmVolume(float volume);
void SetSfxVolume(float volume);
void SetMute(bool mute);
}
public interface IAudioHandle
{
void Stop(float fadeOut = 0f);
bool IsPlaying { get; }
}
```
规则:
- 同一时间只有一首 BGM。
- 播放新 BGM 自动停止旧 BGM。
- SFX 使用简单 AudioSource 池。
- 音量参数 clamp 到 `[0, 1]`
- 资源通过 `IResourceService` 加载。
暂不做:
- 单个音频 Pause/Resume。
- 单个音频 Volume。
- AudioMixer 分组。
- 3D 音频。
- 动态音乐。
- 音频配置表。
---
## P0 最小 Sample 验收
P0 完成时必须提供一个最小 Sample
```text
GameBootstrap
-> 创建根 Container
-> 注册 Container / Config / Save / Resource / UI / Audio
-> 注册 PlayerData
-> GameFlow.StartupAsync<MainMenuFeature>()
MainMenuFeature
-> LoadAsync: 加载 MainMenuPanel 资源,创建 MainMenuViewModel
-> EnterAsync: UIManager.OpenAsync<MainMenuPanel, MainMenuViewModel>()
-> 点击按钮修改 PlayerData.Gold
-> UI 文本响应刷新
-> ShutdownAsync 保存 PlayerData
```
验收标准:
- Sample 可在 Unity 中运行。
- 首次启动创建默认 PlayerData。
- 修改 Gold 后 UI 立即刷新。
- 退出后保存 PlayerData。
- 重新启动后恢复 PlayerData。
- Feature 退出后资源组释放。
- Feature 退出后订阅不再触发。
- 连续打开/关闭 Panel 不残留订阅。
---
## P0 测试矩阵
| 模块 | 必测内容 |
|------|----------|
| Container | 显式工厂、自动注入、scope 覆盖、Dispose、循环依赖 |
| GameFlow | 启动顺序、切换顺序、失败清理、非法状态调用、关闭 best-effort |
| Feature | Load/Enter/Exit/Dispose 顺序、资源和订阅释放 |
| Data/R3 | ReactiveProperty 同步、序列化/反序列化、订阅释放 |
| Config | JSON 解析、重复 Id、不存在 Id、格式错误 |
| Save | 保存/加载、默认值、删除、反序列化失败 |
| Resource | 同 key 并发、引用计数、Group 释放、加载失败 |
| UI | 层级注册、Open/Close、LIFO、Bind/Unbind、Cache/Destroy |
| Audio | BGM 播放/停止、SFX 池、音量 clamp、静音、资源加载失败 |
---
## P1/P2 扩展方向
后续能力优先做成可插拔扩展,而不是污染 P0 核心:
| 能力 | 建议阶段 | 形式 |
|------|----------|------|
| CSV / Luban 配置 | P1/P2 | `IConfigParser` 插件 |
| 按模块配置加载 | P1 | `IConfigSource` 扩展 |
| 存档版本迁移 | P1/P2 | `ISaveMigration` 插件 |
| 云存档 | P2 | `ISaveStorage` 插件 |
| Resources / YooAsset / AssetBundle | P1/P2 | `IResourceBackend` 插件 |
| UI 路由 / 返回栈策略 | P1 | `IUIScreenNavigator` 扩展 |
| Panel 预加载 | P1 | UIManager 扩展服务 |
| AudioMixer / 3D 音频 | P1/P2 | `IAudioBackend` 或 AudioService 扩展 |
| 编辑器导入向导 | P2 | Editor 包 |
| 配置/资源检查工具 | P2 | Editor 工具包 |
---
## 需要同步修正的旧文档
以下旧文档需要按本文档重新收敛:
- `docs/game-core-requirement-tiers.md`
- `docs/requirements/p0-container.md`
- `docs/requirements/p0-gameflow.md`
- `docs/requirements/p0-feature.md`
- `docs/requirements/p0-configprovider.md`
- `docs/requirements/p0-saveservice.md`
- `docs/requirements/p0-resourceservice.md`
- `docs/requirements/p0-uimanager.md`
- `docs/requirements/p0-audioservice.md`
其中主文档必须更新的决策:
- DI 自动注入进入 P0。
- UI 层级从固定三层改为可配置层级。
- UI 层内 LIFO 栈进入 P0。
- JSON 是 P0 唯一默认配置格式。
- 资源 P0 只保留 Addressables 默认后端,其他后端插件化。
- 存档 P0 只保留本地轻量实现,云存档插件化。