Files
FlowScope/docs/superpowers/specs/2026-05-14-game-core-design.md
2026-05-15 15:38:40 +08:00

448 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FlowScope Game Core 设计文档
## 定位
面向休闲/超休闲游戏的 Unity 客户端通用框架。业务逻辑层保持纯 C#,基础设施层直接使用 Unity API。
## 整体架构
```
FlowScope Game Core
├── 基础设施层
│ ├── Container — 自研轻量 DIscope/resolve/dispose无反射
│ └── R3 — ReactiveProperty、事件流、集合订阅
├── 核心服务层(全部接口化,实现可替换)
│ ├── IResourceService — 资源加载/引用计数/分组释放
│ ├── ISaveService — 存档/读档持久化
│ ├── IConfigProvider — 配置表加载/查询
│ ├── IAudioService — BGM/SFX 池化/音量控制
│ ├── INetworkService — 网络请求/推送
│ └── UIManager — MVVM Panel 生命周期/页面栈/层级
├── 数据层按业务域拆分ReactiveProperty 驱动)
│ └── 各游戏按需定义自己的 Data 类
├── 业务特性层(自包含,独立生命周期)
│ └── 各 Feature: Load → Enter → Exit → Dispose
└── 生命周期编排
└── GameFlow — 启动/切换 Feature/关闭的全局时间线
```
## 设计原则
1. 接口化:核心服务全部走接口,实现可替换
2. 显式优于隐式DI 不做反射,手动注册手动传参
3. 按域拆分:数据类按业务域拆分,不建大 Data 类
4. 特性自包含:每个 Feature 自带资源组、订阅、ViewModel切换时干净释放
5. 框架只提供工具:不预设具体 Data 类,游戏自己定义
## 依赖方向(单向,不反向)
```
Feature → Core Services → Interfaces → Infrastructure (Container + R3)
Data Classes纯 C#,无依赖)
```
---
## 基础设施层
### Container — 自研轻量 DI
不做反射、不做构造函数自动注入、不做标签注入。显式注册,显式解析。
```csharp
public class Container : IDisposable
{
void RegisterInstance<T>(T instance);
void RegisterFactory<T>(Func<T> factory);
T Resolve<T>();
bool TryResolve<T>(out T value);
Container CreateScope(); // 子 scope 可访问父级,可覆盖父级
void Dispose(); // 释放 scope 内的 IDisposable
}
```
注册方式:
```csharp
container.RegisterInstance<IConfig>(config);
container.RegisterFactory<ISaveService>(() => new FileSaveService());
```
### R3
直接使用 R3 库提供:
- ReactiveProperty — 可观察属性MVVM 绑定基石
- ReactiveCollection — 可观察列表
- Subject — 事件流
- Observable.Timer / EveryUpdate — 计时和帧调度
- CompositeDisposable — 批量释放订阅
---
## 核心服务层
### IResourceService — 资源管理
```csharp
public interface IResourceService
{
Task<IResourceHandle<T>> LoadAsync<T>(string key) where T : class;
IResourceGroup CreateGroup();
}
public interface IResourceHandle<T> : IDisposable
{
T Asset { get; }
}
public interface IResourceGroup : IDisposable
{
void Add<T>(IResourceHandle<T> handle);
}
```
引用计数机制:同一 key 多次 Load 只加载一次,最后一个 handle Dispose 时才真正卸载。
默认实现AddressablesResourceService。
### ISaveService — 持久化
```csharp
public interface ISaveService
{
Task SaveAsync<T>(string key, T data);
Task<T> LoadAsync<T>(string key, T defaultValue = default);
void Delete(string key);
}
```
默认实现可选 FileSaveServiceJSON 文件)或 PlayerPrefsSaveService。
### IConfigProvider — 配置管理
```csharp
public interface IConfigRow
{
int Id { get; }
}
public interface IConfigProvider
{
Task LoadAllAsync();
T Get<T>(int id) where T : IConfigRow;
IReadOnlyList<T> GetAll<T>() where T : IConfigRow;
}
```
不绑定数据源JSON / CSV / Luban由实现决定。默认 JsonConfigProvider。
### IAudioService — 音频管理
```csharp
public interface IAudioService
{
void PlayBgm(string key, bool loop = true);
void StopBgm(float fadeOut = 0.5f);
void PlaySfx(string key);
void SetBgmVolume(float volume);
void SetSfxVolume(float volume);
void SetMute(bool mute);
}
```
内部AudioSource 对象池BGM 单独一个SFX 池化复用。
### INetworkService — 网络管理
```csharp
public interface INetworkService
{
Task<TResponse> SendAsync<TRequest, TResponse>(string path, TRequest data);
void On<TEvent>(Action<TEvent> handler);
}
```
初期可能用不上,但留好接口。默认 HttpNetworkService。
### UIManager — UI 管理
MVVM 模式的 Panel 生命周期管理。
```csharp
public class UIManager
{
Task OpenAsync<TPanel, TViewModel>(TViewModel viewModel) where TPanel : IPanel;
void Close<TPanel>();
void CloseAll();
}
public interface IPanel
{
void Bind(object viewModel);
void Unbind();
}
```
Panel 生命周期Instantiate → Bind → Show → ... → Hide → Unbind → Destroy。
层级管理Bottom基础层、Middle弹窗层、Top提示层
ViewModel 示例(纯 C#
```csharp
public class ShopViewModel
{
public ReactiveProperty<string> Title { get; } = new("商店");
public ReactiveCollection<ShopItemViewModel> Items { get; } = new();
private readonly PlayerData _player;
private readonly IConfigProvider _config;
public void BuyItem(int itemId)
{
var config = _config.Get<ItemConfig>(itemId);
if (_player.Gold.Value < config.Price) return;
_player.Gold.Value -= config.Price;
}
}
```
Panel 示例Unity MonoBehaviour
```csharp
public class ShopPanel : MonoBehaviour, IPanel
{
[SerializeField] private Text titleText;
[SerializeField] private Transform itemListRoot;
private ShopViewModel _vm;
private CompositeDisposable _disposables = new();
public void Bind(object viewModel)
{
_vm = (ShopViewModel)viewModel;
_vm.Title.Subscribe(t => titleText.text = t).AddTo(_disposables);
_vm.Items.ObserveAdd().Subscribe(e => CreateItemCell(e.Value)).AddTo(_disposables);
}
public void Unbind()
{
_disposables.Clear();
_vm = null;
}
private void OnDestroy() => Unbind();
}
```
---
## 数据层
框架不提供任何具体 Data 类,只提供 ReactiveProperty / ReactiveCollection 工具。
每个游戏按业务域自己定义:
```csharp
// 游戏需要金币?建一个
public class PlayerData
{
public ReactiveProperty<int> Level { get; } = new(1);
public ReactiveProperty<int> Gold { get; } = new(0);
}
// 不需要背包?就不建 InventoryData
// 需要设置?按需加
public class SettingsData
{
public ReactiveProperty<float> BgmVolume { get; } = new(1f);
public ReactiveProperty<string> Language { get; } = new("zh");
}
```
原则:一个 Data 类 = 一个业务域,新业务加新类。
---
## 业务特性层
### IFeature
```csharp
public interface IFeature : IDisposable
{
Task LoadAsync(); // 加载资源、创建 ViewModel
Task EnterAsync(); // 显示 UI、订阅事件
Task ExitAsync(); // 隐藏 UI、取消订阅
void Dispose(); // 释放资源
}
```
每个 Feature 自包含自己的资源组、订阅、ViewModel。切换时干净释放。
Feature 示例:
```csharp
public class ShopFeature : IFeature
{
private readonly IResourceService _resource;
private readonly UIManager _uiManager;
private readonly PlayerData _player;
private readonly IConfigProvider _config;
private IResourceGroup _resources;
private CompositeDisposable _disposables;
private ShopViewModel _viewModel;
public async Task LoadAsync()
{
_resources = _resource.CreateGroup();
var bgm = await _resource.LoadHandleAsync<AudioClip>("audio/bgm_shop");
_resources.Add(bgm);
_viewModel = new ShopViewModel(_player, _config);
}
public async Task EnterAsync()
{
_disposables = new CompositeDisposable();
await _uiManager.OpenAsync<ShopPanel, ShopViewModel>(_viewModel);
}
public async Task ExitAsync()
{
_uiManager.Close<ShopPanel>();
_disposables?.Dispose();
}
public void Dispose()
{
_resources?.Dispose();
_viewModel = null;
}
}
```
---
## 生命周期编排
### GameFlow
```csharp
public class GameFlow
{
private readonly Container _container;
private IFeature _currentFeature;
public async Task StartupAsync()
{
var config = _container.Resolve<IConfigProvider>();
await config.LoadAllAsync();
var save = _container.Resolve<ISaveService>();
var player = await save.LoadAsync<PlayerData>("player", new PlayerData());
_container.RegisterInstance(player);
await SwitchToAsync<MainMenuFeature>();
}
public async Task SwitchToAsync<TFeature>() where TFeature : IFeature, new()
{
if (_currentFeature != null)
{
await _currentFeature.ExitAsync();
_currentFeature.Dispose();
}
var feature = new TFeature();
await feature.LoadAsync();
await feature.EnterAsync();
_currentFeature = feature;
}
public async Task ShutdownAsync()
{
if (_currentFeature != null)
{
await _currentFeature.ExitAsync();
_currentFeature.Dispose();
}
var save = _container.Resolve<ISaveService>();
await save.SaveAsync("player", _container.Resolve<PlayerData>());
}
}
```
### 启动入口
```csharp
public class GameBootstrap : MonoBehaviour
{
private void Awake()
{
var container = new Container();
// 核心服务
container.RegisterInstance<IResourceService>(new AddressablesResourceService());
container.RegisterInstance<ISaveService>(new FileSaveService());
container.RegisterInstance<IConfigProvider>(new JsonConfigProvider());
container.RegisterInstance<IAudioService>(new UnityAudioService());
container.RegisterInstance<UIManager>(new UIManager());
// 游戏数据
container.RegisterInstance(new PlayerData());
container.RegisterInstance(new SettingsData());
var flow = new GameFlow(container);
flow.StartupAsync().Forget();
}
}
```
---
## 模块总表
| 模块 | 职责 | 类型 |
|------|------|------|
| Container | 轻量 DIscope/resolve/dispose | 自研 |
| R3 | ReactiveProperty、事件流、调度 | 第三方库 |
| IResourceService | 资源加载、引用计数、分组释放 | 接口 |
| ISaveService | 存档/读档持久化 | 接口 |
| IConfigProvider | 配置表加载/查询 | 接口 |
| IAudioService | BGM/SFX 池化、音量控制 | 接口 |
| INetworkService | 网络请求/推送 | 接口 |
| UIManager | MVVM Panel 生命周期、页面栈、层级 | 类 |
| Data 类 | 按业务域拆分的运行时状态 | 游戏自定义 |
| IFeature | 自包含特性模块,独立生命周期 | 接口 |
| GameFlow | 全局启动/切换/关闭时间线 | 类 |
## 技术选型
| 选型 | 决定 | 理由 |
|------|------|------|
| DI 容器 | 自研 | 休闲游戏只需显式注册/解析/scope不需要反射注入 |
| 响应式 | R3 | 成熟的 Unity 响应式库,自带 ObservableProperty/Collection |
| 异步 | C# Task + async/await | 休闲游戏的 GC 开销可忽略,不需要 UniTask 的零 GC |
| 资源加载 | Addressables | Unity 标准,通过 IResourceService 接口可替换 |
| 配置数据 | JSON默认 | 通过 IConfigProvider 接口可换 Luban |
| UI | uGUI + MVVM | BindableProperty 驱动ViewModel 纯 C# |
## 不做的事
- 不做反射式 DI / 自动注入
- 不做完整的 ORM
- 不做 UI 自动绑定
- 不做热更新框架
- 不做编辑器工具
- 不预设具体业务 Data 类
- 不依赖 UniTaskC# Task 够用)