Files
FlowScope/docs/requirements/p0-requirements-set.md
2026-05-20 15:36:10 +08:00

19 KiB
Raw Permalink Blame History

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 支持运行时注册层级:

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)

默认实现二选一:

  • FileSaveStorage
  • PlayerPrefsSaveStorage

Exists 可作为轻量辅助进入 P0ListKeys、云存档、加密、压缩、版本迁移、冲突解决不进入 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 接口统一使用 TaskCancellationToken

规则:

  • StartupAsyncSwitchToAsyncShutdownAsync 必须接受 CancellationToken
  • Feature 生命周期通过 FeatureContext.CancellationToken 获取取消信号。
  • 切换期间再次调用 SwitchToAsyncInvalidOperationExceptionP0 不做排队。
  • ShutdownAsyncStartingSwitching 中调用时P0 抛 InvalidOperationExceptionP1 可考虑中断式关闭。
  • 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 通过 IConfigParserIConfigSource 插件化接入,不修改业务调用。

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);
}

默认实现:

  • FileSaveStoragePlayerPrefsSaveStorage 二选一。
  • 默认序列化为 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 核心:

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 只保留本地轻量实现,云存档插件化。