274 lines
8.8 KiB
Markdown
274 lines
8.8 KiB
Markdown
# 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 中完成最小消费方验证。
|
||
|
||
仍未完成:
|
||
- package sample 导入后的人工验收。
|
||
- package `Tests~` 全量策略与 PlayMode 测试迁移。
|
||
- 真正空白 Unity 项目的 Git URL 安装复验。
|
||
- 下一个 preview tag。
|
||
|
||
## 最小 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<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 收口前仍需要完成:
|
||
|
||
1. package sample 导入验收。
|
||
2. package `Tests~` 全量策略和 PlayMode 迁移策略。
|
||
3. 真正空白 Unity 项目 Git URL 安装复验。
|
||
4. 下一个 preview tag。
|
||
5. Unity Test Runner 或明确记录无法执行原因。
|