镜像 MainMenuP0 包样例

This commit is contained in:
JSD\13999
2026-06-04 20:16:28 +08:00
parent 3b3957fb74
commit ac8fcc3c24
29 changed files with 1548 additions and 2 deletions

View File

@@ -34,5 +34,6 @@ Add the package to the consuming project's `Packages/manifest.json`:
- `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.
Runtime must not depend on Addressables, Editor, Samples, or Tests.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 49d9c024747f4af0b560e21d931a7816
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dffe7921ed374f83b8077b49644bc744
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 76085fdd1da44ab08b69395b17064b59
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"Gold": 0
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: bd391f0c499b42a3a264d0a9de2483f8
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: eb3b4bf3dfdc46e4a9352fc2203bbfd4
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c88fe653c15d473cac0029539a2ff355
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 05cda67df190bb746b57ab6f944469c5
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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`.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9df9d94a34fa4ec2b16df2c8cf2a651b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 946526d21b06440688088de21f383782
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 03630d34cf304668afe00ab221f3557e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 93879cc02e2644039382a2006c5434b1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cd97432f2838454cbbf2d19dbe14d7f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b015663928874766b56036f39f0aca6d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a641bd6447847f2af2cd5af40305a5a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 68e2343c920540a280ac2718ceae12ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
using R3;
namespace FlowScope.Samples.MainMenuP0
{
public sealed class PlayerData
{
public ReactiveProperty<int> Gold { get; set; } = new(0);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d62814450dc46fba84df1fba2a372f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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"
}
]
}

View File

@@ -53,7 +53,7 @@
## 剩余 P2 第二阶段事项
1. 当前 preview tag 已可被消费方解析,但 tag 内 package 文档仍是打 tag 当时的快照。后续如果要让 package 内文档也随 tag 发布,应在文档修正后创建下一个 preview tag。
2. 还没有迁移 `Samples~/MainMenuP0`
2. 已在后续切片镜像 `Samples~/MainMenuP0`,但尚未在消费方导入 package sample 做人工验收
3. 还没有迁移 package 内 `Tests`
4. Addressables 测试资源仍位于主项目测试目录,后续迁移 Tests 前需要统一 fixture 路径。
5. 仍需在一个真正空白 Unity 项目中重复 Git package 接入FishingFrenzy 是真实消费方,但不是空白项目。