8.8 KiB
8.8 KiB
FlowScope 安装与最小接入
日期:2026-06-06
本文说明 FlowScope 在 P2 package/editor 阶段的当前安装方式、最小接入写法和已知限制。当前仓库已经存在真实 Unity Package 目录 Packages/com.flowscope.gamecore,同时仍保留 My project/Assets/FlowScope 作为开发工程内的测试与样例源。
当前可用安装方式
Git URL
消费方项目推荐通过 SSH Git URL 安装,并固定到 preview tag 或明确 commit:
{
"scopedRegistries": [
{
"name": "Unity NuGet",
"url": "https://unitynuget-registry.openupm.com",
"scopes": [
"org.nuget"
]
}
],
"dependencies": {
"com.cysharp.r3": "https://github.com/Cysharp/R3.git?path=src/R3.Unity/Assets/R3.Unity#1.3.0",
"com.flowscope.gamecore": "ssh://git@git.zz.com:2222/Developer/FlowScope.git?path=/Packages/com.flowscope.gamecore#v0.2.0-preview.2",
"org.nuget.r3": "1.3.0"
}
}
注意:
com.cysharp.r3提供 Unity 适配层。org.nuget.r3提供R3.dll和相关 BCL DLL。- Unity Package Manager 不可靠支持在 package 的
package.json中声明 Git dependency,因此 R3 必须由消费方 manifest 显式声明。 - 当前
v0.2.0-preview.2覆盖空白项目 Git URL 安装、最小 smoke probe 编译、Documentation~导入内容,以及Samples~/MainMenuP0手动导入验收。
本地 package path
本机调试时可在消费方 Unity 项目的 Packages/manifest.json 中使用本地路径:
{
"dependencies": {
"com.flowscope.gamecore": "file:../FlowScope/Packages/com.flowscope.gamecore"
}
}
适用场景:
- 同时开发 FlowScope 和消费方项目。
- 需要快速验证 Runtime、Addressables、Editor、Samples 或 Tests package 布局。
- 不希望每次小改都重新打 Git tag。
项目内 Assets 开发模式
FlowScope 自身开发工程仍保留:
My project/Assets/FlowScope
该路径继续承担:
- P0/P1 Runtime 回归测试。
- 主项目内 Addressables 真实 fixture 验收。
- MainMenuP0 Assets 样例源。
- 迁移到 package
Samples~/Tests~前的稳定参照。
不要在消费方项目同时复制 Assets/FlowScope 并安装 Git package,否则会混淆“项目内源码导入”和“package 消费”两条验证路径。
当前 package 内容
Packages/com.flowscope.gamecore/
package.json
Runtime/
Addressables/
Editor/
Samples~/
Tests~/
Documentation~/
README.md
CHANGELOG.md
已完成:
- Runtime / Addressables / Editor 镜像到 package。
Samples~/MainMenuP0作为 package sample 候选。Tests~/EditMode第一刀最小 smoke 测试。- Git package 在 FishingFrenzy 中完成最小消费方验证。
仍未完成:
- package sample 导入后的人工验收。
- package
Tests~全量策略与 PlayMode 测试迁移。 - 真正空白 Unity 项目的 Git URL 安装复验。
- 下一个 preview tag。
最小 Bootstrap 示例
下面示例只使用当前真实 API 名称。示例使用 Addressables 作为资源后端;如果项目暂时没有 Addressables 资源,也可以参考 MainMenuP0 中的 SampleResourceBackend 做本地验证。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FlowScope.Addressables;
using FlowScope.Audio;
using FlowScope.Config;
using FlowScope.Flow;
using FlowScope.Resources;
using FlowScope.Save;
using FlowScope.UI;
using UnityEngine;
using ContainerType = FlowScope.Container.Container;
public sealed class FlowScopeBootstrap : MonoBehaviour
{
[SerializeField] private Canvas hudCanvas;
private ContainerType _root;
private GameFlow _gameFlow;
private UIPreloadService _preloadService;
private CancellationTokenSource _lifetime;
public Task StartupTask { get; private set; }
private void Awake()
{
_lifetime = new CancellationTokenSource();
StartupTask = RunStartupWithLoggingAsync(_lifetime.Token);
}
public async Task StartupAsync(CancellationToken cancellationToken)
{
_root = new ContainerType();
_gameFlow = new GameFlow();
IResourceService resources = new AddressablesResourceService(
new AddressablesResourceBackend());
_preloadService = new UIPreloadService(resources);
var uiManager = new UIManager(resources, _preloadService);
uiManager.RegisterLayer("hud", hudCanvas, 0, PanelStrategy.Destroy);
var screenNavigator = new UIScreenNavigator(uiManager);
var configProvider = new JsonConfigProvider(new[]
{
JsonConfigProvider.Mapping<ExampleConfigRow>(
"example.json",
new InMemoryConfigSource(
new Dictionary<string, string>
{
{ "example.json", "[{\"Id\":1,\"Name\":\"Start\"}]" }
},
"Bootstrap"),
new JsonConfigParser())
});
var saveService = new SaveService(
new PlayerPrefsSaveStorage(),
new JsonSaveSerializer());
var audioService = gameObject
.AddComponent<AudioService>()
.Initialize(resources);
_root.RegisterInstance(_root);
_root.RegisterInstance<IConfigProvider>(configProvider);
_root.RegisterInstance<IResourceService>(resources);
_root.RegisterInstance<ISaveService>(saveService);
_root.RegisterInstance<IAudioService>(audioService);
_root.RegisterInstance(uiManager);
_root.RegisterInstance<IUIPreloadService>(_preloadService);
_root.RegisterInstance<IUIScreenNavigator>(screenNavigator);
_root.RegisterTransient(_ => new ExampleFeature(screenNavigator));
await _gameFlow.StartupAsync<ExampleFeature>(_root, cancellationToken);
}
public async Task ShutdownAsync(CancellationToken cancellationToken)
{
if (_gameFlow != null && _gameFlow.State != GameFlowState.Disposed)
{
await _gameFlow.ShutdownAsync(cancellationToken);
}
_preloadService?.ReleaseAll();
_lifetime?.Cancel();
_lifetime?.Dispose();
_lifetime = null;
}
private void OnApplicationQuit()
{
_ = RunShutdownWithLoggingAsync(CancellationToken.None);
}
private async Task RunStartupWithLoggingAsync(CancellationToken cancellationToken)
{
try
{
await StartupAsync(cancellationToken);
}
catch (Exception exception)
{
Debug.LogException(exception);
throw;
}
}
private async Task RunShutdownWithLoggingAsync(CancellationToken cancellationToken)
{
try
{
await ShutdownAsync(cancellationToken);
}
catch (Exception exception)
{
Debug.LogException(exception);
}
}
private sealed class ExampleConfigRow : IConfigRow
{
public int Id { get; set; }
public string Name { get; set; }
}
private sealed class ExampleFeature : IFeature
{
private readonly IUIScreenNavigator _screenNavigator;
public ExampleFeature(IUIScreenNavigator screenNavigator)
{
_screenNavigator = screenNavigator;
}
public Task LoadAsync(FeatureContext context) => Task.CompletedTask;
public Task EnterAsync(FeatureContext context) => Task.CompletedTask;
public Task ExitAsync(FeatureContext context) => Task.CompletedTask;
public void Dispose() { }
}
}
接入要点
GameFlow.StartupAsync<TInitialFeature>会先解析并加载IConfigProvider。- 初始 Feature 必须实现
IFeature,并能从 root 或 Feature scope 中解析。 - 业务资源统一通过
IResourceService或IResourceGroup加载,不直接调用 Addressables。 - UI 层使用
UIManager.RegisterLayer(...)建立层级,再由UIScreenNavigator或业务 Feature 打开面板。 - Shutdown 时应调用
GameFlow.ShutdownAsync(...),并释放 UI 预加载资源,例如_preloadService.ReleaseAll()。
常见误用
- 不要让业务 Feature 直接调用
UnityEngine.AddressableAssets.Addressables。 - 不要把短生命周期对象注册为
RegisterInstance或RegisterSingletonFactory。 - 不要跳过
GameFlow.ShutdownAsync(...)。 - 不要把
UIScreenNavigator当作完整页面路由系统。 - 不要让
FlowScope.Runtime反向依赖 Addressables、Editor、Samples 或 Tests。 - 不要在没有注册 UI layer 的情况下打开面板。
- 不要在未初始化
AudioService.Initialize(...)前调用播放接口。
当前限制
P2 收口前仍需要完成:
- package sample 导入验收。
- package
Tests~全量策略和 PlayMode 迁移策略。 - 真正空白 Unity 项目 Git URL 安装复验。
- 下一个 preview tag。
- Unity Test Runner 或明确记录无法执行原因。