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

12 KiB
Raw Blame History

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

不做反射、不做构造函数自动注入、不做标签注入。显式注册,显式解析。

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

默认实现可选 FileSaveServiceJSON 文件)或 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 轻量 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 够用)