文档记录

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,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
```

View File

@@ -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<T>(int id) where T : class, IConfigRow;
IReadOnlyList<T> GetAll<T>() where T : class, IConfigRow;
}
public interface IConfigRow
{
int Id { get; }
}
```
## 功能需求
- 启动时通过 `LoadAllAsync` 加载全部 JSON 配置。
- 配置行必须实现 `IConfigRow`
- 查询使用泛型类型 + `Id`
- `Get<T>(missingId)` 返回 null。
- `GetAll<T>()` 返回只读列表;未加载该类型时返回空列表。
- 同一配置类型内重复 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<T> Parse<T>(string content) where T : class, IConfigRow;
}
public interface IConfigSource
{
Task<string> LoadTextAsync(string key, CancellationToken cancellationToken);
}
```
## 验收标准
| # | 标准 | 通过条件 |
|---|------|---------|
| 1 | JSON 加载 | `LoadAllAsync` 后配置可查询 |
| 2 | 按 Id 查询 | `Get<WeaponConfig>(101)` 返回正确对象 |
| 3 | 不存在 Id | 返回 null不抛异常 |
| 4 | 获取列表 | `GetAll<WeaponConfig>()` 返回全部配置 |
| 5 | 重复 Id | 加载时抛异常并包含重复 Id |
| 6 | 格式错误 | 抛异常并包含文件名 |
| 7 | 取消加载 | CancellationToken 取消时抛 `OperationCanceledException` |
## 依赖关系
```text
IConfigProvider
├── JSON 库
├── 可选 IResourceService 或文件读取适配
└── 纯 C# 配置行类型
```

View File

@@ -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>(T instance);
public void RegisterFactory<T>(Func<Container, T> factory);
public T Resolve<T>();
public bool TryResolve<T>(out T value);
public Container CreateScope();
public void Dispose();
}
```
规则:
- `RegisterInstance<T>` 注册外部实例Container 不负责释放。
- `RegisterFactory<T>` 首次 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<TInterface, TImplementation>()
where TImplementation : TInterface;
public void RegisterType<TImplementation>();
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
```

View File

@@ -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<int> Gold { get; } = new(0);
public ReactiveProperty<int> Level { get; } = new(1);
}
```
规则:
- 一个 Data 类对应一个业务域。
- Data 类注册在全局 Container 中。
- Data 类保持纯 C#,不依赖 Unity API。
- Data 类不持有 View 或 ViewModel 引用。
- Data 类不包含复杂业务流程逻辑。
- Data 类之间不互相直接引用。
- Data 类使用 `ReactiveProperty<T>``ReactiveCollection<T>` 表达可观察状态。
## ViewModel 规则
- ViewModel 可以引用 Data。
- ViewModel 负责 UI 表现逻辑和用户操作逻辑。
- ViewModel 不持有 MonoBehaviour 或具体 View 引用。
- ViewModel 订阅必须加入自己的 `CompositeDisposable`,或加入 `FeatureContext.Disposables`
## 序列化规则
- `ReactiveProperty<T>` 序列化为 `.Value`
- `ReactiveCollection<T>` 序列化为 JSON 数组。
- 反序列化到已有 Data 实例时,更新 `.Value` 或集合内容,不替换 Data 实例。
- 反序列化类型不匹配时抛异常并包含字段名。
## 事件流规则
P0 允许使用 R3 `Subject<T>` 做 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
```

View File

@@ -0,0 +1,128 @@
# P0-3: Feature 需求详细文档
## 对齐说明
本文档以 `docs/requirements/p0-requirements-set.md` 为准。Feature 不再自己创建根 scopeGameFlow 创建 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
```

View File

