Files
FlowScope/docs/guides/flowscope-installation.md
2026-06-08 16:38:02 +08:00

274 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 或明确记录无法执行原因。