# 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/关闭的全局时间线 ``` ## 设计原则 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 instance); void RegisterFactory(Func factory); T Resolve(); bool TryResolve(out T value); Container CreateScope(); // 子 scope 可访问父级,可覆盖父级 void Dispose(); // 释放 scope 内的 IDisposable } ``` 注册方式: ```csharp container.RegisterInstance(config); container.RegisterFactory(() => new FileSaveService()); ``` ### R3 直接使用 R3 库提供: - ReactiveProperty — 可观察属性,MVVM 绑定基石 - ReactiveCollection — 可观察列表 - Subject — 事件流 - Observable.Timer / EveryUpdate — 计时和帧调度 - CompositeDisposable — 批量释放订阅 --- ## 核心服务层 ### IResourceService — 资源管理 ```csharp public interface IResourceService { Task> LoadAsync(string key) where T : class; IResourceGroup CreateGroup(); } public interface IResourceHandle : IDisposable { T Asset { get; } } public interface IResourceGroup : IDisposable { void Add(IResourceHandle handle); } ``` 引用计数机制:同一 key 多次 Load 只加载一次,最后一个 handle Dispose 时才真正卸载。 默认实现:AddressablesResourceService。 ### ISaveService — 持久化 ```csharp public interface ISaveService { Task SaveAsync(string key, T data); Task LoadAsync(string key, T defaultValue = default); void Delete(string key); } ``` 默认实现可选 FileSaveService(JSON 文件)或 PlayerPrefsSaveService。 ### IConfigProvider — 配置管理 ```csharp public interface IConfigRow { int Id { get; } } public interface IConfigProvider { Task LoadAllAsync(); T Get(int id) where T : IConfigRow; IReadOnlyList GetAll() 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 SendAsync(string path, TRequest data); void On(Action handler); } ``` 初期可能用不上,但留好接口。默认 HttpNetworkService。 ### UIManager — UI 管理 MVVM 模式的 Panel 生命周期管理。 ```csharp public class UIManager { Task OpenAsync(TViewModel viewModel) where TPanel : IPanel; void Close(); 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 Title { get; } = new("商店"); public ReactiveCollection Items { get; } = new(); private readonly PlayerData _player; private readonly IConfigProvider _config; public void BuyItem(int itemId) { var config = _config.Get(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 Level { get; } = new(1); public ReactiveProperty Gold { get; } = new(0); } // 不需要背包?就不建 InventoryData // 需要设置?按需加 public class SettingsData { public ReactiveProperty BgmVolume { get; } = new(1f); public ReactiveProperty 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("audio/bgm_shop"); _resources.Add(bgm); _viewModel = new ShopViewModel(_player, _config); } public async Task EnterAsync() { _disposables = new CompositeDisposable(); await _uiManager.OpenAsync(_viewModel); } public async Task ExitAsync() { _uiManager.Close(); _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(); await config.LoadAllAsync(); var save = _container.Resolve(); var player = await save.LoadAsync("player", new PlayerData()); _container.RegisterInstance(player); await SwitchToAsync(); } public async Task SwitchToAsync() 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(); await save.SaveAsync("player", _container.Resolve()); } } ``` ### 启动入口 ```csharp public class GameBootstrap : MonoBehaviour { private void Awake() { var container = new Container(); // 核心服务 container.RegisterInstance(new AddressablesResourceService()); container.RegisterInstance(new FileSaveService()); container.RegisterInstance(new JsonConfigProvider()); container.RegisterInstance(new UnityAudioService()); container.RegisterInstance(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 够用)