19 KiB
FlowScope Game Core P0 需求集
文档目的
本文档定义 FlowScope Game Core 的 P0 范围。P0 的目标不是做一个覆盖所有未来项目的完整框架,而是交付一套能支撑真实休闲/超休闲 Unity 游戏最小纵向切片的 Core:
- Unity 启动后进入 GameBootstrap。
- 注册核心服务。
- 加载 JSON 配置。
- 加载或创建本地存档数据。
- 进入初始 Feature。
- 打开一个 MVVM Panel。
- ViewModel/Data 通过 R3 驱动 UI 刷新。
- 切换或关闭 Feature 时释放资源、订阅和作用域。
- 退出时保存必要数据。
P0 允许包含部分开发体验能力,但这些能力必须服务于最小闭环,不能把 P0 扩张成完整包生态。
P0 范围原则
- 显式接口优先,业务模型不由 Core 暗猜。
- Core 不包含具体玩法规则、具体 UI 文案、具体数值或业务 Data 类。
- 默认实现只保留一条主路径,扩展实现通过接口或独立包接入。
- 生命周期、资源、订阅、作用域必须有明确所有者。
- 所有异步流程必须定义失败、取消和重复调用行为。
- 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 支持运行时注册层级:
RegisterLayer(string name, Canvas canvas, int sortOrder, PanelStrategy strategy);
P0 仍需提供一个推荐默认预设,方便最小项目快速接入:
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)
默认实现二选一:
FileSaveStoragePlayerPrefsSaveStorage
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
public sealed class FeatureContext
{
public Container Scope { get; }
public IResourceGroup Resources { get; }
public CompositeDisposable Disposables { get; }
public CancellationToken CancellationToken { get; }
}
Feature 生命周期接口:
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 切换和关闭。
状态:
Idle -> Starting -> Running <-> Switching -> ShuttingDown -> Disposed
接口:
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
目标:定义业务特性的统一生命周期。
接口:
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 配置读取。
接口:
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
目标:提供本地轻量存档。
接口:
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
目标:提供资源异步加载、引用计数和分组释放。
接口:
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 生命周期管理、可配置层级和层内栈。
接口:
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:
[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 基类:
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 时,按命名约定推导资源路径。
- C# Attribute 构造参数不支持
Nullable<T>,因此OverrideStrategy属性保持 nullable,但构造函数通过重载表达“未指定”和“指定覆盖策略”。 - 同层级采用 LIFO 栈。
- 关闭非栈顶 Panel 抛异常。
- Destroy 策略关闭时释放资源 handle。
- Cache 策略关闭时隐藏对象,复用时重新 Bind。
暂不做:
- 自动 UI 绑定。
- 导航路由系统。
- Panel 预加载。
- SafeArea。
- 多语言切换。
- 红点系统。
- 复杂转场动画。
P0-9 AudioService
目标:提供基础音频播放能力。
接口:
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:
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.mddocs/requirements/p0-container.mddocs/requirements/p0-gameflow.mddocs/requirements/p0-feature.mddocs/requirements/p0-configprovider.mddocs/requirements/p0-saveservice.mddocs/requirements/p0-resourceservice.mddocs/requirements/p0-uimanager.mddocs/requirements/p0-audioservice.md
其中主文档必须更新的决策:
- DI 自动注入进入 P0。
- UI 层级从固定三层改为可配置层级。
- UI 层内 LIFO 栈进入 P0。
- JSON 是 P0 唯一默认配置格式。
- 资源 P0 只保留 Addressables 默认后端,其他后端插件化。
- 存档 P0 只保留本地轻量实现,云存档插件化。