# 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: ```json { "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` 中使用本地路径: ```json { "dependencies": { "com.flowscope.gamecore": "file:../FlowScope/Packages/com.flowscope.gamecore" } } ``` 适用场景: - 同时开发 FlowScope 和消费方项目。 - 需要快速验证 Runtime、Addressables、Editor、Samples 或 Tests package 布局。 - 不希望每次小改都重新打 Git tag。 ### 项目内 Assets 开发模式 FlowScope 自身开发工程仍保留: ```text My project/Assets/FlowScope ``` 该路径继续承担: - P0/P1 Runtime 回归测试。 - 主项目内 Addressables 真实 fixture 验收。 - MainMenuP0 Assets 样例源。 - 迁移到 package `Samples~` / `Tests~` 前的稳定参照。 不要在消费方项目同时复制 `Assets/FlowScope` 并安装 Git package,否则会混淆“项目内源码导入”和“package 消费”两条验证路径。 ## 当前 package 内容 ```text 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 中完成最小消费方验证。 - `v0.2.0-preview.2` 已在 `TestFlowScopePackage` 完成空白消费方刷新确认。 不作为 P2 package/editor 收尾阻塞: - package `Tests~` 全量策略与 PlayMode 测试迁移。 - 是否将 package 作为源码主线。 ## 最小 Bootstrap 示例 下面示例只使用当前真实 API 名称。示例使用 Addressables 作为资源后端;如果项目暂时没有 Addressables 资源,也可以参考 `MainMenuP0` 中的 `SampleResourceBackend` 做本地验证。 ```csharp 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( "example.json", new InMemoryConfigSource( new Dictionary { { "example.json", "[{\"Id\":1,\"Name\":\"Start\"}]" } }, "Bootstrap"), new JsonConfigParser()) }); var saveService = new SaveService( new PlayerPrefsSaveStorage(), new JsonSaveSerializer()); var audioService = gameObject .AddComponent() .Initialize(resources); _root.RegisterInstance(_root); _root.RegisterInstance(configProvider); _root.RegisterInstance(resources); _root.RegisterInstance(saveService); _root.RegisterInstance(audioService); _root.RegisterInstance(uiManager); _root.RegisterInstance(_preloadService); _root.RegisterInstance(screenNavigator); _root.RegisterTransient(_ => new ExampleFeature(screenNavigator)); await _gameFlow.StartupAsync(_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` 会先解析并加载 `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/editor 已随 `v0.2.0-preview.2` 完成收口。后续仍需要单独规划: 1. package `Tests~` 全量策略和 PlayMode 测试迁移策略。 2. 是否将 package 作为源码主线。 3. 更完整的发布流水线和版本兼容策略。