Compare commits
11 Commits
v0.2.0-pre
...
v0.2.0-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b91fc5484a | ||
|
|
d42c8f9d1d | ||
|
|
e92fed2d4f | ||
|
|
e6d1f00e23 | ||
|
|
55fda1e0cd | ||
|
|
8f3982c165 | ||
|
|
dfa25aaf15 | ||
|
|
7b2243a40b | ||
|
|
0930e7c173 | ||
|
|
ac8fcc3c24 | ||
|
|
3b3957fb74 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ Client/ServerData
|
||||
/cfgPack/encryptcfg
|
||||
.gemini/
|
||||
.worktrees/
|
||||
TestFlowScopePackage/
|
||||
GEMINI.md
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.2.0-preview.2
|
||||
|
||||
- Added `Samples~/MainMenuP0` package sample metadata and content.
|
||||
- Added the first `Tests~/EditMode` package smoke test slice.
|
||||
- Updated package installation and layout documentation to reflect the current P2 package state.
|
||||
- Fixed the package `Documentation~` content so consuming projects no longer see an empty documentation folder warning.
|
||||
- Validated Git package installation, package smoke references, and manual `MainMenuP0` sample import in a clean consumer project.
|
||||
|
||||
## 0.2.0-preview.1
|
||||
|
||||
- Added the first Git-consumable FlowScope Game Core package layout.
|
||||
- Mirrored Runtime, Addressables, and Editor sources from `Assets/FlowScope`.
|
||||
- Added package metadata and Git installation notes for consuming Unity projects.
|
||||
|
||||
|
||||
@@ -2,18 +2,28 @@
|
||||
|
||||
## Consumer Manifest
|
||||
|
||||
Add FlowScope and its Git-hosted R3 dependency to the consuming project's `Packages/manifest.json`:
|
||||
Add FlowScope, R3.Unity, and the NuGet R3 core dependency to the consuming project's `Packages/manifest.json`:
|
||||
|
||||
```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@192.168.9.108:2222/Developer/BombGameFramework.git?path=/Packages/com.flowscope.gamecore#codex/pre-p2-architecture-fixes"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Keep the R3 dependency in the project manifest. Unity Package Manager can install a Git package from the project manifest, but it does not support Git dependencies declared inside another package's `package.json`.
|
||||
Keep both R3 entries in the project manifest. Unity Package Manager can install a Git package from the project manifest, but Git dependencies declared inside another package's `package.json` are not resolved reliably. `org.nuget.r3` supplies the core R3 and BCL DLLs required by `R3.Unity`.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -23,5 +33,6 @@ After Unity resolves the package:
|
||||
2. Open `Tools/FlowScope/Diagnostics`.
|
||||
3. Confirm Addressables and UGUI dependencies are present.
|
||||
4. Add a small probe script in the consuming project that references `FlowScope.Container.Container` and `FlowScope.Flow.GameFlow`.
|
||||
5. Confirm the consuming project has zero compile errors and zero Console errors after package resolution.
|
||||
|
||||
Do not copy `Assets/FlowScope` into the consuming project when testing the Git package path. That would validate project-internal source import rather than package consumption.
|
||||
|
||||
@@ -10,20 +10,31 @@ Add the package to the consuming project's `Packages/manifest.json`:
|
||||
|
||||
```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@192.168.9.108:2222/Developer/BombGameFramework.git?path=/Packages/com.flowscope.gamecore#codex/pre-p2-architecture-fixes"
|
||||
"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` must be declared by the consuming project because Unity Package Manager does not support Git dependencies inside a package's `package.json`.
|
||||
`com.cysharp.r3` and `org.nuget.r3` must be declared by the consuming project. Unity Package Manager can install Git packages from the project manifest, but Git dependencies declared inside another package's `package.json` are not resolved reliably. `org.nuget.r3` supplies the core R3 and BCL DLLs required by `R3.Unity`.
|
||||
|
||||
## Assembly Boundaries
|
||||
|
||||
- `FlowScope.Runtime` references `R3.Unity`.
|
||||
- `FlowScope.Addressables` references `FlowScope.Runtime`, `Unity.Addressables`, and `Unity.ResourceManager`.
|
||||
- `FlowScope.Editor` is Editor-only and only provides read-only diagnostics.
|
||||
- `Samples~/MainMenuP0` is a package sample candidate mirrored from the project-internal MainMenuP0 sample.
|
||||
- `Tests~/EditMode` contains the first package-level smoke tests.
|
||||
|
||||
Runtime must not depend on Addressables, Editor, Samples, or Tests.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20098efc5dc925a4194600eea4969d68
|
||||
guid: 49d9c024747f4af0b560e21d931a7816
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
8
Packages/com.flowscope.gamecore/Samples~/MainMenuP0.meta
Normal file
8
Packages/com.flowscope.gamecore/Samples~/MainMenuP0.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dffe7921ed374f83b8077b49644bc744
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76085fdd1da44ab08b69395b17064b59
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Gold": 0
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd391f0c499b42a3a264d0a9de2483f8
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "FlowScope.Samples.MainMenuP0",
|
||||
"rootNamespace": "FlowScope.Samples.MainMenuP0",
|
||||
"references": [
|
||||
"FlowScope.Runtime",
|
||||
"R3.Unity"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb3b4bf3dfdc46e4a9352fc2203bbfd4
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c88fe653c15d473cac0029539a2ff355
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,453 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &4224295462618148274
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2785319262204863001}
|
||||
- component: {fileID: 6821847843857072034}
|
||||
- component: {fileID: 8750438775342100074}
|
||||
- component: {fileID: 8038936202738359261}
|
||||
m_Layer: 0
|
||||
m_Name: MainMenuPanel
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &2785319262204863001
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4224295462618148274}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 6248245286658728259}
|
||||
- {fileID: 6082938367091974448}
|
||||
- {fileID: 8836034375789789371}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 420, y: 240}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &6821847843857072034
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4224295462618148274}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3a641bd6447847f2af2cd5af40305a5a, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
goldText: {fileID: 1736696561195866944}
|
||||
incrementButton: {fileID: 4338913550008398238}
|
||||
--- !u!222 &8750438775342100074
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4224295462618148274}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8038936202738359261
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4224295462618148274}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.08, g: 0.1, b: 0.12, a: 0.92}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &4373334964405757163
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8836034375789789371}
|
||||
- component: {fileID: 2680961909818563798}
|
||||
- component: {fileID: 1639113149993436237}
|
||||
- component: {fileID: 4338913550008398238}
|
||||
m_Layer: 0
|
||||
m_Name: IncrementGoldButton
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &8836034375789789371
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4373334964405757163}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 9086714161511357950}
|
||||
m_Father: {fileID: 2785319262204863001}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0}
|
||||
m_AnchorMax: {x: 0.5, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 48}
|
||||
m_SizeDelta: {x: 220, y: 48}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2680961909818563798
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4373334964405757163}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1639113149993436237
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4373334964405757163}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.2, g: 0.54, b: 0.8, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &4338913550008398238
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4373334964405757163}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 1639113149993436237}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &5175570728316865318
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6082938367091974448}
|
||||
- component: {fileID: 901708792770677843}
|
||||
- component: {fileID: 1736696561195866944}
|
||||
m_Layer: 0
|
||||
m_Name: GoldText
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6082938367091974448
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5175570728316865318}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2785319262204863001}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0.5}
|
||||
m_AnchorMax: {x: 1, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 18}
|
||||
m_SizeDelta: {x: -48, y: 60}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &901708792770677843
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5175570728316865318}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1736696561195866944
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5175570728316865318}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_FontSize: 40
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 10
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 4
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: 0
|
||||
--- !u!1 &7474348118067574816
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6248245286658728259}
|
||||
- component: {fileID: 8827375201563443825}
|
||||
- component: {fileID: 1429476040966027426}
|
||||
m_Layer: 0
|
||||
m_Name: Title
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6248245286658728259
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7474348118067574816}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2785319262204863001}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: -24}
|
||||
m_SizeDelta: {x: -48, y: 46}
|
||||
m_Pivot: {x: 0.5, y: 1}
|
||||
--- !u!222 &8827375201563443825
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7474348118067574816}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1429476040966027426
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7474348118067574816}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_FontSize: 28
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 10
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 4
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: FlowScope Main Menu
|
||||
--- !u!1 &8599479246113536964
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 9086714161511357950}
|
||||
- component: {fileID: 2312868584952693925}
|
||||
- component: {fileID: 9151466043238822226}
|
||||
m_Layer: 0
|
||||
m_Name: Label
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &9086714161511357950
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8599479246113536964}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8836034375789789371}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2312868584952693925
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8599479246113536964}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &9151466043238822226
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8599479246113536964}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_FontSize: 20
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 10
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 4
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: Add Gold
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05cda67df190bb746b57ab6f944469c5
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
# MainMenuP0 Package Sample
|
||||
|
||||
`MainMenuP0` is the minimal FlowScope package sample. It demonstrates the recommended bootstrap path for a small Unity project without introducing a full game template.
|
||||
|
||||
## What It Covers
|
||||
|
||||
- Creating a root `Container`.
|
||||
- Registering config, resource, save, audio, UI, preload, and screen navigation services.
|
||||
- Registering `MainMenuFeature` with `RegisterTransient`.
|
||||
- Preloading `MainMenuPanel` through `UIPreloadService`.
|
||||
- Opening the main menu through `UIScreenNavigator`.
|
||||
- Updating `PlayerData.Gold` through R3-backed view data.
|
||||
- Saving data and releasing preloaded resources during `ShutdownAsync`.
|
||||
|
||||
## How To Import
|
||||
|
||||
In a consuming Unity project, open Package Manager, select `FlowScope Game Core`, and import the `MainMenuP0` sample.
|
||||
|
||||
Do not import this package sample into the FlowScope development project while `Assets/FlowScope/Samples/MainMenuP0` is still present. The development project keeps the Assets-based sample as the source of truth for tests during P2 migration.
|
||||
|
||||
## How To Run
|
||||
|
||||
After importing the sample, open:
|
||||
|
||||
```text
|
||||
Assets/Samples/FlowScope Game Core/<version>/MainMenuP0/Scenes/MainMenuP0.unity
|
||||
```
|
||||
|
||||
Enter Play Mode. The scene should show the main menu panel, a Gold text, and an increment button.
|
||||
|
||||
## Validation Points
|
||||
|
||||
- The first startup shows default Gold.
|
||||
- Clicking the button increments Gold immediately.
|
||||
- `ShutdownAsync` writes the `main-menu-player` save.
|
||||
- Restarting restores the last saved Gold.
|
||||
- Shutdown releases preloaded UI resources.
|
||||
- Console has no unexpected Error or Exception.
|
||||
|
||||
## Current Limits
|
||||
|
||||
- The sample uses `SampleResourceBackend` instead of real Addressables assets.
|
||||
- Runtime fallback UI exists so the sample can still run if scene or prefab references are missing.
|
||||
- P2 package tests are not migrated yet; current automated tests still live under the FlowScope development project's `Assets/FlowScope/Tests`.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9df9d94a34fa4ec2b16df2c8cf2a651b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 946526d21b06440688088de21f383782
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,440 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 9
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
m_GIWorkflowMode: 1
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_FinalGather: 0
|
||||
m_FinalGatherFiltering: 1
|
||||
m_FinalGatherRayCount: 256
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 1
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
m_PVRFilteringGaussRadiusAO: 2
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &204902849
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 204902852}
|
||||
- component: {fileID: 204902851}
|
||||
- component: {fileID: 204902850}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &204902850
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 204902849}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &204902851
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 204902849}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 2
|
||||
m_BackGroundColor: {r: 0.05, g: 0.06, b: 0.07, a: 1}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &204902852
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 204902849}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1061918572
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1061918574}
|
||||
- component: {fileID: 1061918573}
|
||||
m_Layer: 0
|
||||
m_Name: GameBootstrap
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1061918573
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1061918572}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: cd97432f2838454cbbf2d19dbe14d7f3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
hudCanvas: {fileID: 1965407523}
|
||||
mainMenuPanelPrefab: {fileID: 6821847843857072034, guid: 05cda67df190bb746b57ab6f944469c5, type: 3}
|
||||
defaultGold: 0
|
||||
--- !u!4 &1061918574
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1061918572}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1965407522
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1965407526}
|
||||
- component: {fileID: 1965407523}
|
||||
- component: {fileID: 1965407525}
|
||||
- component: {fileID: 1965407524}
|
||||
m_Layer: 0
|
||||
m_Name: HUD Canvas
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!223 &1965407523
|
||||
Canvas:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1965407522}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_RenderMode: 0
|
||||
m_Camera: {fileID: 0}
|
||||
m_PlaneDistance: 100
|
||||
m_PixelPerfect: 0
|
||||
m_ReceivesEvents: 1
|
||||
m_OverrideSorting: 0
|
||||
m_OverridePixelPerfect: 0
|
||||
m_SortingBucketNormalizedSize: 0
|
||||
m_VertexColorAlwaysGammaSpace: 0
|
||||
m_AdditionalShaderChannelsFlag: 0
|
||||
m_UpdateRectTransformForStandalone: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingOrder: 0
|
||||
m_TargetDisplay: 0
|
||||
--- !u!114 &1965407524
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1965407522}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreReversedGraphics: 1
|
||||
m_BlockingObjects: 0
|
||||
m_BlockingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
--- !u!114 &1965407525
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1965407522}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UiScaleMode: 0
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 1
|
||||
m_PresetInfoIsWorld: 0
|
||||
--- !u!224 &1965407526
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1965407522}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0, y: 0, z: 0}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!1 &2023591418
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2023591421}
|
||||
- component: {fileID: 2023591420}
|
||||
- component: {fileID: 2023591419}
|
||||
m_Layer: 0
|
||||
m_Name: EventSystem
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &2023591419
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2023591418}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_SendPointerHoverToParent: 1
|
||||
m_HorizontalAxis: Horizontal
|
||||
m_VerticalAxis: Vertical
|
||||
m_SubmitButton: Submit
|
||||
m_CancelButton: Cancel
|
||||
m_InputActionsPerSecond: 10
|
||||
m_RepeatDelay: 0.5
|
||||
m_ForceModuleActive: 0
|
||||
--- !u!114 &2023591420
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2023591418}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_FirstSelected: {fileID: 0}
|
||||
m_sendNavigationEvents: 1
|
||||
m_DragThreshold: 10
|
||||
--- !u!4 &2023591421
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2023591418}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 204902852}
|
||||
- {fileID: 2023591421}
|
||||
- {fileID: 1965407526}
|
||||
- {fileID: 1061918574}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03630d34cf304668afe00ab221f3557e
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93879cc02e2644039382a2006c5434b1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FlowScope.Audio;
|
||||
using FlowScope.Config;
|
||||
using FlowScope.Flow;
|
||||
using FlowScope.Resources;
|
||||
using FlowScope.Save;
|
||||
using FlowScope.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using ContainerType = FlowScope.Container.Container;
|
||||
|
||||
namespace FlowScope.Samples.MainMenuP0
|
||||
{
|
||||
public sealed class GameBootstrap : MonoBehaviour
|
||||
{
|
||||
private const string SaveKey = "main-menu-player";
|
||||
|
||||
[SerializeField] private Canvas hudCanvas;
|
||||
[SerializeField] private MainMenuPanel mainMenuPanelPrefab;
|
||||
[SerializeField] private int defaultGold;
|
||||
|
||||
private ContainerType _root;
|
||||
private GameFlow _gameFlow;
|
||||
private ISaveService _saveService;
|
||||
private UIPreloadService _preloadService;
|
||||
private SampleResourceBackend _sampleBackend;
|
||||
private PlayerData _playerData;
|
||||
private CancellationTokenSource _lifetime;
|
||||
private bool _started;
|
||||
private bool _shutdownInProgress;
|
||||
private bool _shutdown;
|
||||
|
||||
public int ReleasedResourceCount => _sampleBackend?.ReleaseCount ?? 0;
|
||||
/// <summary>
|
||||
/// True after runtime cleanup has completed; shutdown sub-step failures are still reported by ShutdownAsync.
|
||||
/// </summary>
|
||||
public bool IsShutdown => _shutdown;
|
||||
public Task StartupTask { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_lifetime = new CancellationTokenSource();
|
||||
StartupTask = RunStartupWithLoggingAsync(_lifetime.Token);
|
||||
}
|
||||
|
||||
public async Task StartupAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_started = true;
|
||||
_shutdown = false;
|
||||
_root = new ContainerType();
|
||||
_gameFlow = new GameFlow();
|
||||
|
||||
_sampleBackend = new SampleResourceBackend();
|
||||
_sampleBackend.Register(
|
||||
"FlowScope/Samples/MainMenuP0/MainMenuPanel",
|
||||
ResolveMainMenuPanelPrefab().gameObject);
|
||||
var resources = new ResourceService(_sampleBackend);
|
||||
_preloadService = new UIPreloadService(resources);
|
||||
var uiManager = new UIManager(resources, _preloadService);
|
||||
uiManager.RegisterLayer("hud", ResolveHudCanvas(), 0, PanelStrategy.Destroy);
|
||||
var screenNavigator = new UIScreenNavigator(uiManager);
|
||||
|
||||
var migrations = new SaveMigrationRegistry();
|
||||
migrations.Register(new PlayerDataV1ToV2Migration());
|
||||
_saveService = new SaveService(
|
||||
new FileSaveStorage(),
|
||||
new JsonSaveSerializer(2, migrations));
|
||||
_playerData = await _saveService.LoadAsync(
|
||||
SaveKey,
|
||||
new PlayerData { Gold = new R3.ReactiveProperty<int>(defaultGold) },
|
||||
cancellationToken);
|
||||
|
||||
await _preloadService.PreloadAsync<MainMenuPanel>(cancellationToken);
|
||||
|
||||
var audioService = gameObject.AddComponent<AudioService>().Initialize(resources);
|
||||
_root.RegisterInstance(_root);
|
||||
_root.RegisterInstance<IConfigProvider>(
|
||||
new JsonConfigProvider(new[]
|
||||
{
|
||||
JsonConfigProvider.Mapping<SampleMenuConfig>(
|
||||
"main_menu.json",
|
||||
new InMemoryConfigSource(
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "main_menu.json", "[{\"Id\":1,\"Title\":\"Main Menu P1\"}]" }
|
||||
},
|
||||
"MainMenuP0"),
|
||||
new JsonConfigParser())
|
||||
}));
|
||||
_root.RegisterInstance<IResourceService>(resources);
|
||||
_root.RegisterInstance<ISaveService>(_saveService);
|
||||
_root.RegisterInstance<IAudioService>(audioService);
|
||||
_root.RegisterInstance(uiManager);
|
||||
_root.RegisterInstance<IUIPreloadService>(_preloadService);
|
||||
_root.RegisterInstance<IUIScreenNavigator>(screenNavigator);
|
||||
_root.RegisterInstance(_playerData);
|
||||
_root.RegisterTransient(_ => new MainMenuFeature(screenNavigator, _playerData));
|
||||
|
||||
await _gameFlow.StartupAsync<MainMenuFeature>(_root, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task ShutdownAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_shutdown || _shutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_shutdownInProgress = true;
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
try
|
||||
{
|
||||
if (_saveService != null && _playerData != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _saveService.SaveAsync(SaveKey, _playerData, cancellationToken);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exceptions.Add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
if (_gameFlow != null && _gameFlow.State != GameFlowState.Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _gameFlow.ShutdownAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exceptions.Add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_preloadService?.ReleaseAll();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exceptions.Add(exception);
|
||||
}
|
||||
|
||||
_shutdown = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_shutdownInProgress = false;
|
||||
}
|
||||
|
||||
if (exceptions.Count == 1)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exceptions[0]).Throw();
|
||||
}
|
||||
|
||||
if (exceptions.Count > 1)
|
||||
{
|
||||
throw new AggregateException("MainMenuP0 shutdown completed with failures.", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
_lifetime?.Cancel();
|
||||
_ = RunShutdownWithLoggingAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_lifetime?.Cancel();
|
||||
_lifetime?.Dispose();
|
||||
}
|
||||
|
||||
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 Canvas ResolveHudCanvas()
|
||||
{
|
||||
if (hudCanvas != null)
|
||||
{
|
||||
return hudCanvas;
|
||||
}
|
||||
|
||||
var canvasObject = new GameObject("FlowScope HUD Canvas");
|
||||
canvasObject.transform.SetParent(transform, false);
|
||||
hudCanvas = canvasObject.AddComponent<Canvas>();
|
||||
hudCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
return hudCanvas;
|
||||
}
|
||||
|
||||
private MainMenuPanel ResolveMainMenuPanelPrefab()
|
||||
{
|
||||
if (mainMenuPanelPrefab != null)
|
||||
{
|
||||
return mainMenuPanelPrefab;
|
||||
}
|
||||
|
||||
var panelObject = new GameObject("MainMenuPanel Template");
|
||||
panelObject.transform.SetParent(transform, false);
|
||||
panelObject.SetActive(false);
|
||||
|
||||
var panel = panelObject.AddComponent<MainMenuPanel>();
|
||||
|
||||
var goldObject = new GameObject("GoldText");
|
||||
goldObject.transform.SetParent(panelObject.transform, false);
|
||||
var goldText = goldObject.AddComponent<Text>();
|
||||
goldText.text = defaultGold.ToString();
|
||||
goldText.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var buttonObject = new GameObject("IncrementGoldButton");
|
||||
buttonObject.transform.SetParent(panelObject.transform, false);
|
||||
buttonObject.AddComponent<Image>();
|
||||
var button = buttonObject.AddComponent<Button>();
|
||||
|
||||
panel.Configure(goldText, button);
|
||||
mainMenuPanelPrefab = panel;
|
||||
return mainMenuPanelPrefab;
|
||||
}
|
||||
|
||||
private sealed class SampleResourceBackend : IResourceBackend
|
||||
{
|
||||
private readonly Dictionary<string, GameObject> _prefabs = new();
|
||||
|
||||
public string Name => "MainMenuP0Sample";
|
||||
public int ReleaseCount { get; private set; }
|
||||
|
||||
public void Register(string key, GameObject prefab)
|
||||
{
|
||||
_prefabs[key] = prefab;
|
||||
}
|
||||
|
||||
public Task<T> LoadAsync<T>(
|
||||
string key,
|
||||
CancellationToken cancellationToken)
|
||||
where T : class
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (typeof(T) == typeof(GameObject) &&
|
||||
_prefabs.TryGetValue(key, out var prefab))
|
||||
{
|
||||
return Task.FromResult(prefab as T);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Sample resource is not registered: {key}");
|
||||
}
|
||||
|
||||
public void Release<T>(string key, T asset)
|
||||
where T : class
|
||||
{
|
||||
ReleaseCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SampleMenuConfig : IConfigRow
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
}
|
||||
|
||||
private sealed class PlayerDataV1ToV2Migration : ISaveMigration
|
||||
{
|
||||
public string Key => typeof(PlayerData).FullName;
|
||||
public int FromVersion => 1;
|
||||
public int ToVersion => 2;
|
||||
|
||||
public string Migrate(string json) => json;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd97432f2838454cbbf2d19dbe14d7f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using FlowScope.Flow;
|
||||
using FlowScope.UI;
|
||||
|
||||
namespace FlowScope.Samples.MainMenuP0
|
||||
{
|
||||
public sealed class MainMenuFeature : FeatureBase
|
||||
{
|
||||
private readonly IUIScreenNavigator _screenNavigator;
|
||||
private readonly PlayerData _playerData;
|
||||
private MainMenuViewModel _viewModel;
|
||||
|
||||
public MainMenuFeature(IUIScreenNavigator screenNavigator, PlayerData playerData)
|
||||
{
|
||||
_screenNavigator = screenNavigator;
|
||||
_playerData = playerData;
|
||||
}
|
||||
|
||||
public override Task LoadAsync(FeatureContext context)
|
||||
{
|
||||
base.LoadAsync(context);
|
||||
_viewModel = new MainMenuViewModel(_playerData);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task EnterAsync(FeatureContext context)
|
||||
{
|
||||
await _screenNavigator.PushAsync<MainMenuPanel, MainMenuViewModel>(
|
||||
_viewModel,
|
||||
context.CancellationToken);
|
||||
}
|
||||
|
||||
public override Task ExitAsync(FeatureContext context)
|
||||
{
|
||||
_screenNavigator.Clear();
|
||||
return base.ExitAsync(context);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_viewModel = null;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b015663928874766b56036f39f0aca6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using FlowScope.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace FlowScope.Samples.MainMenuP0
|
||||
{
|
||||
[UIPanel("hud", "FlowScope/Samples/MainMenuP0/MainMenuPanel")]
|
||||
public sealed class MainMenuPanel : UIPanelBase<MainMenuViewModel>
|
||||
{
|
||||
[SerializeField] private Text goldText;
|
||||
[SerializeField] private Button incrementButton;
|
||||
|
||||
public void Configure(Text goldLabel, Button incrementGoldButton)
|
||||
{
|
||||
goldText = goldLabel;
|
||||
incrementButton = incrementGoldButton;
|
||||
}
|
||||
|
||||
protected override void OnBind(MainMenuViewModel viewModel)
|
||||
{
|
||||
if (goldText == null)
|
||||
{
|
||||
goldText = GetComponentInChildren<Text>(true);
|
||||
}
|
||||
|
||||
if (incrementButton == null)
|
||||
{
|
||||
incrementButton = GetComponentInChildren<Button>(true);
|
||||
}
|
||||
|
||||
UpdateGold(viewModel.Gold.Value);
|
||||
viewModel.GoldChanged += UpdateGold;
|
||||
if (incrementButton != null)
|
||||
{
|
||||
incrementButton.onClick.AddListener(viewModel.IncrementGold);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Unbind()
|
||||
{
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.GoldChanged -= UpdateGold;
|
||||
}
|
||||
|
||||
if (incrementButton != null)
|
||||
{
|
||||
incrementButton.onClick.RemoveAllListeners();
|
||||
}
|
||||
|
||||
base.Unbind();
|
||||
}
|
||||
|
||||
private void UpdateGold(int value)
|
||||
{
|
||||
if (goldText != null)
|
||||
{
|
||||
goldText.text = value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a641bd6447847f2af2cd5af40305a5a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using R3;
|
||||
|
||||
namespace FlowScope.Samples.MainMenuP0
|
||||
{
|
||||
public sealed class MainMenuViewModel
|
||||
{
|
||||
private readonly PlayerData _data;
|
||||
|
||||
public MainMenuViewModel(PlayerData data)
|
||||
{
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
public ReactiveProperty<int> Gold => _data.Gold;
|
||||
public event Action<int> GoldChanged;
|
||||
|
||||
public void IncrementGold()
|
||||
{
|
||||
Gold.Value++;
|
||||
GoldChanged?.Invoke(Gold.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68e2343c920540a280ac2718ceae12ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
using R3;
|
||||
|
||||
namespace FlowScope.Samples.MainMenuP0
|
||||
{
|
||||
public sealed class PlayerData
|
||||
{
|
||||
public ReactiveProperty<int> Gold { get; set; } = new(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d62814450dc46fba84df1fba2a372f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.flowscope.gamecore/Tests~.meta
Normal file
8
Packages/com.flowscope.gamecore/Tests~.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9593988203244d3796c30233d04321de
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.flowscope.gamecore/Tests~/EditMode.meta
Normal file
8
Packages/com.flowscope.gamecore/Tests~/EditMode.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7e85b5827334420a6d81b9f37a5c8d1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "FlowScope.PackageTests.EditMode",
|
||||
"rootNamespace": "FlowScope.PackageTests.EditMode",
|
||||
"references": [
|
||||
"FlowScope.Runtime",
|
||||
"FlowScope.Addressables",
|
||||
"R3.Unity"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"R3.dll",
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e213b3e4bd60472087919ca2efba94bc
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FlowScope.Addressables;
|
||||
using FlowScope.Container;
|
||||
using FlowScope.Resources;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace FlowScope.PackageTests.EditMode
|
||||
{
|
||||
public sealed class PackageSmokeTests
|
||||
{
|
||||
[Test]
|
||||
public void PackageAssemblies_CanBeReferencedFromPackageTests()
|
||||
{
|
||||
var container = new Container();
|
||||
var backend = new AddressablesResourceBackend();
|
||||
IResourceService resources = new ResourceService(new ImmediateBackend());
|
||||
|
||||
container.RegisterInstance(resources);
|
||||
|
||||
Assert.AreEqual("Addressables", backend.Name);
|
||||
Assert.AreSame(resources, container.Resolve<IResourceService>());
|
||||
|
||||
container.Dispose();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResourceService_LoadAsync_CanRunInsidePackageTestAssembly()
|
||||
{
|
||||
var backend = new ImmediateBackend();
|
||||
var service = new ResourceService(backend);
|
||||
|
||||
using var handle = service
|
||||
.LoadAsync<TestAsset>("package-smoke", CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
Assert.That(handle.Asset, Is.Not.Null);
|
||||
Assert.AreEqual(1, backend.LoadCalls);
|
||||
|
||||
handle.Dispose();
|
||||
|
||||
Assert.AreEqual(1, backend.ReleaseCalls);
|
||||
}
|
||||
|
||||
private sealed class ImmediateBackend : IResourceBackend
|
||||
{
|
||||
public string Name => "Immediate";
|
||||
public int LoadCalls { get; private set; }
|
||||
public int ReleaseCalls { get; private set; }
|
||||
|
||||
public Task<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
|
||||
where T : class
|
||||
{
|
||||
LoadCalls++;
|
||||
return Task.FromResult(new TestAsset() as T);
|
||||
}
|
||||
|
||||
public void Release<T>(string key, T asset)
|
||||
where T : class
|
||||
{
|
||||
ReleaseCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestAsset
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adbf3a7109f64fea8a6c75febc85d0f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Packages/com.flowscope.gamecore/Tests~/README.md
Normal file
22
Packages/com.flowscope.gamecore/Tests~/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# FlowScope Package Tests
|
||||
|
||||
This directory contains the first package-level test slice for FlowScope Game Core.
|
||||
|
||||
The current tests are intentionally small. They prove that a package test assembly can reference:
|
||||
|
||||
- `FlowScope.Runtime`
|
||||
- `FlowScope.Addressables`
|
||||
- Unity Test Framework / NUnit
|
||||
|
||||
The full development test suite still lives under `My project/Assets/FlowScope/Tests` during P2 migration. Real Addressables fixture validation also remains in the development project because Addressables settings are project-level assets.
|
||||
|
||||
## Current Scope
|
||||
|
||||
- `EditMode/PackageSmokeTests.cs`
|
||||
|
||||
## Not Migrated Yet
|
||||
|
||||
- Full Runtime EditMode tests.
|
||||
- Full PlayMode tests.
|
||||
- MainMenuP0 sample tests.
|
||||
- Real Addressables fixture tests.
|
||||
7
Packages/com.flowscope.gamecore/Tests~/README.md.meta
Normal file
7
Packages/com.flowscope.gamecore/Tests~/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b532bf970be439aab8ef4bc3eb5cf2c
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "com.flowscope.gamecore",
|
||||
"displayName": "FlowScope Game Core",
|
||||
"version": "0.2.0-preview.1",
|
||||
"version": "0.2.0-preview.2",
|
||||
"unity": "2022.3",
|
||||
"description": "A lightweight Unity game flow, resource, config, save, audio, and UI runtime foundation.",
|
||||
"keywords": [
|
||||
@@ -17,5 +17,12 @@
|
||||
"dependencies": {
|
||||
"com.unity.addressables": "1.21.21",
|
||||
"com.unity.ugui": "1.0.0"
|
||||
},
|
||||
"samples": [
|
||||
{
|
||||
"displayName": "MainMenuP0",
|
||||
"description": "Minimal FlowScope bootstrap, feature, UI, save, and preload sample.",
|
||||
"path": "Samples~/MainMenuP0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,43 @@
|
||||
# FlowScope 安装与最小接入
|
||||
|
||||
日期:2026-05-26
|
||||
日期:2026-06-06
|
||||
|
||||
本文说明 FlowScope 在 P2 第一阶段面向使用者的安装路径、项目内开发模式,以及基于当前真实 API 的最小 Bootstrap 写法。当前仓库尚未完成标准 Unity Package 物理搬迁,package path 与 Git URL 方式是目标接入形态;项目内 `Assets/FlowScope` 开发模式是当前可用形态。
|
||||
本文说明 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` 中使用本地路径:
|
||||
本机调试时可在消费方 Unity 项目的 `Packages/manifest.json` 中使用本地路径:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -19,66 +48,60 @@
|
||||
```
|
||||
|
||||
适用场景:
|
||||
|
||||
- 本机同时开发 FlowScope 与游戏项目。
|
||||
- 需要在包仓库中调试 Runtime、Addressables、Editor 或 Samples。
|
||||
- 希望由 Unity Package Manager 识别包结构与 `Samples~`。
|
||||
|
||||
注意:P2 第一阶段当前仓库还没有 `Packages/com.flowscope.gamecore` 物理目录,因此此方式需要等第二阶段本地 package 试迁移完成后再用于实装验证。
|
||||
|
||||
### Git URL
|
||||
|
||||
当包目录与发布分支稳定后,可在 `Packages/manifest.json` 中使用 Git URL:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"com.flowscope.gamecore": "<最终发布仓库 Git URL>?path=/Packages/com.flowscope.gamecore#v0.2.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
适用场景:
|
||||
|
||||
- 使用方只消费稳定版本,不直接修改 FlowScope 源码。
|
||||
- 团队希望通过 tag 或固定 commit 控制接入版本。
|
||||
|
||||
当前仓库 `origin` 仍带有旧项目名,不应直接写入对外安装文档。发布前需要由团队确认最终 FlowScope 包仓库地址;使用 Git URL 时应固定 tag 或 commit,避免使用浮动分支导致使用方项目在不同时间解析到不同代码。
|
||||
- 同时开发 FlowScope 和消费方项目。
|
||||
- 需要快速验证 Runtime、Addressables、Editor、Samples 或 Tests package 布局。
|
||||
- 不希望每次小改都重新打 Git tag。
|
||||
|
||||
### 项目内 Assets 开发模式
|
||||
|
||||
当前可用模式是将 FlowScope 放在 Unity 项目内:
|
||||
|
||||
```text
|
||||
Assets/
|
||||
FlowScope/
|
||||
Runtime/
|
||||
Addressables/
|
||||
Samples/
|
||||
Tests/
|
||||
```
|
||||
|
||||
本仓库当前路径为:
|
||||
FlowScope 自身开发工程仍保留:
|
||||
|
||||
```text
|
||||
My project/Assets/FlowScope
|
||||
```
|
||||
|
||||
适用场景:
|
||||
该路径继续承担:
|
||||
- P0/P1 Runtime 回归测试。
|
||||
- 主项目内 Addressables 真实 fixture 验收。
|
||||
- MainMenuP0 Assets 样例源。
|
||||
- 迁移到 package `Samples~` / `Tests~` 前的稳定参照。
|
||||
|
||||
- P2 第一阶段继续验证 P0/P1 Runtime 行为。
|
||||
- 并行开发 Addressables 验收、Editor 诊断与 package 文档。
|
||||
- 在包迁移前保持 `MainMenuP0` 示例与测试路径稳定。
|
||||
不要在消费方项目同时复制 `Assets/FlowScope` 并安装 Git package,否则会混淆“项目内源码导入”和“package 消费”两条验证路径。
|
||||
|
||||
使用此模式时,不需要在 `manifest.json` 中声明 `com.flowscope.gamecore`。仍需要确保项目已有当前代码依赖的包,例如 `com.cysharp.r3`、`com.unity.addressables` 与 `com.unity.ugui`。
|
||||
## 当前 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` 示例中的自定义 `IResourceBackend` 做本地验证。
|
||||
下面示例只使用当前真实 API 名称。示例使用 Addressables 作为资源后端;如果项目暂时没有 Addressables 资源,也可以参考 `MainMenuP0` 中的 `SampleResourceBackend` 做本地验证。
|
||||
|
||||
```csharp
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FlowScope.Addressables;
|
||||
@@ -213,67 +236,38 @@ public sealed class FlowScopeBootstrap : MonoBehaviour
|
||||
_screenNavigator = screenNavigator;
|
||||
}
|
||||
|
||||
public Task LoadAsync(FeatureContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task EnterAsync(FeatureContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ExitAsync(FeatureContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
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>(Container root, CancellationToken cancellationToken)` 会先解析并加载 `IConfigProvider`。
|
||||
- 初始 Feature 必须实现 `IFeature`,并能从 Feature scope 中解析;示例使用 `_root.RegisterTransient(_ => new ExampleFeature(screenNavigator))`。
|
||||
- 业务资源统一通过 `IResourceService` 或 `IResourceGroup` 加载,不直接在业务层调用 Addressables。
|
||||
- `GameFlow.StartupAsync<TInitialFeature>` 会先解析并加载 `IConfigProvider`。
|
||||
- 初始 Feature 必须实现 `IFeature`,并能从 root 或 Feature scope 中解析。
|
||||
- 业务资源统一通过 `IResourceService` 或 `IResourceGroup` 加载,不直接调用 Addressables。
|
||||
- UI 层使用 `UIManager.RegisterLayer(...)` 建立层级,再由 `UIScreenNavigator` 或业务 Feature 打开面板。
|
||||
- Shutdown 时应调用 `GameFlow.ShutdownAsync(...)`,并释放 UI 预加载资源,例如 `_preloadService.ReleaseAll()`。
|
||||
|
||||
## Addressables 接入说明
|
||||
|
||||
使用 Addressables 时,推荐注册:
|
||||
|
||||
```csharp
|
||||
IResourceService resources = new AddressablesResourceService(
|
||||
new AddressablesResourceBackend());
|
||||
```
|
||||
|
||||
这会把业务层依赖收敛到 `IResourceService`。`AddressablesResourceBackend` 只位于 Addressables 适配程序集,Runtime 不需要知道 Addressables 的存在。
|
||||
|
||||
如果某个系统需要独立资源生命周期,应通过 `resources.CreateGroup()` 创建 `IResourceGroup`,让 Feature 的资源释放跟随 `FeatureContext.Resources.Dispose()`。
|
||||
|
||||
## 常见误用
|
||||
|
||||
- 不要让业务 Feature 直接调用 `UnityEngine.AddressableAssets.Addressables`;应通过 `IResourceService` 或 `IResourceGroup` 访问资源。
|
||||
- 不要把短生命周期对象注册成 `RegisterInstance` 或 `RegisterSingletonFactory`;临时对象优先使用 `RegisterTransient`,需要随 Feature 生命周期释放的对象放在 Feature scope。
|
||||
- 不要跳过 `GameFlow.ShutdownAsync(...)`;它负责退出当前 Feature、释放 Feature scope,并处置 root container。
|
||||
- 不要把 `UIScreenNavigator` 当成完整页面路由系统;它是当前 UI 导航辅助,不负责 URL、跨 Feature 页面恢复或全局路由。
|
||||
- 不要让业务 Feature 直接调用 `UnityEngine.AddressableAssets.Addressables`。
|
||||
- 不要把短生命周期对象注册为 `RegisterInstance` 或 `RegisterSingletonFactory`。
|
||||
- 不要跳过 `GameFlow.ShutdownAsync(...)`。
|
||||
- 不要把 `UIScreenNavigator` 当作完整页面路由系统。
|
||||
- 不要让 `FlowScope.Runtime` 反向依赖 Addressables、Editor、Samples 或 Tests。
|
||||
- 不要在没有注册 UI layer 的情况下打开面板;`UIManager.OpenAsync` 会要求面板声明的 layer 已经通过 `RegisterLayer` 注册。
|
||||
- 不要在未初始化 `AudioService.Initialize(IResourceService, AudioServiceConfig)` 前调用播放接口。
|
||||
- 不要假设 package path 与 Git URL 在 P2 第一阶段已经可安装;当前真实可用形态仍是项目内 `Assets/FlowScope` 开发模式。
|
||||
- 不要在没有注册 UI layer 的情况下打开面板。
|
||||
- 不要在未初始化 `AudioService.Initialize(...)` 前调用播放接口。
|
||||
|
||||
## 当前限制
|
||||
|
||||
P2 第一阶段文档完成后,FlowScope 仍需要后续 worker 或阶段完成以下验证后,才能把 package 安装方式视为可交付:
|
||||
P2 收口前仍需要完成:
|
||||
|
||||
1. 建立真实 package 根目录与 `package.json`。
|
||||
2. 通过本地 package path 在干净 Unity 项目中验证安装。
|
||||
3. 验证 Git URL 安装与依赖解析。
|
||||
4. 将 `MainMenuP0` 从 `Assets/FlowScope/Samples` 整理为可导入 package sample。
|
||||
5. 在干净 Unity 项目中复验 Addressables 测试资源、settings 与 PlayMode 用例;主项目内的真实 load/release 验收记录已完成。
|
||||
6. 保持 generated csproj build 与 Unity Test Runner 记录为 0 error。
|
||||
1. package sample 导入验收。
|
||||
2. package `Tests~` 全量策略和 PlayMode 迁移策略。
|
||||
3. 真正空白 Unity 项目 Git URL 安装复验。
|
||||
4. 下一个 preview tag。
|
||||
5. Unity Test Runner 或明确记录无法执行原因。
|
||||
|
||||
@@ -1,86 +1,88 @@
|
||||
# FlowScope Package 目标结构
|
||||
# FlowScope Package 结构与迁移边界
|
||||
|
||||
日期:2026-05-26
|
||||
日期:2026-06-06
|
||||
|
||||
本文说明 FlowScope P2 阶段的目标 Unity Package 目录形态,以及第一阶段暂不搬迁 `Assets/FlowScope` 的边界。本文只描述目标结构与迁移约束,不代表当前仓库已经完成物理搬迁。
|
||||
本文说明 FlowScope P2 阶段当前 package 结构、仍保留的 Assets 开发模式,以及后续迁移边界。
|
||||
|
||||
## 当前状态
|
||||
|
||||
当前 FlowScope 仍处于项目内开发模式:
|
||||
FlowScope 当前采用双轨结构:
|
||||
|
||||
```text
|
||||
My project/
|
||||
Assets/
|
||||
FlowScope/
|
||||
Runtime/
|
||||
Addressables/
|
||||
Samples/
|
||||
Tests/
|
||||
My project/Assets/FlowScope
|
||||
Packages/com.flowscope.gamecore
|
||||
```
|
||||
|
||||
当前尚未建立标准 Unity Package 根目录,未发现包级 `package.json`、`Samples~`、`Tests~`、`Documentation~` 目录。`MainMenuP0` 示例仍位于 `Assets/FlowScope/Samples/MainMenuP0`。
|
||||
`Assets/FlowScope` 仍是开发工程内的测试和样例源;`Packages/com.flowscope.gamecore` 是面向消费方的 Unity Package 雏形。
|
||||
|
||||
## 目标包根目录
|
||||
|
||||
目标包建议使用独立目录承载,例如:
|
||||
当前 package 目录:
|
||||
|
||||
```text
|
||||
Packages/
|
||||
com.flowscope.gamecore/
|
||||
Packages/com.flowscope.gamecore/
|
||||
package.json
|
||||
Runtime/
|
||||
Addressables/
|
||||
Editor/
|
||||
Samples~/
|
||||
Tests/
|
||||
Tests~/
|
||||
Documentation~/
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
LICENSE.md
|
||||
```
|
||||
|
||||
目录职责如下:
|
||||
已完成:
|
||||
- Runtime 镜像到 `Runtime/`。
|
||||
- Addressables adapter 镜像到 `Addressables/`。
|
||||
- Editor 诊断窗口镜像到 `Editor/`。
|
||||
- MainMenuP0 镜像到 `Samples~/MainMenuP0`。
|
||||
- `Tests~/EditMode` 已建立最小 smoke 测试。
|
||||
- package 顶层 `.meta` 已补齐,避免 immutable package 导入错误。
|
||||
|
||||
未完成:
|
||||
- package `Tests~` 全量迁移策略。
|
||||
- package sample 导入验收。
|
||||
- 空白 Unity 项目安装复验。
|
||||
- 下一个 preview tag。
|
||||
|
||||
## 目录职责
|
||||
|
||||
| 目录 | 职责 |
|
||||
| --- | --- |
|
||||
| `package.json` | Unity Package Manager 元数据,声明包名、显示名、版本、Unity 版本与依赖。 |
|
||||
| `Runtime/` | FlowScope 核心运行时代码,包括 Container、Feature、GameFlow、Resource、Config、Save、Audio、UI 等能力。 |
|
||||
| `package.json` | Unity Package Manager 元数据,声明包名、显示名、版本、Unity 版本、依赖和 sample。 |
|
||||
| `Runtime/` | FlowScope 核心运行时代码,包括 Container、Feature、GameFlow、Resource、Config、Save、Audio、UI。 |
|
||||
| `Addressables/` | Addressables 适配层,提供 `AddressablesResourceBackend` 与 `AddressablesResourceService`。 |
|
||||
| `Editor/` | 只放编辑器诊断、检查、菜单入口等 Editor-only 内容,不承载运行时逻辑。 |
|
||||
| `Samples~/` | Package Manager 可导入示例,例如 `MainMenuP0`。示例可以依赖 Runtime 与所演示的适配包。 |
|
||||
| `Tests/` | 包内测试,按 EditMode 与 PlayMode 分层保留测试 asmdef。 |
|
||||
| `Documentation~/` | Package Manager 文档入口,包含安装、接入、诊断与示例说明。 |
|
||||
| `Editor/` | Editor-only 诊断入口,不承载运行时逻辑,不自动修改用户资源。 |
|
||||
| `Samples~/` | Package Manager 可导入示例。当前包含 `MainMenuP0`。 |
|
||||
| `Tests~/` | package 内测试候选。当前先放最小 EditMode smoke 测试,不替代开发工程内完整测试。 |
|
||||
| `Documentation~/` | Package Manager 文档入口,包含安装和接入说明。 |
|
||||
|
||||
## package.json 建议形态
|
||||
|
||||
目标包元数据建议保持最小可识别:
|
||||
## package.json 当前形态
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "com.flowscope.gamecore",
|
||||
"displayName": "FlowScope Game Core",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.0-preview.2",
|
||||
"unity": "2022.3",
|
||||
"description": "A lightweight Unity game flow, resource, config, save, audio, and UI runtime foundation.",
|
||||
"dependencies": {
|
||||
"com.cysharp.r3": "https://github.com/Cysharp/R3.git?path=src/R3.Unity/Assets/R3.Unity#1.3.0",
|
||||
"com.unity.addressables": "1.21.21",
|
||||
"com.unity.ugui": "1.0.0"
|
||||
},
|
||||
"samples": [
|
||||
{
|
||||
"displayName": "MainMenuP0",
|
||||
"description": "Minimal FlowScope bootstrap, feature, UI, save, and preload sample.",
|
||||
"path": "Samples~/MainMenuP0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
依赖说明:
|
||||
|
||||
- `com.cysharp.r3` 是当前 `FlowScope.Runtime.asmdef` 直接引用 `R3.Unity` 的来源。
|
||||
- `com.unity.addressables` 只服务 `FlowScope.Addressables`,Runtime 不直接依赖 Addressables。
|
||||
- `com.unity.ugui` 服务当前 UI 与示例中的 `Canvas`、`Button`、`Text` 等 UGUI 类型。
|
||||
|
||||
如果后续发布渠道不接受 Git URL 形式依赖,应在发布前改为团队确认过的 OpenUPM、内网 registry 或嵌入式依赖策略。
|
||||
注意:`com.cysharp.r3` 和 `org.nuget.r3` 当前由消费方项目显式声明,原因是 Unity Package Manager 不可靠支持 package 内 Git dependency 传递解析。
|
||||
|
||||
## asmdef 依赖方向
|
||||
|
||||
当前与目标结构都必须保持以下方向:
|
||||
必须保持以下方向:
|
||||
|
||||
```text
|
||||
FlowScope.Runtime
|
||||
@@ -93,45 +95,49 @@ FlowScope.Addressables
|
||||
|
||||
FlowScope.Editor
|
||||
-> FlowScope.Runtime
|
||||
-> 需要诊断的编辑器程序集
|
||||
-> 诊断所需的 Editor 程序集
|
||||
|
||||
FlowScope.Samples.MainMenuP0
|
||||
-> FlowScope.Runtime
|
||||
-> R3.Unity
|
||||
|
||||
FlowScope.Tests.EditMode / FlowScope.Tests.PlayMode
|
||||
-> 被测程序集
|
||||
FlowScope.PackageTests.EditMode
|
||||
-> FlowScope.Runtime
|
||||
-> FlowScope.Addressables
|
||||
-> NUnit / Unity Test Framework
|
||||
```
|
||||
|
||||
禁止反向依赖:
|
||||
|
||||
- `FlowScope.Runtime` 不依赖 `FlowScope.Addressables`。
|
||||
- `FlowScope.Runtime` 不依赖 `FlowScope.Editor`。
|
||||
- `FlowScope.Runtime` 不依赖 `FlowScope.Samples.*`。
|
||||
- `FlowScope.Runtime` 不依赖测试程序集。
|
||||
- Samples 不作为 Runtime 的依赖来源。
|
||||
|
||||
`AddressablesResourceService` 当前命名空间位于 `FlowScope.Resources`,但文件处于 `Assets/FlowScope/Addressables` 并编译进 `FlowScope.Addressables` 程序集;迁移时应保留程序集边界,不应因为命名空间相同而把 Addressables 适配代码并回 Runtime。
|
||||
`AddressablesResourceService` 当前命名空间为 `FlowScope.Resources`,但文件位于 Addressables 适配程序集;迁移和镜像时必须保留程序集边界,不应因为命名空间相同而把 Addressables 适配代码并回 Runtime。
|
||||
|
||||
## 第一阶段暂不搬迁 Assets/FlowScope 的原因
|
||||
## 双轨边界
|
||||
|
||||
P2 第一阶段只建立分发边界、安装接入文档、Addressables 验收与最小 Editor 诊断入口,不做物理搬迁,原因如下:
|
||||
当前不删除 `Assets/FlowScope`,原因:
|
||||
|
||||
1. 当前 `Assets/FlowScope` 已承载 P0/P1 Runtime、Addressables、Samples、Tests 的稳定验证路径。
|
||||
2. `MainMenuP0` 示例、PlayMode 测试与 Unity 生成的 csproj 仍依赖当前 Asset 路径。
|
||||
3. 一次性移动 Runtime、Samples、Tests 会同时改变 asmdef 路径、资源路径、场景引用、prefab 引用与测试发现路径,容易把包结构问题和运行时回归混在一起。
|
||||
4. 第一阶段还有并行 worker 处理 Addressables 验收与 Editor 诊断;Package 文档 worker 不应覆盖或移动他们的文件。
|
||||
5. 先用文档锁定目标结构与依赖方向,可以让第二阶段本地 package 试迁移按批次验证,而不是一次性大搬迁。
|
||||
1. `Assets/FlowScope` 仍承载完整 EditMode / PlayMode 回归测试。
|
||||
2. 主项目内 Addressables 真实 fixture 和 settings 是当前真实验收来源。
|
||||
3. `Samples~/MainMenuP0` 是 package sample 候选,但尚未完成消费方导入验收。
|
||||
4. 一次性把 Samples、Tests 和 Addressables fixture 全部迁入 package 会扩大验证面。
|
||||
|
||||
## 建议迁移顺序
|
||||
当前迁移策略是“镜像 + 逐步验收”:
|
||||
|
||||
后续进入 P2 第二阶段时,建议按以下顺序迁移,每一步都保留独立验证记录:
|
||||
1. package 内保留 Runtime / Addressables / Editor 镜像。
|
||||
2. package sample 先镜像 MainMenuP0,不删除 Assets 样例。
|
||||
3. package tests 先建立最小 smoke 测试,不删除 Assets 测试。
|
||||
4. 待 package 导入、Tests~、空白项目验证完成后,再决定是否把 package 作为源码主线。
|
||||
|
||||
1. 创建 package 根目录与 `package.json`,先不移动 Runtime。
|
||||
2. 将 `Runtime/` 移入包目录并验证 `FlowScope.Runtime` 编译边界。
|
||||
3. 将 `Addressables/` 移入包目录并验证 `FlowScope.Addressables` 仍只依赖 Runtime 与 Unity Addressables。
|
||||
4. 将文档迁入 `Documentation~/` 或同步保留到仓库 `docs/`。
|
||||
5. 将 `MainMenuP0` 作为包示例迁入 `Samples~/MainMenuP0`,验证场景、prefab、配置与测试路径。
|
||||
6. 将 EditMode / PlayMode 测试整理进包内 `Tests/`,验证 Unity Test Runner 发现与执行。
|
||||
## 后续 P2 顺序
|
||||
|
||||
每一步完成前都应验证 generated csproj build、Unity Test Runner 和关键示例路径;如果某一步失败,应先回到该步骤修复,不继续叠加后续搬迁。
|
||||
建议继续按以下顺序收口:
|
||||
|
||||
1. 完成 package `Tests~` 第一刀并验证静态结构。
|
||||
2. 决定哪些测试可迁入 package,哪些仍留在开发工程。
|
||||
3. 在消费方导入 `Samples~/MainMenuP0` 做人工验收。
|
||||
4. 在空白 Unity 项目中复验 Git URL 安装。
|
||||
5. 根据最新 package 内容打下一个 preview tag。
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
真实 Addressables 测试资源:
|
||||
|
||||
- 路径:`Assets/FlowScope/Tests/PlayMode/Resources_moved/Fixtures/Addressables/FlowScopeAddressablesText.txt`
|
||||
- 路径:`Assets/FlowScope/Tests/PlayMode/Resources/Fixtures/Addressables/FlowScopeAddressablesText.txt`
|
||||
- 内容:`FlowScope Addressables Acceptance`
|
||||
- GUID:`7e04733ce9dab74459592d3bd99fcafd`
|
||||
- Addressables group:`Default Local Group`
|
||||
@@ -68,7 +68,7 @@ m_Address: FlowScope.Tests.Addressables.Text
|
||||
|
||||
## 剩余注意事项
|
||||
|
||||
- 当前 Addressables 测试资源位于 `Resources_moved` 路径下;这是本轮真实验收的有效路径,但后续 package 迁移时应统一测试资源目录命名。
|
||||
- 当前 Addressables 测试资源已整理到 `Resources/Fixtures/Addressables` 路径下,为后续 package `Tests~` 迁移去掉临时目录命名。
|
||||
- `Assets/FlowScope/Tests/PlayMode/Resources/Fixtures` 当前只剩空目录 meta,属于提交前清理候选。
|
||||
- 当前验收仍在主项目内完成;P2 后续 package 阶段还需要在干净 Unity 项目中验证本地 package path 或 Git URL 安装。
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
剩余缺口:
|
||||
|
||||
- 当前 Addressables 测试资源路径为 `Assets/FlowScope/Tests/PlayMode/Resources_moved/Fixtures/Addressables/FlowScopeAddressablesText.txt`,后续 package 迁移时应统一测试资源目录命名。
|
||||
- 当前 Addressables 测试资源路径为 `Assets/FlowScope/Tests/PlayMode/Resources/Fixtures/Addressables/FlowScopeAddressablesText.txt`,早期临时目录命名已在 P2 第二阶段整理。
|
||||
- `Assets/FlowScope/Tests/PlayMode/Resources/Fixtures` 当前只剩空目录 meta,属于提交前清理候选。
|
||||
|
||||
### 4. Editor 最小诊断入口
|
||||
@@ -165,7 +165,7 @@ git diff --check
|
||||
1. 尚未创建正式 package 根目录和 `package.json` 文件。
|
||||
2. 尚未在干净 Unity 项目中验证本地 package path 或 Git URL 安装。
|
||||
3. 尚未实际打开 `Tools/FlowScope/Diagnostics` 做人工 UI 检查。
|
||||
4. Addressables 测试资源目录命名仍需在 package 迁移前统一。
|
||||
4. Addressables 测试资源目录命名已统一到 `Resources/Fixtures/Addressables`,后续仍需决定是否随 Tests 迁移到 package `Tests~`。
|
||||
|
||||
## 下一阶段建议
|
||||
|
||||
@@ -176,5 +176,5 @@ git diff --check
|
||||
1. 创建 `Packages/com.flowscope.gamecore` 和最小 `package.json`。
|
||||
2. 先复制或镜像 Runtime 到 package 目录,不删除 `Assets/FlowScope`。
|
||||
3. 在当前项目和一个干净 Unity 项目中验证本地 package path。
|
||||
4. 整理 Addressables 测试资源目录,再迁移 package 内 Tests。
|
||||
4. 基于已整理的 Addressables fixture 路径,迁移或镜像 package 内 Tests。
|
||||
5. 再考虑 `Samples~/MainMenuP0` 和 Tests 的分批迁移。
|
||||
|
||||
182
docs/reviews/p2-phase2-package-preview-report.md
Normal file
182
docs/reviews/p2-phase2-package-preview-report.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# FlowScope P2 第二阶段 Package Preview 记录
|
||||
|
||||
日期:2026-06-04
|
||||
|
||||
## 本轮范围
|
||||
|
||||
本轮承接 P2 第一阶段后的 package 试迁移工作,重点不是改 Runtime API,而是把 FlowScope Git package 从“仓库内结构存在”推进到“真实消费方可用”的 preview 状态。
|
||||
|
||||
## 已完成事项
|
||||
|
||||
1. FlowScope package preview tag
|
||||
- tag:`v0.2.0-preview.1`
|
||||
- 提交:`31641b2c35d1d6c4f056772f144562007f0d0782`
|
||||
- 远端:`ssh://git@git.zz.com:2222/Developer/FlowScope.git`
|
||||
|
||||
2. 消费方 Git package 接入
|
||||
- 消费项目:`C:\Users\13999\Documents\FishingFrenzy\FishingFrenzy`
|
||||
- package URL:`ssh://git@git.zz.com:2222/Developer/FlowScope.git?path=/Packages/com.flowscope.gamecore#v0.2.0-preview.1`
|
||||
- R3 依赖:同时保留 `com.cysharp.r3` 与 `org.nuget.r3`。
|
||||
- Unity NuGet registry:`https://unitynuget-registry.openupm.com`,scope 为 `org.nuget`。
|
||||
|
||||
3. 最小消费方验证脚本
|
||||
- 文件:`FishingFrenzy/Assets/Scripts/FlowScopePackageSmokeProbe.cs`
|
||||
- 覆盖:编译期引用 `FlowScope.Container.Container`、`FlowScope.Flow.GameFlow`、`FlowScope.Addressables.AddressablesResourceBackend` 与 `FlowScope.Resources.IResourceService`。
|
||||
- 运行方式:挂到临时 GameObject 后,通过组件右键菜单 `FlowScope/Validate Package References` 手动执行。
|
||||
- 设计边界:不自动挂场景、不自动启动、不加载真实业务资源,只证明消费方工程能引用 package API。
|
||||
|
||||
4. 消费方验证结果
|
||||
- UnitySkills `debug_check_compilation`:`isCompiling = false`,`isUpdating = false`。
|
||||
- UnitySkills `debug_get_errors`:`count = 0`。
|
||||
- UnitySkills `console_get_stats`:`errors = 0`。
|
||||
- UnitySkills `package_list`:可见 `com.flowscope.gamecore`,版本为 `0.2.0-preview.1`。
|
||||
|
||||
5. 安装文档同步
|
||||
- `Packages/com.flowscope.gamecore/README.md`
|
||||
- `Packages/com.flowscope.gamecore/Documentation~/installation.md`
|
||||
- 已改为正式 SSH URL、preview tag、Unity NuGet registry 和 `org.nuget.r3` 说明。
|
||||
|
||||
## 本轮踩坑记录
|
||||
|
||||
1. FlowScope remote 曾残留旧仓库地址
|
||||
- 本地 `origin` 一度指向旧的 `BombGameFramework` 地址。
|
||||
- 已改为 `ssh://git@git.zz.com:2222/Developer/FlowScope.git` 后再推送 tag。
|
||||
|
||||
2. R3 Git package 不等于完整 R3 依赖
|
||||
- `com.cysharp.r3` 提供 Unity 适配层。
|
||||
- `R3.dll` 与 BCL DLL 需要由 `org.nuget.r3` 及其 NuGet 传递依赖提供。
|
||||
|
||||
3. Git package 必须保留 Unity `.meta`
|
||||
- preview tag 基于已补齐 package 顶层 `.meta` 的提交。
|
||||
- 缺少 `.meta` 时,消费方会出现 immutable package 相关导入错误。
|
||||
|
||||
## 剩余 P2 第二阶段事项
|
||||
|
||||
1. 当前 preview tag 已可被消费方解析,但 tag 内 package 文档仍是打 tag 当时的快照。后续如果要让 package 内文档也随 tag 发布,应在文档修正后创建下一个 preview tag。
|
||||
2. 已在后续切片镜像 `Samples~/MainMenuP0`,但尚未在消费方导入 package sample 做人工验收。
|
||||
3. 已在后续切片建立 package `Tests~/EditMode` 第一刀 smoke 测试,但尚未迁移完整 Runtime/EditMode/PlayMode 测试。
|
||||
4. Addressables 测试资源已统一到主项目测试目录的 `Resources/Fixtures/Addressables`,后续迁移 Tests 前需要决定是否镜像到 package `Tests~`。
|
||||
5. 仍需在一个真正空白 Unity 项目中重复 Git package 接入;FishingFrenzy 是真实消费方,但不是空白项目。
|
||||
|
||||
## 2026-06-06 验证补充
|
||||
|
||||
本轮按 P2 收尾顺序补做以下检查:
|
||||
|
||||
1. Addressables 测试资源 entry
|
||||
- `Default Local Group.asset` 曾出现本地未提交改动,内容为清空 `FlowScope.Tests.Addressables.Text` 对应 entry。
|
||||
- 已确认该改动不是预期变更,并恢复到干净工作树;当前 `git status` 无该文件 diff。
|
||||
|
||||
2. 静态与生成 csproj 编译验证
|
||||
- `Packages/com.flowscope.gamecore/package.json` 可被 `ConvertFrom-Json` 解析。
|
||||
- `Packages/com.flowscope.gamecore/Tests~/EditMode/FlowScope.PackageTests.EditMode.asmdef` 可被 `ConvertFrom-Json` 解析。
|
||||
- `FlowScope.Runtime.csproj`、`FlowScope.Addressables.csproj`、`FlowScope.Tests.EditMode.csproj`、`FlowScope.Tests.PlayMode.csproj` 均可 `dotnet build --no-restore` 成功。
|
||||
- 编译仍有 Unity 生成 csproj 常见的 `MSB3277` 引用版本冲突 warning,但无错误。
|
||||
|
||||
3. Unity Test Runner
|
||||
- UnitySkills `http://127.0.0.1:8090/health` 当前不可连接。
|
||||
- 去掉 `-quit` 后重跑 Unity batchmode EditMode Test Runner,已生成 `C:\tmp\flowscope-p2-editmode-retry3-results.xml`。
|
||||
- 结果:`testcasecount=56`,`result=Passed`,`passed=56`,`failed=0`,`skipped=0`。
|
||||
- 注意:Unity 在保存结果后未自动退出,已手动停止残留 batchmode 进程;这不影响本次 XML 结果判定。
|
||||
|
||||
4. 空白 Unity 项目 Git URL 复验
|
||||
- 使用 `C:\tmp\flowscope-upm-probe-p2` 手工创建最小 probe 并写入 Git package manifest 后,Unity batchmode 能启动,但 UPM 在更新 manifest 时失败:`The "path" argument must be of type string. Received undefined`,未生成 `packages-lock.json`。
|
||||
- 该 probe 不是由 Unity Hub/template 正式创建的完整项目,因此本轮结果不能判定为 FlowScope package 安装失败,只能记录为“手工最小 probe 不足以完成空白项目 gate”。
|
||||
- 后续仍需使用一个真正由 Unity 创建的空白项目复验 Git URL 安装。
|
||||
|
||||
## 2026-06-06 空白项目复验补充
|
||||
|
||||
用户已在仓库内创建完整 Unity 空白项目 `TestFlowScopePackage`,本轮将该目录加入根 `.gitignore`,避免误提交验证工程。
|
||||
|
||||
验证步骤与结果:
|
||||
|
||||
1. 在 `TestFlowScopePackage/Packages/manifest.json` 写入 FlowScope Git package、R3 Git package、Unity NuGet scoped registry 和 `org.nuget.r3`。
|
||||
2. Unity batchmode 打开项目时 UPM 失败:`The "path" argument must be of type string. Received undefined`,未生成可用的 FlowScope package lock。
|
||||
3. 为定位原因,分别移除 FlowScope、R3 Git package、`org.nuget.r3`、scoped registry 做隔离验证。
|
||||
4. 清理 `Library`、`Temp` 和 `Packages/packages-lock.json` 后,使用不含 FlowScope/R3/NuGet 的原始 manifest 重跑,仍出现同一个 UPM resolve 错误。
|
||||
|
||||
当前判断:该错误不是 FlowScope Git package 本身触发;`TestFlowScopePackage` 在原始 manifest 下也无法完成 UPM resolve。空白项目 gate 需要先在 Unity GUI 中确认该项目自身 Package Manager 可正常打开且不报错,再继续验证 FlowScope Git package。
|
||||
|
||||
## 2026-06-06 空白项目复验通过
|
||||
|
||||
用户使用 Unity GUI 打开并关闭 `TestFlowScopePackage` 后,项目自身 UPM 状态恢复正常;随后继续复验 FlowScope Git package。
|
||||
|
||||
验证结果:
|
||||
|
||||
1. `Packages/packages-lock.json` 已解析出:
|
||||
- `com.flowscope.gamecore`
|
||||
- source:`git`
|
||||
- version:`ssh://git@git.zz.com:2222/Developer/FlowScope.git?path=/Packages/com.flowscope.gamecore#v0.2.0-preview.1`
|
||||
- hash:`31641b2c35d1d6c4f056772f144562007f0d0782`
|
||||
- `com.cysharp.r3`
|
||||
- source:`git`
|
||||
- hash:`329ca7915713417f2e0837b0e0a80b4da074db4a`
|
||||
- `org.nuget.r3`
|
||||
- source:`registry`
|
||||
- version:`1.3.0`
|
||||
2. Unity batchmode 导入验证通过:
|
||||
- log:`C:\tmp\flowscope-testpackage-import-aftergui.log`
|
||||
- exit code:`0`
|
||||
- 日志可见 `com.flowscope.gamecore@...#v0.2.0-preview.1` 被注册到 `Library/PackageCache/com.flowscope.gamecore@31641b2c35`
|
||||
3. 空白消费方 smoke probe 编译验证通过:
|
||||
- 文件:`TestFlowScopePackage/Assets/FlowScopePackageSmokeProbe.cs`
|
||||
- log:`C:\tmp\flowscope-testpackage-smoke-compile2.log`
|
||||
- exit code:`0`
|
||||
- probe 覆盖 `FlowScope.Container.Container`、`FlowScope.Resources.ResourceService`、`FlowScope.Resources.IResourceService`、`FlowScope.Addressables.AddressablesResourceBackend`。
|
||||
|
||||
非阻塞注意事项:
|
||||
|
||||
- batchmode 日志提示 `Packages/com.flowscope.gamecore/Documentation~` 对应 `.meta` 存在但目录缺失,Unity 自动创建了该空目录。下一个 preview tag 前建议补齐 `Documentation~` 实际内容或移除空目录 `.meta`,避免消费方导入时出现该提示。
|
||||
|
||||
## 2026-06-06 当前提交导入与 sample 验收尝试
|
||||
|
||||
为验证下一个 preview tag 是否能消除 `Documentation~` 空目录提示,临时将忽略工程 `TestFlowScopePackage` 的 manifest 指向当前提交:
|
||||
|
||||
- `ssh://git@git.zz.com:2222/Developer/FlowScope.git?path=/Packages/com.flowscope.gamecore#e6d1f00e235a6d7cd81d84cbe9a1f8be2d27b89e`
|
||||
|
||||
验证观察:
|
||||
|
||||
1. Unity 默认 `Editor.log` 可见 `Packages/com.flowscope.gamecore/Documentation~` 被正常导入,未再出现 `Documentation~.meta` 存在但目录缺失的提示。
|
||||
2. Unity 默认 `Editor.log` 可见 `Packages/com.flowscope.gamecore/Runtime`、`Addressables`、`Editor`、`README.md`、`CHANGELOG.md`、`package.json` 等内容被导入。
|
||||
3. 未发现 `error CS`、`Compilation failed`、`Scripts have compiler errors` 等编译错误日志。
|
||||
|
||||
未完成项:
|
||||
|
||||
- 尝试通过 `FlowScopeSampleImportVerifier.ImportMainMenuP0` 自动导入 `Samples~/MainMenuP0`,但 `TestFlowScopePackage` 的 batchmode 没有稳定生成验证脚本对应的 Editor 程序集,也没有执行到 sample import 日志。
|
||||
- 因此本轮只能确认“当前提交已补齐 `Documentation~` 导入内容”,不能宣称 `Samples~/MainMenuP0` 消费方导入验收通过。
|
||||
|
||||
后续处理:
|
||||
|
||||
1. 下一个 preview tag 可以覆盖 `Documentation~` 空目录 warning。
|
||||
2. `Samples~/MainMenuP0` 仍需要在 Unity GUI 的 Package Manager 中人工导入,或在验证工程稳定生成 Editor 程序集后再跑自动导入脚本。
|
||||
|
||||
## 2026-06-08 MainMenuP0 手动导入与验证脚本修正
|
||||
|
||||
用户已在 `TestFlowScopePackage` 通过 Unity GUI 手动导入 `Samples~/MainMenuP0`,本地可见导入目录:
|
||||
|
||||
- `TestFlowScopePackage/Assets/Samples/FlowScope Game Core/0.2.0-preview.1/MainMenuP0`
|
||||
|
||||
导入后曾出现两个编译错误,来源均为忽略工程中的临时验证脚本 `Assets/Editor/FlowScopeSampleImportVerifier.cs`,不是 FlowScope package 本体:
|
||||
|
||||
1. `CS0104: 'PackageInfo' is an ambiguous reference between 'UnityEditor.PackageManager.PackageInfo' and 'UnityEditor.PackageInfo'`
|
||||
- 修正:使用完整类型名 `UnityEditor.PackageManager.PackageInfo.FindForAssetPath(...)`。
|
||||
2. `CS0019: Operator '==' cannot be applied to operands of type 'Sample' and '<null>'`
|
||||
- 修正:`UnityEditor.PackageManager.UI.Sample` 不能按 `null` 判断;改用 `string.IsNullOrEmpty(sample.displayName)` 判断是否找到 sample。
|
||||
|
||||
当前状态:
|
||||
|
||||
- `MainMenuP0` sample 已完成手动导入。
|
||||
- 临时验证脚本已在 `TestFlowScopePackage` 内修正。
|
||||
- Unity batchmode 在当前机器状态下仍未稳定生成指定 log 并退出,因此还不能把自动 batchmode sample 验收记为通过。
|
||||
|
||||
## 2026-06-08 MainMenuP0 手动导入验收通过
|
||||
|
||||
用户重新打开 `TestFlowScopePackage` 后确认:
|
||||
|
||||
- Unity Console 已无 `FlowScopeSampleImportVerifier.cs` 的 `CS0104` / `CS0019` 编译错误。
|
||||
- `Samples~/MainMenuP0` 已通过 Unity GUI 手动导入。
|
||||
|
||||
本轮判定:
|
||||
|
||||
- `Samples~/MainMenuP0` 消费方手动导入验收通过。
|
||||
- 自动 batchmode sample import 仍不作为通过项记录,因为当前机器状态下 batchmode 未稳定生成指定 log 并退出。
|
||||
- P2 package preview 可以进入 `0.2.0-preview.2` 版本收口;该版本覆盖空白项目 Git URL 安装、smoke probe 编译、`Documentation~` 内容补齐、`Tests~/EditMode` 第一刀和 `MainMenuP0` 手动导入验收。
|
||||
Reference in New Issue
Block a user