12 KiB
12 KiB
FlowScope Game Core 设计文档
定位
面向休闲/超休闲游戏的 Unity 客户端通用框架。业务逻辑层保持纯 C#,基础设施层直接使用 Unity API。
整体架构
FlowScope Game Core
│
├── 基础设施层
│ ├── Container — 自研轻量 DI(scope/resolve/dispose,无反射)
│ └── R3 — ReactiveProperty、事件流、集合订阅
│
├── 核心服务层(全部接口化,实现可替换)
│ ├── IResourceService — 资源加载/引用计数/分组释放
│ ├── ISaveService — 存档/读档持久化
│ ├── IConfigProvider — 配置表加载/查询
│ ├── IAudioService — BGM/SFX 池化/音量控制
│ ├── INetworkService — 网络请求/推送
│ └── UIManager — MVVM Panel 生命周期/页面栈/层级
│
├── 数据层(按业务域拆分,ReactiveProperty 驱动)
│ └── 各游戏按需定义自己的 Data 类
│
├── 业务特性层(自包含,独立生命周期)
│ └── 各 Feature: Load → Enter → Exit → Dispose
│
└── 生命周期编排
└── GameFlow — 启动/切换 Feature/关闭的全局时间线
设计原则
- 接口化:核心服务全部走接口,实现可替换
- 显式优于隐式:DI 不做反射,手动注册手动传参
- 按域拆分:数据类按业务域拆分,不建大 Data 类
- 特性自包含:每个 Feature 自带资源组、订阅、ViewModel,切换时干净释放
- 框架只提供工具:不预设具体 Data 类,游戏自己定义
依赖方向(单向,不反向)
Feature → Core Services → Interfaces → Infrastructure (Container + R3)
↓
Data Classes(纯 C#,无依赖)
基础设施层
Container — 自研轻量 DI
不做反射、不做构造函数自动注入、不做标签注入。显式注册,显式解析。
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
}
注册方式:
container.RegisterInstance<IConfig>(config);
container.RegisterFactory<ISaveService>(() => new FileSaveService());
R3
直接使用 R3 库提供:
- ReactiveProperty — 可观察属性,MVVM 绑定基石
- ReactiveCollection — 可观察列表
- Subject — 事件流
- Observable.Timer / EveryUpdate — 计时和帧调度
- CompositeDisposable — 批量释放订阅
核心服务层
IResourceService — 资源管理
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 — 持久化
public interface ISaveService
{
Task SaveAsync<T>(string key, T data);
Task<T> LoadAsync<T>(string key, T defaultValue = default);
void Delete(string key);
}
默认实现可选 FileSaveService(JSON 文件)或 PlayerPrefsSaveService。
IConfigProvider — 配置管理
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 — 音频管理
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 — 网络管理
public interface INetworkService
{
Task<TResponse> SendAsync<TRequest, TResponse>(string path, TRequest data);
void On<TEvent>(Action<TEvent> handler);
}
初期可能用不上,但留好接口。默认 HttpNetworkService。
UIManager — UI 管理
MVVM 模式的 Panel 生命周期管理。
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#):
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):
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 工具。
每个游戏按业务域自己定义:
// 游戏需要金币?建一个
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
public interface IFeature : IDisposable
{
Task LoadAsync(); // 加载资源、创建 ViewModel
Task EnterAsync(); // 显示 UI、订阅事件
Task ExitAsync(); // 隐藏 UI、取消订阅
void Dispose(); // 释放资源
}
每个 Feature 自包含:自己的资源组、订阅、ViewModel。切换时干净释放。
Feature 示例:
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
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>());
}
}
启动入口
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 | 轻量 DI,scope/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 类
- 不依赖 UniTask(C# Task 够用)