@@ -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<TInitialFeature>(
Container root,
CancellationToken cancellationToken)
where TInitialFeature : IFeature;
public Task SwitchToAsync<TFeature>(
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` 失败:清理新 FeatureState = NoActiveFeature抛异常。
- 新 Feature `EnterAsync` 失败:调用新 Feature DisposeState = 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
```

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

View File

@@ -0,0 +1,104 @@
# P0-7: IResourceService 需求详细文档
## 对齐说明
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 资源系统包含引用计数和资源组,因为它直接影响 Feature 释放正确性;但 P0 只实现 Addressables 默认后端,其他后端通过插件扩展。
## 目标
提供异步资源加载、同 key 并发合并、引用计数和资源组释放能力。
## 接口
```csharp
public interface IResourceService
{
Task<IResourceHandle<T>> LoadAsync<T>(
string key,
CancellationToken cancellationToken)
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; }
}
```
## 行为规则
- `LoadAsync<T>` 加载失败时抛异常,异常包含 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<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
where T : class;
void Release(string key, object asset);
}
```
## 验收标准
| # | 标准 | 通过条件 |
|---|------|---------|
| 1 | 正常加载 | LoadAsync 返回 handleAsset 可用 |
| 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 依赖
```

View File

@@ -0,0 +1,96 @@
# P0-6: ISaveService 需求详细文档
## 对齐说明
本文档以 `docs/requirements/p0-requirements-set.md` 为准。P0 存档系统只提供本地轻量存取;云存档、加密、压缩、复杂迁移和冲突解决通过后续扩展接入。
## 目标
提供本地保存、读取、删除和存在性检查能力,支撑 P0 Sample 的 Data 持久化。
## 接口
```csharp
public interface ISaveService
{
Task SaveAsync<T>(string key, T data, CancellationToken cancellationToken);
Task<T> LoadAsync<T>(string key, T defaultValue, CancellationToken cancellationToken);
void Delete(string key);
bool Exists(string key);
}
```
## 默认实现
P0 默认实现二选一:
| 实现 | 说明 |
|------|------|
| `FileSaveStorage` | 使用本地文件,适合普通存档 |
| `PlayerPrefsSaveStorage` | 使用 PlayerPrefs适合小型数据 |
P0 默认序列化为 JSON并支持 R3 Data 中的 `ReactiveProperty<T>` / `ReactiveCollection<T>` 转换。
## 行为规则
- `LoadAsync` 读取不存在 key 时返回 `defaultValue`
- 反序列化失败返回 `defaultValue` 并记录警告。
- 存储读取失败返回 `defaultValue` 并记录警告。
- 写入失败抛异常,异常包含 key。
- `Delete` 删除不存在 key 时静默忽略。
- `Exists` 只检查当前默认存储后端。
## 暂不做
- `ListKeys`
- 多存档槽管理。
- 自动存档。
- 云存档完整实现。
- 本地 + 云双写。
- 加密。
- 压缩。
- 存档版本迁移。
- 冲突解决。
## 扩展方向
后续通过以下接口扩展:
```csharp
public interface ISaveStorage
{
Task WriteAsync(string key, byte[] data, CancellationToken cancellationToken);
Task<byte[]> ReadAsync(string key, CancellationToken cancellationToken);
void Delete(string key);
bool Exists(string key);
}
public interface ISaveSerializer
{
byte[] Serialize<T>(T data);
T Deserialize<T>(byte[] bytes);
}
```
## 验收标准
| # | 标准 | 通过条件 |
|---|------|---------|
| 1 | 保存和读取 | Save 后 Load 返回等价数据 |
| 2 | 默认值 | 不存在 key 时返回传入 defaultValue |
| 3 | 删除 | Delete 后 Exists 为 falseLoad 返回 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
```

View File

@@ -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<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);
}
```
## 打开与关闭接口
```csharp
public sealed class UIManager
{
public Task<TPanel> OpenAsync<TPanel, TViewModel>(
TViewModel viewModel,
CancellationToken cancellationToken)
where TPanel : UIPanelBase<TViewModel>;
public void Close<TPanel>();
public void CloseLayer(string layerName);
public void CloseAll();
public bool IsOpen<TPanel>();
}
```
## 生命周期
```text
OpenAsync:
1. 读取 UIPanelAttribute
2. 校验层级已注册
3. 解析资源路径
4. IResourceService.LoadAsync<GameObject>
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
```