Implement MVP for the statue decoration minigame (#65)
MVP implemented with: - placing, removing etc. decorations - saving the state, displaying it on the map, restoring when game restarts - saving screenshots to folder on device Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: #65
This commit is contained in:
@@ -13,6 +13,7 @@ MonoBehaviour:
|
|||||||
m_Name: AddressableAssetGroupSortSettings
|
m_Name: AddressableAssetGroupSortSettings
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
sortOrder:
|
sortOrder:
|
||||||
|
- b58f7c15a2ffcbc4483cb2bd0a0dee90
|
||||||
- 21420e71d44619f468badaed8efc42a7
|
- 21420e71d44619f468badaed8efc42a7
|
||||||
- 0d5d36d6da388314b92b9c6967d23f39
|
- 0d5d36d6da388314b92b9c6967d23f39
|
||||||
- 75e1f68b7bf77f34f8ad4aeab74d4244
|
- 75e1f68b7bf77f34f8ad4aeab74d4244
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ MonoBehaviour:
|
|||||||
m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077
|
m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077
|
||||||
m_currentHash:
|
m_currentHash:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Hash: 027dc5937f1a989438d4c39629cbe46f
|
Hash: b9c3c669d28f187e4cec7b0693d59a99
|
||||||
m_OptimizeCatalogSize: 0
|
m_OptimizeCatalogSize: 0
|
||||||
m_BuildRemoteCatalog: 0
|
m_BuildRemoteCatalog: 0
|
||||||
m_CatalogRequestsTimeout: 0
|
m_CatalogRequestsTimeout: 0
|
||||||
@@ -65,6 +65,7 @@ MonoBehaviour:
|
|||||||
- {fileID: 11400000, guid: efe7e1728e73e9546ac5dfee2eff524f, type: 2}
|
- {fileID: 11400000, guid: efe7e1728e73e9546ac5dfee2eff524f, type: 2}
|
||||||
- {fileID: 11400000, guid: 6e4927e7e19eef34b93dc2baa9e9e8e2, type: 2}
|
- {fileID: 11400000, guid: 6e4927e7e19eef34b93dc2baa9e9e8e2, type: 2}
|
||||||
- {fileID: 11400000, guid: 4186fdd83f912a14b97fbf4644266b0d, type: 2}
|
- {fileID: 11400000, guid: 4186fdd83f912a14b97fbf4644266b0d, type: 2}
|
||||||
|
- {fileID: 11400000, guid: fd8b6934620e3a54a818495e62a103e0, type: 2}
|
||||||
- {fileID: 11400000, guid: e25c7672a65b5974bb354fcfb2a8400c, type: 2}
|
- {fileID: 11400000, guid: e25c7672a65b5974bb354fcfb2a8400c, type: 2}
|
||||||
- {fileID: 11400000, guid: 7fcc03e584505ed4381983b6ebb1179d, type: 2}
|
- {fileID: 11400000, guid: 7fcc03e584505ed4381983b6ebb1179d, type: 2}
|
||||||
m_BuildSettings:
|
m_BuildSettings:
|
||||||
@@ -107,6 +108,7 @@ MonoBehaviour:
|
|||||||
m_LabelNames:
|
m_LabelNames:
|
||||||
- default
|
- default
|
||||||
- BlokkemonCard
|
- BlokkemonCard
|
||||||
|
- StatueDecorations
|
||||||
m_SchemaTemplates: []
|
m_SchemaTemplates: []
|
||||||
m_GroupTemplateObjects:
|
m_GroupTemplateObjects:
|
||||||
- {fileID: 11400000, guid: ea0a5135f5495eb4693a23d94617fe92, type: 2}
|
- {fileID: 11400000, guid: ea0a5135f5495eb4693a23d94617fe92, type: 2}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
|
||||||
|
m_Name: StatueDecorations_BundledAssetGroupSchema
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Group: {fileID: 11400000, guid: fd8b6934620e3a54a818495e62a103e0, type: 2}
|
||||||
|
m_InternalBundleIdMode: 1
|
||||||
|
m_Compression: 1
|
||||||
|
m_IncludeAddressInCatalog: 1
|
||||||
|
m_IncludeGUIDInCatalog: 1
|
||||||
|
m_IncludeLabelsInCatalog: 1
|
||||||
|
m_InternalIdNamingMode: 0
|
||||||
|
m_CacheClearBehavior: 0
|
||||||
|
m_IncludeInBuild: 1
|
||||||
|
m_BundledAssetProviderType:
|
||||||
|
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
|
||||||
|
m_StripDownloadOptions: 0
|
||||||
|
m_ForceUniqueProvider: 0
|
||||||
|
m_UseAssetBundleCache: 1
|
||||||
|
m_UseAssetBundleCrc: 1
|
||||||
|
m_UseAssetBundleCrcForCachedBundles: 1
|
||||||
|
m_UseUWRForLocalBundles: 0
|
||||||
|
m_Timeout: 0
|
||||||
|
m_ChunkedTransfer: 0
|
||||||
|
m_RedirectLimit: -1
|
||||||
|
m_RetryCount: 0
|
||||||
|
m_BuildPath:
|
||||||
|
m_Id: c258e2bcd3e8ac742ba98f152c0e6322
|
||||||
|
m_LoadPath:
|
||||||
|
m_Id: 88323bf0b2ef98446961cedd6232bd47
|
||||||
|
m_BundleMode: 0
|
||||||
|
m_AssetBundleProviderType:
|
||||||
|
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
|
||||||
|
m_UseDefaultSchemaSettings: 0
|
||||||
|
m_SelectedPathPairIndex: 0
|
||||||
|
m_BundleNaming: 0
|
||||||
|
m_AssetLoadMode: 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5acac321571c9e84f8b97709b80b77ea
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
|
||||||
|
m_Name: StatueDecorations_ContentUpdateGroupSchema
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Group: {fileID: 11400000, guid: fd8b6934620e3a54a818495e62a103e0, type: 2}
|
||||||
|
m_StaticContent: 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 17c82ac3ce25c6b4e90e5e7e0e3cc941
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
|
||||||
|
m_Name: StatueDecorations
|
||||||
|
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.AddressableAssetGroup
|
||||||
|
m_GroupName: StatueDecorations
|
||||||
|
m_GUID: b58f7c15a2ffcbc4483cb2bd0a0dee90
|
||||||
|
m_SerializeEntries:
|
||||||
|
- m_GUID: 2ea75de9ff6dbfb4b8c246a654868479
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 4101d48e428899d409df02f24c83571f
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 7.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 5efa934e009bc234e920904b05db3c2f
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 2.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 8819ec8b1f4910a4494755cf043636d1
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 3.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 8838477f768600848813a215ab6a46fe
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 5.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: a5d493c2c7c9cf74cab038023b401273
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 9.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: b01ee8334ee052b4784225337e9a5ece
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 8.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: b09b79db8ef15144bb2138ec59f26a9c
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 4.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: ca949a6208ce5b5488e90ea3e2eed6df
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 1.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: f0df83df3cff9d84ba9fd4895e5d1b58
|
||||||
|
m_Address: Assets/Data/Minigames/StatueDressup/TestDecorationData 6.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- StatueDecorations
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_Settings: {fileID: 11400000, guid: 11da9bb90d9dd5848b4f7629415a6937, type: 2}
|
||||||
|
m_SchemaSet:
|
||||||
|
m_Schemas:
|
||||||
|
- {fileID: 11400000, guid: 5acac321571c9e84f8b97709b80b77ea, type: 2}
|
||||||
|
- {fileID: 11400000, guid: 17c82ac3ce25c6b4e90e5e7e0e3cc941, type: 2}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fd8b6934620e3a54a818495e62a103e0
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -122,6 +122,32 @@ TextureImporter:
|
|||||||
ignorePlatformSupport: 0
|
ignorePlatformSupport: 0
|
||||||
androidETC2FallbackOverride: 0
|
androidETC2FallbackOverride: 0
|
||||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WindowsStoreApps
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
spriteSheet:
|
spriteSheet:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
sprites:
|
sprites:
|
||||||
@@ -129,12 +155,12 @@ TextureImporter:
|
|||||||
name: DecorationMinigame_0
|
name: DecorationMinigame_0
|
||||||
rect:
|
rect:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 143
|
x: 0
|
||||||
y: 59
|
y: 0
|
||||||
width: 701
|
width: 962
|
||||||
height: 983
|
height: 1109
|
||||||
alignment: 0
|
alignment: 0
|
||||||
pivot: {x: 0, y: 0}
|
pivot: {x: 0.5, y: 0.5}
|
||||||
border: {x: 0, y: 0, z: 0, w: 0}
|
border: {x: 0, y: 0, z: 0, w: 0}
|
||||||
customData:
|
customData:
|
||||||
outline: []
|
outline: []
|
||||||
@@ -151,7 +177,7 @@ TextureImporter:
|
|||||||
customData:
|
customData:
|
||||||
physicsShape: []
|
physicsShape: []
|
||||||
bones: []
|
bones: []
|
||||||
spriteID:
|
spriteID: 99610a41e95434e42bc921e55c481562
|
||||||
internalID: 0
|
internalID: 0
|
||||||
vertices: []
|
vertices: []
|
||||||
indices:
|
indices:
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ MonoBehaviour:
|
|||||||
targetLevelSceneName: StatueDecoration
|
targetLevelSceneName: StatueDecoration
|
||||||
targetMinigameSceneName:
|
targetMinigameSceneName:
|
||||||
description: Level loading for Quarry
|
description: Level loading for Quarry
|
||||||
mapSprite: {fileID: 3001265211797695019, guid: afdc182c0c32e8f4c9ab04187ea11042, type: 3}
|
mapSprite: {fileID: -9176826819293939900, guid: 7b00cd0000ef4424985cd21fb4f197ee, type: 3}
|
||||||
menuSprite: {fileID: 6629659431539980573, guid: 965b29ed7760d9442b031ee4190b49d4, type: 3}
|
menuSprite: {fileID: 6629659431539980573, guid: 965b29ed7760d9442b031ee4190b49d4, type: 3}
|
||||||
minigameMenuSprite: {fileID: 6629659431539980573, guid: 965b29ed7760d9442b031ee4190b49d4, type: 3}
|
minigameMenuSprite: {fileID: 6629659431539980573, guid: 965b29ed7760d9442b031ee4190b49d4, type: 3}
|
||||||
|
|||||||
@@ -1168,6 +1168,7 @@ MonoBehaviour:
|
|||||||
eagleEye: {fileID: 8093509920149135307}
|
eagleEye: {fileID: 8093509920149135307}
|
||||||
ramaSjangButton: {fileID: 4599222264323240281}
|
ramaSjangButton: {fileID: 4599222264323240281}
|
||||||
scrabBookButton: {fileID: 2880351836456325619}
|
scrabBookButton: {fileID: 2880351836456325619}
|
||||||
|
pauseButton: {fileID: 4413855344712201574}
|
||||||
cinematicSprites: {fileID: 0}
|
cinematicSprites: {fileID: 0}
|
||||||
cinematicBackgroundSprites: {fileID: 0}
|
cinematicBackgroundSprites: {fileID: 0}
|
||||||
currentCinematicPlayer: {fileID: 0}
|
currentCinematicPlayer: {fileID: 0}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ RectTransform:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||||
m_AnchorMax: {x: 0, y: 0}
|
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: 0, y: 0}
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
@@ -102,4 +102,4 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.DragDrop.DecorationDraggableInstance
|
m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.DragDrop.DecorationDraggableInstance
|
||||||
decorationImage: {fileID: 597267714783345863}
|
decorationImage: {fileID: 597267714783345863}
|
||||||
canvasGroup: {fileID: 0}
|
canvasGroup: {fileID: 3617977973382190563}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -468,6 +468,11 @@ PrefabInstance:
|
|||||||
addedObject: {fileID: 706319199}
|
addedObject: {fileID: 706319199}
|
||||||
m_AddedComponents: []
|
m_AddedComponents: []
|
||||||
m_SourcePrefab: {fileID: 100100000, guid: 539b408cd1191614abdcd99506f1157d, type: 3}
|
m_SourcePrefab: {fileID: 100100000, guid: 539b408cd1191614abdcd99506f1157d, type: 3}
|
||||||
|
--- !u!1 &175866977 stripped
|
||||||
|
GameObject:
|
||||||
|
m_CorrespondingSourceObject: {fileID: 7404622075362872657, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 1460027141}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
--- !u!1 &201656041
|
--- !u!1 &201656041
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1971,6 +1976,96 @@ BoxCollider2D:
|
|||||||
m_AutoTiling: 0
|
m_AutoTiling: 0
|
||||||
m_Size: {x: 20.1, y: 10.9}
|
m_Size: {x: 20.1, y: 10.9}
|
||||||
m_EdgeRadius: 0
|
m_EdgeRadius: 0
|
||||||
|
--- !u!1 &831113524
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 831113525}
|
||||||
|
- component: {fileID: 831113526}
|
||||||
|
m_Layer: 10
|
||||||
|
m_Name: MinigameIcon
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &831113525
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 831113524}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: -2.83, z: 0}
|
||||||
|
m_LocalScale: {x: 1.9999999, y: 1.9999999, z: 1.9999999}
|
||||||
|
m_ConstrainProportionsScale: 1
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 1460027142}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!212 &831113526
|
||||||
|
SpriteRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 831113524}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_CastShadows: 0
|
||||||
|
m_ReceiveShadows: 0
|
||||||
|
m_DynamicOccludee: 1
|
||||||
|
m_StaticShadowCaster: 0
|
||||||
|
m_MotionVectors: 1
|
||||||
|
m_LightProbeUsage: 1
|
||||||
|
m_ReflectionProbeUsage: 1
|
||||||
|
m_RayTracingMode: 0
|
||||||
|
m_RayTraceProcedural: 0
|
||||||
|
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||||
|
m_RayTracingAccelStructBuildFlags: 1
|
||||||
|
m_SmallMeshCulling: 1
|
||||||
|
m_ForceMeshLod: -1
|
||||||
|
m_MeshLodSelectionBias: 0
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_RendererPriority: 0
|
||||||
|
m_Materials:
|
||||||
|
- {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2}
|
||||||
|
m_StaticBatchInfo:
|
||||||
|
firstSubMesh: 0
|
||||||
|
subMeshCount: 0
|
||||||
|
m_StaticBatchRoot: {fileID: 0}
|
||||||
|
m_ProbeAnchor: {fileID: 0}
|
||||||
|
m_LightProbeVolumeOverride: {fileID: 0}
|
||||||
|
m_ScaleInLightmap: 1
|
||||||
|
m_ReceiveGI: 1
|
||||||
|
m_PreserveUVs: 0
|
||||||
|
m_IgnoreNormalsForChartDetection: 0
|
||||||
|
m_ImportantGI: 0
|
||||||
|
m_StitchLightmapSeams: 1
|
||||||
|
m_SelectedEditorRenderState: 0
|
||||||
|
m_MinimumChartSize: 4
|
||||||
|
m_AutoUVMaxDistance: 0.5
|
||||||
|
m_AutoUVMaxAngle: 89
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_GlobalIlluminationMeshLod: 0
|
||||||
|
m_SortingLayerID: 0
|
||||||
|
m_SortingLayer: 0
|
||||||
|
m_SortingOrder: 1
|
||||||
|
m_Sprite: {fileID: -9176826819293939900, guid: 7b00cd0000ef4424985cd21fb4f197ee, type: 3}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_FlipX: 0
|
||||||
|
m_FlipY: 0
|
||||||
|
m_DrawMode: 0
|
||||||
|
m_Size: {x: 5.55, y: 6.76}
|
||||||
|
m_AdaptiveModeThreshold: 0.5
|
||||||
|
m_SpriteTileMode: 0
|
||||||
|
m_WasSpriteAssigned: 1
|
||||||
|
m_MaskInteraction: 0
|
||||||
|
m_SpriteSortPoint: 0
|
||||||
--- !u!1 &948124904
|
--- !u!1 &948124904
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -2990,6 +3085,10 @@ PrefabInstance:
|
|||||||
propertyPath: switchData
|
propertyPath: switchData
|
||||||
value:
|
value:
|
||||||
objectReference: {fileID: 11400000, guid: f2a2529c094015a4f974b2ec0c12cc70, type: 2}
|
objectReference: {fileID: 11400000, guid: f2a2529c094015a4f974b2ec0c12cc70, type: 2}
|
||||||
|
- target: {fileID: 634188539509474598, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
|
propertyPath: iconRenderer
|
||||||
|
value:
|
||||||
|
objectReference: {fileID: 831113526}
|
||||||
- target: {fileID: 634188539509474598, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 634188539509474598, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: startUnlocked
|
propertyPath: startUnlocked
|
||||||
value: 1
|
value: 1
|
||||||
@@ -2997,22 +3096,22 @@ PrefabInstance:
|
|||||||
- target: {fileID: 4080194752436190636, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 4080194752436190636, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_Sprite
|
propertyPath: m_Sprite
|
||||||
value:
|
value:
|
||||||
objectReference: {fileID: 3001265211797695019, guid: afdc182c0c32e8f4c9ab04187ea11042, type: 3}
|
objectReference: {fileID: -9176826819293939900, guid: 7b00cd0000ef4424985cd21fb4f197ee, type: 3}
|
||||||
- target: {fileID: 4080194752436190636, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 4080194752436190636, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 2
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_LocalScale.x
|
propertyPath: m_LocalScale.x
|
||||||
value: 1.19
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_LocalScale.y
|
propertyPath: m_LocalScale.y
|
||||||
value: 1.19
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_LocalScale.z
|
propertyPath: m_LocalScale.z
|
||||||
value: 1.19
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_LocalPosition.x
|
propertyPath: m_LocalPosition.x
|
||||||
@@ -3058,30 +3157,72 @@ PrefabInstance:
|
|||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: StatueDecoration
|
value: StatueDecoration
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 8766058064105819536, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
|
propertyPath: m_SpriteTilingProperty.pivot.y
|
||||||
|
value: 0.25
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8766058064105819536, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 8766058064105819536, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_SpriteTilingProperty.oldSize.x
|
propertyPath: m_SpriteTilingProperty.oldSize.x
|
||||||
value: 7.01
|
value: 5.55
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8766058064105819536, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- target: {fileID: 8766058064105819536, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
propertyPath: m_SpriteTilingProperty.oldSize.y
|
propertyPath: m_SpriteTilingProperty.oldSize.y
|
||||||
value: 9.83
|
value: 6.76
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents:
|
||||||
|
- {fileID: 4080194752436190636, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects:
|
m_AddedGameObjects:
|
||||||
|
- targetCorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
|
insertIndex: -1
|
||||||
|
addedObject: {fileID: 831113525}
|
||||||
- targetCorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- targetCorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
insertIndex: -1
|
insertIndex: -1
|
||||||
addedObject: {fileID: 2011577938}
|
addedObject: {fileID: 2011577938}
|
||||||
- targetCorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
- targetCorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
insertIndex: -1
|
insertIndex: -1
|
||||||
addedObject: {fileID: 1253055066}
|
addedObject: {fileID: 1253055066}
|
||||||
m_AddedComponents: []
|
m_AddedComponents:
|
||||||
|
- targetCorrespondingSourceObject: {fileID: 7404622075362872657, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
|
insertIndex: -1
|
||||||
|
addedObject: {fileID: 1460027146}
|
||||||
|
- targetCorrespondingSourceObject: {fileID: 7404622075362872657, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
|
insertIndex: -1
|
||||||
|
addedObject: {fileID: 1460027150}
|
||||||
m_SourcePrefab: {fileID: 100100000, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
m_SourcePrefab: {fileID: 100100000, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
--- !u!4 &1460027142 stripped
|
--- !u!4 &1460027142 stripped
|
||||||
Transform:
|
Transform:
|
||||||
m_CorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
m_CorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||||
m_PrefabInstance: {fileID: 1460027141}
|
m_PrefabInstance: {fileID: 1460027141}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
--- !u!114 &1460027146
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 175866977}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 50d0f4591bbd40fc81dc615fa465e0c5, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Display.StatueDecorationLoader
|
||||||
|
statueSpriteRenderer: {fileID: 831113526}
|
||||||
|
specificPhotoId:
|
||||||
|
applyPivotOffset: 1
|
||||||
|
showDebugInfo: 1
|
||||||
|
--- !u!114 &1460027150
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 175866977}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: afa56ec5b1f84b32ba014a91d9d9a0a0, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Controllers.DecorationDataManager
|
||||||
--- !u!4 &1572710089 stripped
|
--- !u!4 &1572710089 stripped
|
||||||
Transform:
|
Transform:
|
||||||
m_CorrespondingSourceObject: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3}
|
m_CorrespondingSourceObject: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3}
|
||||||
@@ -4270,15 +4411,15 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
||||||
propertyPath: m_LocalScale.x
|
propertyPath: m_LocalScale.x
|
||||||
value: 1.27
|
value: 1.5
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
||||||
propertyPath: m_LocalScale.y
|
propertyPath: m_LocalScale.y
|
||||||
value: 1.27
|
value: 1.5
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
||||||
propertyPath: m_LocalScale.z
|
propertyPath: m_LocalScale.z
|
||||||
value: 1.27
|
value: 1.5
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
||||||
propertyPath: m_LocalPosition.x
|
propertyPath: m_LocalPosition.x
|
||||||
@@ -4324,9 +4465,17 @@ PrefabInstance:
|
|||||||
propertyPath: m_ConstrainProportionsScale
|
propertyPath: m_ConstrainProportionsScale
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 8998003315986923927, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8998003315986923927, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
- target: {fileID: 8998003315986923927, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 8998003315986923927, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
@@ -4650,10 +4799,18 @@ PrefabInstance:
|
|||||||
propertyPath: m_SpriteSortPoint
|
propertyPath: m_SpriteSortPoint
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 1206042899194397164, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 1206042899194397164, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 1206042899194397164, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 1206042899194397164, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 622133659
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 1206042899194397164, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 1206042899194397164, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SpriteSortPoint
|
propertyPath: m_SpriteSortPoint
|
||||||
value: 1
|
value: 1
|
||||||
@@ -4810,6 +4967,18 @@ PrefabInstance:
|
|||||||
propertyPath: m_LocalPosition.y
|
propertyPath: m_LocalPosition.y
|
||||||
value: 79.258865
|
value: 79.258865
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 2074821438325188578, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.w
|
||||||
|
value: 0.9999619
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 2074821438325188578, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.x
|
||||||
|
value: -0.008727774
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 2074821438325188578, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.x
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2081598347110629021, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 2081598347110629021, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
@@ -4854,6 +5023,14 @@ PrefabInstance:
|
|||||||
propertyPath: m_SpriteSortPoint
|
propertyPath: m_SpriteSortPoint
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 2186954516187312013, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 2186954516187312013, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 622133659
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2210348022821117290, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 2210348022821117290, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
@@ -4862,10 +5039,18 @@ PrefabInstance:
|
|||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 2221055336274118641, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2221055336274118641, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 2221055336274118641, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 2221055336274118641, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 622133659
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2254105400590012662, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 2254105400590012662, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_LocalPosition.y
|
propertyPath: m_LocalPosition.y
|
||||||
value: -36.830997
|
value: -36.830997
|
||||||
@@ -5042,9 +5227,17 @@ PrefabInstance:
|
|||||||
propertyPath: m_LocalPosition.y
|
propertyPath: m_LocalPosition.y
|
||||||
value: -3.255498
|
value: -3.255498
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3320920075436016561, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 3320920075436016561, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 3320920075436016561, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 0
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3320920075436016561, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 622133659
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 3322923094593524188, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 3322923094593524188, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_LocalPosition.y
|
propertyPath: m_LocalPosition.y
|
||||||
@@ -5190,10 +5383,18 @@ PrefabInstance:
|
|||||||
propertyPath: m_SpriteSortPoint
|
propertyPath: m_SpriteSortPoint
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3897832075439863271, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 3897832075439863271, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 3897832075439863271, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3897832075439863271, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 622133659
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 3897832075439863271, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 3897832075439863271, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SpriteSortPoint
|
propertyPath: m_SpriteSortPoint
|
||||||
value: 1
|
value: 1
|
||||||
@@ -5242,10 +5443,18 @@ PrefabInstance:
|
|||||||
propertyPath: m_LocalPosition.y
|
propertyPath: m_LocalPosition.y
|
||||||
value: -7.760247
|
value: -7.760247
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 4153523515925215521, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 4153523515925215521, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 4153523515925215521, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 4153523515925215521, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 622133659
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 4162192578336450225, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 4162192578336450225, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
@@ -5802,10 +6011,18 @@ PrefabInstance:
|
|||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 6961455400696245873, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayer
|
||||||
|
value: -1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6961455400696245873, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 6961455400696245873, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SortingOrder
|
propertyPath: m_SortingOrder
|
||||||
value: 1
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 6961455400696245873, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
|
propertyPath: m_SortingLayerID
|
||||||
|
value: 622133659
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6961455400696245873, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
- target: {fileID: 6961455400696245873, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||||
propertyPath: m_SpriteSortPoint
|
propertyPath: m_SpriteSortPoint
|
||||||
value: 1
|
value: 1
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -188,10 +188,21 @@ namespace AppleHills.Core.Settings
|
|||||||
float HoverAnimationDuration { get; }
|
float HoverAnimationDuration { get; }
|
||||||
float PlacementAnimationDuration { get; }
|
float PlacementAnimationDuration { get; }
|
||||||
|
|
||||||
|
// Addressables
|
||||||
|
string DecorationDataLabel { get; }
|
||||||
|
|
||||||
// Photo Settings
|
// Photo Settings
|
||||||
string PhotoSaveKey { get; }
|
string PhotoSaveKey { get; }
|
||||||
int PhotoQuality { get; }
|
int PhotoQuality { get; }
|
||||||
bool CaptureFullScreen { get; }
|
bool CaptureFullScreen { get; }
|
||||||
|
string CutoutPhotoFilename { get; } // Hardcoded filename for transparent cutout version
|
||||||
|
|
||||||
|
// Photo Gallery (DEPRECATED - Feature temporarily disabled)
|
||||||
|
int GalleryItemsPerPage { get; }
|
||||||
|
int GalleryThumbnailSize { get; }
|
||||||
|
int GalleryMaxCachedThumbnails { get; }
|
||||||
|
float GalleryEnlargedScale { get; }
|
||||||
|
float GalleryAnimationDuration { get; }
|
||||||
|
|
||||||
// Rewards
|
// Rewards
|
||||||
int CardsRewardCount { get; }
|
int CardsRewardCount { get; }
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ namespace Core.Settings
|
|||||||
[Tooltip("Duration for placement animation")]
|
[Tooltip("Duration for placement animation")]
|
||||||
[SerializeField] private float placementAnimationDuration = 0.15f;
|
[SerializeField] private float placementAnimationDuration = 0.15f;
|
||||||
|
|
||||||
|
[Header("Addressables")]
|
||||||
|
[Tooltip("Addressables label for loading DecorationData assets")]
|
||||||
|
[SerializeField] private string decorationDataLabel = "StatueDecorations";
|
||||||
|
|
||||||
[Header("Photo Settings")]
|
[Header("Photo Settings")]
|
||||||
[Tooltip("PlayerPrefs key for saving the statue photo")]
|
[Tooltip("PlayerPrefs key for saving the statue photo")]
|
||||||
[SerializeField] private string photoSaveKey = "MrCementStatuePhoto";
|
[SerializeField] private string photoSaveKey = "MrCementStatuePhoto";
|
||||||
@@ -59,6 +63,16 @@ namespace Core.Settings
|
|||||||
[Tooltip("Whether to capture full screen or just statue area")]
|
[Tooltip("Whether to capture full screen or just statue area")]
|
||||||
[SerializeField] private bool captureFullScreen;
|
[SerializeField] private bool captureFullScreen;
|
||||||
|
|
||||||
|
[Tooltip("Filename for transparent cutout version (overwrites each time)")]
|
||||||
|
[SerializeField] private string cutoutPhotoFilename = "StatueCutout_Latest";
|
||||||
|
|
||||||
|
[Header("Photo Gallery - DEPRECATED (Feature Disabled)")]
|
||||||
|
[HideInInspector] [SerializeField] private int galleryItemsPerPage = 20;
|
||||||
|
[HideInInspector] [SerializeField] private int galleryThumbnailSize = 256;
|
||||||
|
[HideInInspector] [SerializeField] private int galleryMaxCachedThumbnails = 50;
|
||||||
|
[HideInInspector] [SerializeField] private float galleryEnlargedScale = 2.5f;
|
||||||
|
[HideInInspector] [SerializeField] private float galleryAnimationDuration = 0.3f;
|
||||||
|
|
||||||
[Header("Rewards")]
|
[Header("Rewards")]
|
||||||
[Tooltip("Number of Blokkemon cards awarded on completion")]
|
[Tooltip("Number of Blokkemon cards awarded on completion")]
|
||||||
[SerializeField] private int cardsRewardCount = 3;
|
[SerializeField] private int cardsRewardCount = 3;
|
||||||
@@ -97,10 +111,21 @@ namespace Core.Settings
|
|||||||
public float HoverAnimationDuration => hoverAnimationDuration;
|
public float HoverAnimationDuration => hoverAnimationDuration;
|
||||||
public float PlacementAnimationDuration => placementAnimationDuration;
|
public float PlacementAnimationDuration => placementAnimationDuration;
|
||||||
|
|
||||||
|
// IStatueDressupSettings implementation - Addressables
|
||||||
|
public string DecorationDataLabel => decorationDataLabel;
|
||||||
|
|
||||||
// IStatueDressupSettings implementation - Photo Settings
|
// IStatueDressupSettings implementation - Photo Settings
|
||||||
public string PhotoSaveKey => photoSaveKey;
|
public string PhotoSaveKey => photoSaveKey;
|
||||||
public int PhotoQuality => photoQuality;
|
public int PhotoQuality => photoQuality;
|
||||||
public bool CaptureFullScreen => captureFullScreen;
|
public bool CaptureFullScreen => captureFullScreen;
|
||||||
|
public string CutoutPhotoFilename => cutoutPhotoFilename;
|
||||||
|
|
||||||
|
// IStatueDressupSettings implementation - Photo Gallery (DEPRECATED)
|
||||||
|
public int GalleryItemsPerPage => galleryItemsPerPage;
|
||||||
|
public int GalleryThumbnailSize => galleryThumbnailSize;
|
||||||
|
public int GalleryMaxCachedThumbnails => galleryMaxCachedThumbnails;
|
||||||
|
public float GalleryEnlargedScale => galleryEnlargedScale;
|
||||||
|
public float GalleryAnimationDuration => galleryAnimationDuration;
|
||||||
|
|
||||||
// IStatueDressupSettings implementation - Rewards
|
// IStatueDressupSettings implementation - Rewards
|
||||||
public int CardsRewardCount => cardsRewardCount;
|
public int CardsRewardCount => cardsRewardCount;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Levels
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeField] private bool startUnlocked = false;
|
[SerializeField] private bool startUnlocked = false;
|
||||||
|
|
||||||
private SpriteRenderer iconRenderer;
|
[SerializeField] private SpriteRenderer iconRenderer;
|
||||||
private IInteractionSettings interactionSettings;
|
private IInteractionSettings interactionSettings;
|
||||||
private bool isUnlocked;
|
private bool isUnlocked;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Core;
|
||||||
|
using Core.Lifecycle;
|
||||||
|
using Minigames.StatueDressup.Data;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||||
|
using Utils;
|
||||||
|
namespace Minigames.StatueDressup.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton manager for statue dressup data loading and caching.
|
||||||
|
/// Loads settings first, then decoration data via Addressables and provides shared access.
|
||||||
|
/// Used by both minigame and town map to avoid duplicate loading.
|
||||||
|
/// </summary>
|
||||||
|
public class DecorationDataManager : ManagedBehaviour, IReadyNotifier
|
||||||
|
{
|
||||||
|
public static DecorationDataManager Instance { get; private set; }
|
||||||
|
|
||||||
|
// Static callback queue for when instance doesn't exist yet
|
||||||
|
private static readonly List<System.Action> PendingCallbacks = new List<System.Action>();
|
||||||
|
private AppleHills.Core.Settings.IStatueDressupSettings settings;
|
||||||
|
private Dictionary<string, DecorationData> decorationDataDict;
|
||||||
|
private AsyncOperationHandle<System.Collections.Generic.IList<DecorationData>> decorationDataHandle;
|
||||||
|
private bool isLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the settings instance
|
||||||
|
/// </summary>
|
||||||
|
public AppleHills.Core.Settings.IStatueDressupSettings Settings => settings;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if data is loaded and ready (implements IReadyNotifier)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsReady => isLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event invoked when data is loaded and ready (implements IReadyNotifier)
|
||||||
|
/// </summary>
|
||||||
|
public event System.Action OnReady;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static method to register callbacks that will execute when manager is ready.
|
||||||
|
/// Can be called before instance exists - callbacks will be queued and executed when ready.
|
||||||
|
/// Handles null instance gracefully by queuing callbacks until instance is created and ready.
|
||||||
|
/// </summary>
|
||||||
|
public static void WhenReady(System.Action callback)
|
||||||
|
{
|
||||||
|
if (callback == null) return;
|
||||||
|
|
||||||
|
// If instance exists and is ready, execute immediately
|
||||||
|
if (Instance != null && Instance.IsReady)
|
||||||
|
{
|
||||||
|
callback.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If instance exists but not ready, use instance method
|
||||||
|
if (Instance != null)
|
||||||
|
{
|
||||||
|
(Instance as IReadyNotifier).WhenReady(callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance doesn't exist yet - queue callback
|
||||||
|
PendingCallbacks.Add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Legacy property - use IsReady instead
|
||||||
|
/// </summary>
|
||||||
|
[System.Obsolete("Use IsReady instead")]
|
||||||
|
public bool IsLoaded => isLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all decoration data as dictionary
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, DecorationData> AllData => decorationDataDict;
|
||||||
|
|
||||||
|
internal override void OnManagedAwake()
|
||||||
|
{
|
||||||
|
// Singleton pattern
|
||||||
|
if (Instance != null && Instance != this)
|
||||||
|
{
|
||||||
|
Logging.Warning("[DecorationDataManager] Duplicate instance detected. Destroying duplicate.");
|
||||||
|
Destroy(gameObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void OnManagedStart()
|
||||||
|
{
|
||||||
|
StartCoroutine(LoadSettingsAndData());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load settings first, then decoration data
|
||||||
|
/// </summary>
|
||||||
|
private System.Collections.IEnumerator LoadSettingsAndData()
|
||||||
|
{
|
||||||
|
Logging.Debug("[DecorationDataManager] Waiting for GameManager to be accessible...");
|
||||||
|
|
||||||
|
// Wait until GameManager is accessible and settings can be retrieved
|
||||||
|
settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
|
||||||
|
|
||||||
|
Logging.Debug("[DecorationDataManager] Settings loaded successfully");
|
||||||
|
|
||||||
|
// Now load decoration data
|
||||||
|
var loadTask = LoadAllDecorationData();
|
||||||
|
yield return new WaitUntil(() => loadTask.IsCompleted);
|
||||||
|
|
||||||
|
if (loadTask.IsFaulted)
|
||||||
|
{
|
||||||
|
Logging.Error($"[DecorationDataManager] Failed to load decoration data: {loadTask.Exception}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load all DecorationData assets via Addressables
|
||||||
|
/// </summary>
|
||||||
|
private async System.Threading.Tasks.Task LoadAllDecorationData()
|
||||||
|
{
|
||||||
|
if (isLoaded)
|
||||||
|
{
|
||||||
|
Logging.Warning("[DecorationDataManager] Data already loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string label = settings?.DecorationDataLabel;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(label))
|
||||||
|
{
|
||||||
|
Logging.Error("[DecorationDataManager] Decoration data label not set in settings!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationDataManager] Loading DecorationData with label '{label}'...");
|
||||||
|
|
||||||
|
// Use utility to load all DecorationData and create dictionary by ID
|
||||||
|
var result = await AddressablesUtility.LoadAssetsByLabelAsync<DecorationData, string>(
|
||||||
|
label,
|
||||||
|
data => data.DecorationId
|
||||||
|
);
|
||||||
|
|
||||||
|
decorationDataDict = result.dictionary;
|
||||||
|
decorationDataHandle = result.handle;
|
||||||
|
isLoaded = true;
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationDataManager] Loaded {decorationDataDict.Count} DecorationData assets");
|
||||||
|
|
||||||
|
// Subscribe all pending callbacks to OnReady event before invoking
|
||||||
|
if (PendingCallbacks.Count > 0)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationDataManager] Subscribing {PendingCallbacks.Count} pending callbacks to OnReady");
|
||||||
|
foreach (var callback in PendingCallbacks)
|
||||||
|
{
|
||||||
|
OnReady += callback;
|
||||||
|
}
|
||||||
|
PendingCallbacks.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as ready and notify listeners (including pending callbacks)
|
||||||
|
OnReady?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get decoration data by ID
|
||||||
|
/// </summary>
|
||||||
|
public DecorationData GetData(string decorationId)
|
||||||
|
{
|
||||||
|
if (!isLoaded)
|
||||||
|
{
|
||||||
|
Logging.Warning("[DecorationDataManager] Data not loaded yet!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(decorationId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
decorationDataDict.TryGetValue(decorationId, out DecorationData data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try get decoration data by ID
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetData(string decorationId, out DecorationData data)
|
||||||
|
{
|
||||||
|
data = null;
|
||||||
|
|
||||||
|
if (!isLoaded || string.IsNullOrEmpty(decorationId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorationDataDict.TryGetValue(decorationId, out data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
// Release Addressables handle
|
||||||
|
AddressablesUtility.ReleaseHandle(decorationDataHandle);
|
||||||
|
|
||||||
|
if (Instance == this)
|
||||||
|
{
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: afa56ec5b1f84b32ba014a91d9d9a0a0
|
||||||
|
timeCreated: 1764240174
|
||||||
@@ -13,6 +13,8 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DecorationMenuController : ManagedBehaviour
|
public class DecorationMenuController : ManagedBehaviour
|
||||||
{
|
{
|
||||||
|
public static DecorationMenuController Instance { get; private set; }
|
||||||
|
|
||||||
[Header("References")]
|
[Header("References")]
|
||||||
[SerializeField] private DecorationGridIcon iconPrefab;
|
[SerializeField] private DecorationGridIcon iconPrefab;
|
||||||
[SerializeField] private DecorationDraggableInstance draggablePrefab;
|
[SerializeField] private DecorationDraggableInstance draggablePrefab;
|
||||||
@@ -20,34 +22,37 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
[SerializeField] private Transform draggableContainer; // Parent for spawned draggables
|
[SerializeField] private Transform draggableContainer; // Parent for spawned draggables
|
||||||
[SerializeField] private Button nextPageButton;
|
[SerializeField] private Button nextPageButton;
|
||||||
[SerializeField] private Button previousPageButton;
|
[SerializeField] private Button previousPageButton;
|
||||||
[SerializeField] private StatueDecorationController statueController; // Controller for registration
|
|
||||||
[SerializeField] private Image statueOutline; // Outline image shown during drag to indicate valid drop area
|
[SerializeField] private Image statueOutline; // Outline image shown during drag to indicate valid drop area
|
||||||
|
|
||||||
[Header("Layout")]
|
[Header("Layout")]
|
||||||
[SerializeField] private GridLayoutGroup gridLayout;
|
[SerializeField] private GridLayoutGroup gridLayout;
|
||||||
|
|
||||||
private int _currentPage;
|
private int currentPage;
|
||||||
private int _totalPages;
|
private int totalPages;
|
||||||
private List<DecorationGridIcon> _spawnedIcons = new List<DecorationGridIcon>();
|
private List<DecorationGridIcon> spawnedIcons = new List<DecorationGridIcon>();
|
||||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
public int CurrentPage => _currentPage;
|
public int CurrentPage => currentPage;
|
||||||
public int TotalPages => _totalPages;
|
public int TotalPages => totalPages;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Early initialization - get settings reference
|
|
||||||
/// </summary>
|
|
||||||
internal override void OnManagedAwake()
|
internal override void OnManagedAwake()
|
||||||
{
|
{
|
||||||
base.OnManagedAwake();
|
base.OnManagedAwake();
|
||||||
|
|
||||||
// Get settings early
|
// Singleton pattern
|
||||||
_settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
|
if (Instance != null && Instance != this)
|
||||||
|
|
||||||
if (_settings == null)
|
|
||||||
{
|
{
|
||||||
Logging.Error("[DecorationMenuController] Failed to load StatueDressupSettings!");
|
Logging.Warning("[DecorationMenuController] Duplicate instance detected. Destroying duplicate.");
|
||||||
|
Destroy(gameObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
// Ensure outline starts hidden (do this early so it's ready for saved decorations)
|
||||||
|
if (statueOutline != null)
|
||||||
|
{
|
||||||
|
statueOutline.gameObject.SetActive(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,33 +63,41 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
{
|
{
|
||||||
base.OnManagedStart();
|
base.OnManagedStart();
|
||||||
|
|
||||||
if (_settings == null)
|
// Wait for data manager to be ready before initializing
|
||||||
|
DecorationDataManager.WhenReady(() =>
|
||||||
|
{
|
||||||
|
InitializeMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize menu once data manager is ready
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeMenu()
|
||||||
|
{
|
||||||
|
var settings = DecorationDataManager.Instance?.Settings;
|
||||||
|
|
||||||
|
if (settings == null)
|
||||||
{
|
{
|
||||||
Logging.Error("[DecorationMenuController] Cannot initialize without settings!");
|
Logging.Error("[DecorationMenuController] Cannot initialize without settings!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure outline starts hidden
|
var allDecorations = settings.AllDecorations;
|
||||||
if (statueOutline != null)
|
int itemsPerPage = settings?.ItemsPerPage ?? StatueDressupConstants.DefaultMenuItemsPerPage;
|
||||||
{
|
|
||||||
statueOutline.gameObject.SetActive(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var allDecorations = _settings.AllDecorations;
|
|
||||||
int itemsPerPage = _settings.ItemsPerPage;
|
|
||||||
|
|
||||||
Logging.Debug($"[DecorationMenuController] Initializing with {allDecorations?.Count ?? 0} decorations");
|
Logging.Debug($"[DecorationMenuController] Initializing with {allDecorations?.Count ?? 0} decorations");
|
||||||
|
|
||||||
// Calculate total pages
|
// Calculate total pages
|
||||||
if (allDecorations != null && allDecorations.Count > 0)
|
if (allDecorations != null && allDecorations.Count > 0)
|
||||||
{
|
{
|
||||||
_totalPages = Mathf.CeilToInt((float)allDecorations.Count / itemsPerPage);
|
totalPages = Mathf.CeilToInt((float)allDecorations.Count / itemsPerPage);
|
||||||
Logging.Debug($"[DecorationMenuController] Total pages: {_totalPages}");
|
Logging.Debug($"[DecorationMenuController] Total pages: {totalPages}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logging.Warning("[DecorationMenuController] No decorations found in settings!");
|
Logging.Warning("[DecorationMenuController] No decorations found in settings!");
|
||||||
_totalPages = 0;
|
totalPages = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup buttons
|
// Setup buttons
|
||||||
@@ -110,10 +123,11 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void PopulateCurrentPage()
|
private void PopulateCurrentPage()
|
||||||
{
|
{
|
||||||
if (_settings == null) return;
|
var settings = DecorationDataManager.Instance?.Settings;
|
||||||
|
if (settings == null) return;
|
||||||
|
|
||||||
var allDecorations = _settings.AllDecorations;
|
var allDecorations = settings.AllDecorations;
|
||||||
int itemsPerPage = _settings.ItemsPerPage;
|
int itemsPerPage = settings.ItemsPerPage;
|
||||||
|
|
||||||
if (allDecorations == null || allDecorations.Count == 0)
|
if (allDecorations == null || allDecorations.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -121,13 +135,13 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.Debug($"[DecorationMenuController] Populating page {_currentPage + 1}/{_totalPages}");
|
Logging.Debug($"[DecorationMenuController] Populating page {currentPage + 1}/{totalPages}");
|
||||||
|
|
||||||
// Clear existing icons
|
// Clear existing icons
|
||||||
ClearIcons();
|
ClearIcons();
|
||||||
|
|
||||||
// Calculate range for current page
|
// Calculate range for current page
|
||||||
int startIndex = _currentPage * itemsPerPage;
|
int startIndex = currentPage * itemsPerPage;
|
||||||
int endIndex = Mathf.Min(startIndex + itemsPerPage, allDecorations.Count);
|
int endIndex = Mathf.Min(startIndex + itemsPerPage, allDecorations.Count);
|
||||||
|
|
||||||
Logging.Debug($"[DecorationMenuController] Spawning icons {startIndex} to {endIndex - 1}");
|
Logging.Debug($"[DecorationMenuController] Spawning icons {startIndex} to {endIndex - 1}");
|
||||||
@@ -156,7 +170,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
DecorationGridIcon icon = Instantiate(iconPrefab, itemsContainer);
|
DecorationGridIcon icon = Instantiate(iconPrefab, itemsContainer);
|
||||||
icon.Initialize(data, this);
|
icon.Initialize(data, this);
|
||||||
|
|
||||||
_spawnedIcons.Add(icon);
|
spawnedIcons.Add(icon);
|
||||||
|
|
||||||
Logging.Debug($"[DecorationMenuController] Spawned icon: {data.DecorationName}");
|
Logging.Debug($"[DecorationMenuController] Spawned icon: {data.DecorationName}");
|
||||||
}
|
}
|
||||||
@@ -167,7 +181,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DecorationDraggableInstance SpawnDraggableInstance(DecorationData data, Vector3 screenPosition)
|
public DecorationDraggableInstance SpawnDraggableInstance(DecorationData data, Vector3 screenPosition)
|
||||||
{
|
{
|
||||||
if (draggablePrefab == null || statueController == null)
|
if (draggablePrefab == null || StatueDecorationController.Instance == null)
|
||||||
{
|
{
|
||||||
Logging.Warning("[DecorationMenuController] Missing draggable prefab or statue controller");
|
Logging.Warning("[DecorationMenuController] Missing draggable prefab or statue controller");
|
||||||
return null;
|
return null;
|
||||||
@@ -185,16 +199,21 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
// Get outline RectTransform for overlap detection
|
// Get outline RectTransform for overlap detection
|
||||||
RectTransform outlineRect = statueOutline != null ? statueOutline.rectTransform : null;
|
RectTransform outlineRect = statueOutline != null ? statueOutline.rectTransform : null;
|
||||||
|
|
||||||
// Initialize with references
|
// Create context for new drag
|
||||||
instance.Initialize(
|
var context = DecorationDragContext.CreateForNewDrag(
|
||||||
data,
|
data,
|
||||||
outlineRect,
|
outlineRect,
|
||||||
statueController.StatueParent,
|
StatueDecorationController.Instance.StatueParent,
|
||||||
statueController,
|
StatueDecorationController.Instance,
|
||||||
_settings,
|
DecorationDataManager.Instance.Settings,
|
||||||
OnDraggableFinished
|
OnDraggableFinished,
|
||||||
|
ShowStatueOutline,
|
||||||
|
HideStatueOutline
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialize with context
|
||||||
|
instance.InitializeWithContext(context);
|
||||||
|
|
||||||
// Position at cursor (in local space)
|
// Position at cursor (in local space)
|
||||||
Canvas canvas = GetComponentInParent<Canvas>();
|
Canvas canvas = GetComponentInParent<Canvas>();
|
||||||
if (canvas != null)
|
if (canvas != null)
|
||||||
@@ -220,19 +239,24 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show the statue outline to indicate valid drop area
|
/// Show the statue outline to indicate valid drop area
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ShowStatueOutline()
|
public void ShowStatueOutline()
|
||||||
{
|
{
|
||||||
|
Logging.Debug($"[DecorationMenuController] ShowStatueOutline called - statueOutline is null: {statueOutline == null}");
|
||||||
if (statueOutline != null)
|
if (statueOutline != null)
|
||||||
{
|
{
|
||||||
statueOutline.gameObject.SetActive(true);
|
statueOutline.gameObject.SetActive(true);
|
||||||
Logging.Debug("[DecorationMenuController] Statue outline shown");
|
Logging.Debug("[DecorationMenuController] Statue outline shown");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Warning("[DecorationMenuController] Cannot show outline - statueOutline is null!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hide the statue outline after drag ends
|
/// Hide the statue outline after drag ends
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HideStatueOutline()
|
public void HideStatueOutline()
|
||||||
{
|
{
|
||||||
if (statueOutline != null)
|
if (statueOutline != null)
|
||||||
{
|
{
|
||||||
@@ -254,7 +278,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ClearIcons()
|
private void ClearIcons()
|
||||||
{
|
{
|
||||||
foreach (var icon in _spawnedIcons)
|
foreach (var icon in spawnedIcons)
|
||||||
{
|
{
|
||||||
if (icon != null)
|
if (icon != null)
|
||||||
{
|
{
|
||||||
@@ -262,7 +286,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_spawnedIcons.Clear();
|
spawnedIcons.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -270,11 +294,11 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnNextPage()
|
private void OnNextPage()
|
||||||
{
|
{
|
||||||
if (_currentPage < _totalPages - 1)
|
if (currentPage < totalPages - 1)
|
||||||
{
|
{
|
||||||
_currentPage++;
|
currentPage++;
|
||||||
PopulateCurrentPage();
|
PopulateCurrentPage();
|
||||||
Logging.Debug($"[DecorationMenuController] Next page: {_currentPage + 1}/{_totalPages}");
|
Logging.Debug($"[DecorationMenuController] Next page: {currentPage + 1}/{totalPages}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,11 +307,11 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnPreviousPage()
|
private void OnPreviousPage()
|
||||||
{
|
{
|
||||||
if (_currentPage > 0)
|
if (currentPage > 0)
|
||||||
{
|
{
|
||||||
_currentPage--;
|
currentPage--;
|
||||||
PopulateCurrentPage();
|
PopulateCurrentPage();
|
||||||
Logging.Debug($"[DecorationMenuController] Previous page: {_currentPage + 1}/{_totalPages}");
|
Logging.Debug($"[DecorationMenuController] Previous page: {currentPage + 1}/{totalPages}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,14 +320,14 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateNavigationButtons()
|
private void UpdateNavigationButtons()
|
||||||
{
|
{
|
||||||
if (previousPageButton != null)
|
|
||||||
{
|
|
||||||
previousPageButton.interactable = _currentPage > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextPageButton != null)
|
if (nextPageButton != null)
|
||||||
{
|
{
|
||||||
nextPageButton.interactable = _currentPage < _totalPages - 1;
|
nextPageButton.interactable = currentPage < totalPages - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousPageButton != null)
|
||||||
|
{
|
||||||
|
previousPageButton.interactable = currentPage > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +351,12 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
|
|
||||||
// Cleanup icons
|
// Cleanup icons
|
||||||
ClearIcons();
|
ClearIcons();
|
||||||
|
|
||||||
|
// Singleton cleanup
|
||||||
|
if (Instance == this)
|
||||||
|
{
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
using Core;
|
|
||||||
using Minigames.StatueDressup.Utils;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace Minigames.StatueDressup.Controllers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Minimal test for photo capture - just capture and save to disk
|
|
||||||
/// </summary>
|
|
||||||
public class PhotoCaptureTestController : MonoBehaviour
|
|
||||||
{
|
|
||||||
[Header("Required")]
|
|
||||||
[SerializeField] private RectTransform captureArea;
|
|
||||||
[SerializeField] private Button captureButton;
|
|
||||||
|
|
||||||
[Header("Optional - UI to hide during capture")]
|
|
||||||
[SerializeField] private GameObject[] hideTheseObjects;
|
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
if (captureButton != null)
|
|
||||||
{
|
|
||||||
captureButton.onClick.AddListener(OnCaptureClicked);
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Log($"[PhotoCaptureTest] Ready. Photo save path: {StatuePhotoManager.GetPhotoDirectory()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCaptureClicked()
|
|
||||||
{
|
|
||||||
if (captureArea == null)
|
|
||||||
{
|
|
||||||
Debug.LogError("[PhotoCaptureTest] Capture Area not assigned!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Log("[PhotoCaptureTest] Starting capture...");
|
|
||||||
StartCoroutine(CaptureCoroutine());
|
|
||||||
}
|
|
||||||
|
|
||||||
private System.Collections.IEnumerator CaptureCoroutine()
|
|
||||||
{
|
|
||||||
// Hide UI
|
|
||||||
foreach (var obj in hideTheseObjects)
|
|
||||||
if (obj != null) obj.SetActive(false);
|
|
||||||
|
|
||||||
yield return new WaitForEndOfFrame();
|
|
||||||
|
|
||||||
// Capture
|
|
||||||
bool done = false;
|
|
||||||
Texture2D photo = null;
|
|
||||||
|
|
||||||
StatuePhotoManager.CaptureAreaPhoto(captureArea, (texture) => {
|
|
||||||
photo = texture;
|
|
||||||
done = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
yield return new WaitUntil(() => done);
|
|
||||||
|
|
||||||
// Restore UI
|
|
||||||
foreach (var obj in hideTheseObjects)
|
|
||||||
if (obj != null) obj.SetActive(true);
|
|
||||||
|
|
||||||
// Save
|
|
||||||
if (photo != null)
|
|
||||||
{
|
|
||||||
string photoId = StatuePhotoManager.SavePhoto(photo, 0);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(photoId))
|
|
||||||
{
|
|
||||||
string path = $"{StatuePhotoManager.GetPhotoDirectory()}/{photoId}.png";
|
|
||||||
Debug.Log($"[PhotoCaptureTest] ✅ SUCCESS! Photo saved: {path}");
|
|
||||||
Debug.Log($"[PhotoCaptureTest] Photo size: {photo.width}x{photo.height}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError("[PhotoCaptureTest] ❌ Failed to save photo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError("[PhotoCaptureTest] ❌ Failed to capture photo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
|
||||||
if (captureButton != null)
|
|
||||||
captureButton.onClick.RemoveListener(OnCaptureClicked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Core;
|
using Core;
|
||||||
using Core.Lifecycle;
|
using Core.Lifecycle;
|
||||||
|
using Minigames.StatueDressup.Data;
|
||||||
using Minigames.StatueDressup.DragDrop;
|
using Minigames.StatueDressup.DragDrop;
|
||||||
|
using UI.Core;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
namespace Minigames.StatueDressup.Controllers
|
namespace Minigames.StatueDressup.Controllers
|
||||||
{
|
{
|
||||||
@@ -13,36 +17,46 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class StatueDecorationController : ManagedBehaviour
|
public class StatueDecorationController : ManagedBehaviour
|
||||||
{
|
{
|
||||||
|
public static StatueDecorationController Instance { get; private set; }
|
||||||
|
|
||||||
[Header("References")]
|
[Header("References")]
|
||||||
[SerializeField] private RectTransform statueArea; // Statue area for overlap detection
|
[SerializeField] private RectTransform statueArea; // Statue area for overlap detection
|
||||||
[SerializeField] private Transform statueParent; // Parent for placed decorations
|
[SerializeField] private Transform statueParent; // Parent for placed decorations
|
||||||
[SerializeField] private DecorationMenuController menuController;
|
|
||||||
[SerializeField] private Button takePhotoButton;
|
[SerializeField] private Button takePhotoButton;
|
||||||
[SerializeField] private GameObject statue;
|
[SerializeField] private GameObject statue;
|
||||||
|
[SerializeField] private DecorationDraggableInstance draggablePrefab; // Prefab for spawning decorations
|
||||||
|
|
||||||
|
[Header("UI Pages")]
|
||||||
|
[SerializeField] private UI.PlayAreaPage playAreaPage;
|
||||||
|
[SerializeField] private UI.PhotoGalleryPage photoGalleryPage;
|
||||||
|
[SerializeField] private Button openGalleryButton;
|
||||||
|
|
||||||
[Header("UI Elements to Hide for Photo")]
|
[Header("UI Elements to Hide for Photo")]
|
||||||
[SerializeField] private GameObject[] uiElementsToHideForPhoto;
|
[SerializeField] private GameObject[] uiElementsToHideForPhoto;
|
||||||
|
|
||||||
[Header("Photo Settings")]
|
[Header("Photo Settings")]
|
||||||
[SerializeField] private RectTransform photoArea; // Area to capture
|
[SerializeField] private RectTransform photoArea; // Area to capture
|
||||||
[SerializeField] private string photoSaveKey = "MrCementStatuePhoto";
|
|
||||||
|
|
||||||
private List<DecorationDraggableInstance> _placedDecorations = new List<DecorationDraggableInstance>();
|
private List<DecorationDraggableInstance> placedDecorations = new List<DecorationDraggableInstance>();
|
||||||
private bool _minigameCompleted;
|
private bool minigameCompleted;
|
||||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
|
||||||
|
|
||||||
// Public property for menu controller
|
// Public properties
|
||||||
public Transform StatueParent => statueParent;
|
public Transform StatueParent => statueParent;
|
||||||
|
public RectTransform StatueArea => statueArea;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Early initialization - get settings reference
|
|
||||||
/// </summary>
|
|
||||||
internal override void OnManagedAwake()
|
internal override void OnManagedAwake()
|
||||||
{
|
{
|
||||||
base.OnManagedAwake();
|
base.OnManagedAwake();
|
||||||
|
|
||||||
// Get settings early
|
// Singleton pattern
|
||||||
_settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
|
if (Instance != null && Instance != this)
|
||||||
|
{
|
||||||
|
Logging.Warning("[StatueDecorationController] Duplicate instance detected. Destroying duplicate.");
|
||||||
|
Destroy(gameObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -54,32 +68,68 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
|
|
||||||
Logging.Debug("[StatueDecorationController] Initializing minigame");
|
Logging.Debug("[StatueDecorationController] Initializing minigame");
|
||||||
|
|
||||||
|
// Wait for decoration data to be ready before initializing
|
||||||
|
DecorationDataManager.WhenReady(() =>
|
||||||
|
{
|
||||||
|
Logging.Debug("[StatueDecorationController] DecorationData ready, initializing minigame");
|
||||||
|
InitializeMinigame();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the minigame after data is ready
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeMinigame()
|
||||||
|
{
|
||||||
|
// TODO: If ever picture gallery
|
||||||
|
// Setup UI pages (DISABLED - kept for future gallery integration)
|
||||||
|
// if (playAreaPage != null && UIPageController.Instance != null)
|
||||||
|
// {
|
||||||
|
// UIPageController.Instance.PushPage(playAreaPage);
|
||||||
|
// Logging.Debug("[StatueDecorationController] Play area page registered");
|
||||||
|
// }
|
||||||
|
|
||||||
// Setup photo button
|
// Setup photo button
|
||||||
if (takePhotoButton != null)
|
if (takePhotoButton != null)
|
||||||
{
|
{
|
||||||
takePhotoButton.onClick.AddListener(OnTakePhoto);
|
takePhotoButton.onClick.AddListener(OnTakePhoto);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to menu controller for tracking placed items
|
// TODO: If ever picture gallery
|
||||||
// Items will manage their own placement via overlap detection
|
// Setup gallery button (DISABLED - kept for future use)
|
||||||
if (menuController != null)
|
// if (openGalleryButton != null)
|
||||||
{
|
// {
|
||||||
// Menu controller will handle spawning replacements
|
// openGalleryButton.onClick.AddListener(OnOpenGallery);
|
||||||
Logging.Debug("[StatueDecorationController] Menu controller connected");
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// Load saved state if exists
|
// Load saved state if exists
|
||||||
LoadStatueState();
|
LoadStatueState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open photo gallery
|
||||||
|
/// </summary>
|
||||||
|
private void OnOpenGallery()
|
||||||
|
{
|
||||||
|
if (photoGalleryPage != null && UIPageController.Instance != null)
|
||||||
|
{
|
||||||
|
UIPageController.Instance.PushPage(photoGalleryPage);
|
||||||
|
Logging.Debug("[StatueDecorationController] Opening photo gallery");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Warning("[StatueDecorationController] Photo gallery page not assigned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register a decoration as placed on statue
|
/// Register a decoration as placed on statue
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RegisterDecoration(DecorationDraggableInstance decoration)
|
public void RegisterDecoration(DecorationDraggableInstance decoration)
|
||||||
{
|
{
|
||||||
if (decoration != null && !_placedDecorations.Contains(decoration))
|
if (decoration != null && !placedDecorations.Contains(decoration))
|
||||||
{
|
{
|
||||||
_placedDecorations.Add(decoration);
|
placedDecorations.Add(decoration);
|
||||||
Logging.Debug($"[StatueDecorationController] Decoration placed: {decoration.Data?.DecorationName}");
|
Logging.Debug($"[StatueDecorationController] Decoration placed: {decoration.Data?.DecorationName}");
|
||||||
|
|
||||||
// Auto-save state
|
// Auto-save state
|
||||||
@@ -92,9 +142,9 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void UnregisterDecoration(DecorationDraggableInstance decoration)
|
public void UnregisterDecoration(DecorationDraggableInstance decoration)
|
||||||
{
|
{
|
||||||
if (decoration != null && _placedDecorations.Contains(decoration))
|
if (decoration != null && placedDecorations.Contains(decoration))
|
||||||
{
|
{
|
||||||
_placedDecorations.Remove(decoration);
|
placedDecorations.Remove(decoration);
|
||||||
Logging.Debug($"[StatueDecorationController] Decoration removed: {decoration.Data?.DecorationName}");
|
Logging.Debug($"[StatueDecorationController] Decoration removed: {decoration.Data?.DecorationName}");
|
||||||
|
|
||||||
// Auto-save state
|
// Auto-save state
|
||||||
@@ -107,7 +157,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnTakePhoto()
|
private void OnTakePhoto()
|
||||||
{
|
{
|
||||||
if (_minigameCompleted)
|
if (minigameCompleted)
|
||||||
{
|
{
|
||||||
Logging.Debug("[StatueDecorationController] Minigame already completed");
|
Logging.Debug("[StatueDecorationController] Minigame already completed");
|
||||||
return;
|
return;
|
||||||
@@ -115,76 +165,66 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
|
|
||||||
Logging.Debug("[StatueDecorationController] Taking photo of statue");
|
Logging.Debug("[StatueDecorationController] Taking photo of statue");
|
||||||
|
|
||||||
// Hide UI elements
|
// CapturePhotoCoroutine handles all UI hiding/showing
|
||||||
HideUIForPhoto(true);
|
|
||||||
|
|
||||||
// Wait a frame for UI to hide, then capture
|
|
||||||
StartCoroutine(CapturePhotoCoroutine());
|
StartCoroutine(CapturePhotoCoroutine());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Capture photo after UI is hidden
|
/// Capture photo and save decoration metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private System.Collections.IEnumerator CapturePhotoCoroutine()
|
private System.Collections.IEnumerator CapturePhotoCoroutine()
|
||||||
{
|
{
|
||||||
yield return new WaitForEndOfFrame();
|
int decorationCount = placedDecorations.Count;
|
||||||
|
bool captureSuccess = false;
|
||||||
|
string savedPhotoId = null;
|
||||||
|
|
||||||
// Capture using Screenshot Helper via StatuePhotoManager
|
// Capture photo
|
||||||
bool captureComplete = false;
|
yield return PhotoManager.CaptureAndSaveCoroutine(
|
||||||
Texture2D capturedPhoto = null;
|
CaptureType.StatueMinigame,
|
||||||
|
|
||||||
Utils.StatuePhotoManager.CaptureAreaPhoto(
|
|
||||||
photoArea,
|
photoArea,
|
||||||
(Texture2D texture) =>
|
uiElementsToHideForPhoto,
|
||||||
{
|
onSuccess: (photoId) =>
|
||||||
capturedPhoto = texture;
|
|
||||||
captureComplete = true;
|
|
||||||
},
|
|
||||||
Camera.main
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait for capture to complete
|
|
||||||
yield return new WaitUntil(() => captureComplete);
|
|
||||||
|
|
||||||
if (capturedPhoto != null)
|
|
||||||
{
|
|
||||||
// Save photo with StatuePhotoManager
|
|
||||||
int decorationCount = _placedDecorations.Count;
|
|
||||||
string photoId = Utils.StatuePhotoManager.SavePhoto(capturedPhoto, decorationCount);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(photoId))
|
|
||||||
{
|
{
|
||||||
Logging.Debug($"[StatueDecorationController] Photo saved: {photoId}");
|
Logging.Debug($"[StatueDecorationController] Photo saved: {photoId}");
|
||||||
|
savedPhotoId = photoId;
|
||||||
// Award cards
|
captureSuccess = true;
|
||||||
AwardCards();
|
},
|
||||||
|
onFailure: (error) =>
|
||||||
// Update town icon
|
|
||||||
UpdateTownIcon(capturedPhoto);
|
|
||||||
|
|
||||||
// Show completion feedback
|
|
||||||
ShowCompletionFeedback();
|
|
||||||
|
|
||||||
_minigameCompleted = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
Logging.Error("[StatueDecorationController] Failed to save photo!");
|
Logging.Error($"[StatueDecorationController] Photo capture failed: {error}");
|
||||||
DebugUIMessage.Show("Failed to save photo!", Color.red);
|
DebugUIMessage.Show("Failed to save photo!", Color.red);
|
||||||
}
|
captureSuccess = false;
|
||||||
}
|
},
|
||||||
else
|
metadata: decorationCount
|
||||||
|
);
|
||||||
|
|
||||||
|
// If photo failed, abort
|
||||||
|
if (!captureSuccess)
|
||||||
{
|
{
|
||||||
Logging.Error("[StatueDecorationController] Failed to capture photo!");
|
yield break;
|
||||||
DebugUIMessage.Show("Failed to capture photo!", Color.red);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore UI
|
// Save decoration metadata
|
||||||
HideUIForPhoto(false);
|
SaveDecorationMetadata(savedPhotoId);
|
||||||
|
|
||||||
|
// Load the saved photo for town icon update
|
||||||
|
Texture2D savedPhoto = PhotoManager.LoadPhoto(CaptureType.StatueMinigame, savedPhotoId);
|
||||||
|
|
||||||
|
// Award cards
|
||||||
|
AwardCards();
|
||||||
|
|
||||||
|
// Update town icon
|
||||||
|
if (savedPhoto != null)
|
||||||
|
{
|
||||||
|
UpdateTownIcon(savedPhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show completion feedback
|
||||||
|
ShowCompletionFeedback();
|
||||||
|
|
||||||
|
minigameCompleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Award Blokkemon cards to player
|
/// Award Blokkemon cards to player
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -218,17 +258,62 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hide/show UI elements for photo
|
/// Save decoration metadata for reconstruction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HideUIForPhoto(bool hide)
|
private void SaveDecorationMetadata(string photoId)
|
||||||
{
|
{
|
||||||
foreach (var element in uiElementsToHideForPhoto)
|
// Determine coordinate system type
|
||||||
|
bool isUIRectTransform = statue != null && statue.GetComponent<RectTransform>() != null;
|
||||||
|
|
||||||
|
// Get statue size for coordinate conversion
|
||||||
|
Vector2 statueSize = Vector2.one;
|
||||||
|
if (isUIRectTransform && statue != null)
|
||||||
{
|
{
|
||||||
if (element != null)
|
RectTransform statueRect = statue.GetComponent<RectTransform>();
|
||||||
|
statueSize = statueRect.rect.size;
|
||||||
|
}
|
||||||
|
else if (statue != null)
|
||||||
|
{
|
||||||
|
SpriteRenderer statueSprite = statue.GetComponent<SpriteRenderer>();
|
||||||
|
if (statueSprite != null && statueSprite.sprite != null)
|
||||||
{
|
{
|
||||||
element.SetActive(!hide);
|
statueSize = statueSprite.sprite.bounds.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatueDecorationData data = new StatueDecorationData
|
||||||
|
{
|
||||||
|
photoId = photoId,
|
||||||
|
timestamp = DateTime.Now.ToString("o"),
|
||||||
|
coordinateSystem = isUIRectTransform ? CoordinateSystemType.UIRectTransform : CoordinateSystemType.WorldSpace,
|
||||||
|
sourceStatueSize = statueSize
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect all decoration placements
|
||||||
|
foreach (var decoration in placedDecorations)
|
||||||
|
{
|
||||||
|
if (decoration == null || decoration.Data == null) continue;
|
||||||
|
|
||||||
|
RectTransform rectTransform = decoration.GetComponent<RectTransform>();
|
||||||
|
SpriteRenderer spriteRenderer = decoration.GetComponent<SpriteRenderer>();
|
||||||
|
|
||||||
|
DecorationPlacement placement = new DecorationPlacement
|
||||||
|
{
|
||||||
|
decorationId = decoration.Data.DecorationId,
|
||||||
|
localPosition = decoration.transform.localPosition,
|
||||||
|
localScale = decoration.transform.localScale,
|
||||||
|
sizeDelta = rectTransform != null ? rectTransform.sizeDelta : Vector2.zero,
|
||||||
|
rotation = decoration.transform.eulerAngles.z,
|
||||||
|
sortingOrder = spriteRenderer != null ? spriteRenderer.sortingOrder : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
data.placements.Add(placement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save metadata using PhotoManager
|
||||||
|
PhotoManager.SaveDecorationMetadata(CaptureType.StatueMinigame, photoId, data);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatueDecorationController] Saved decoration metadata: {data.placements.Count} decorations");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -236,8 +321,10 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void SaveStatueState()
|
private void SaveStatueState()
|
||||||
{
|
{
|
||||||
|
var settings = DecorationDataManager.Instance?.Settings;
|
||||||
|
|
||||||
// Check if persistence is enabled
|
// Check if persistence is enabled
|
||||||
if (_settings == null || !_settings.EnableStatePersistence)
|
if (settings == null || !settings.EnableStatePersistence)
|
||||||
{
|
{
|
||||||
Logging.Debug("[StatueDecorationController] State persistence disabled");
|
Logging.Debug("[StatueDecorationController] State persistence disabled");
|
||||||
return;
|
return;
|
||||||
@@ -247,25 +334,125 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
// Save decoration ID + position + rotation for each placed item
|
// Save decoration ID + position + rotation for each placed item
|
||||||
// Respect MaxSavedDecorations limit
|
// Respect MaxSavedDecorations limit
|
||||||
|
|
||||||
Logging.Debug($"[StatueDecorationController] State saved to {_settings.StateSaveKey} (TODO: implement persistence)");
|
Logging.Debug($"[StatueDecorationController] State saved to {settings.StateSaveKey} (TODO: implement persistence)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load saved statue decoration state
|
/// Load saved statue decoration state from metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void LoadStatueState()
|
private void LoadStatueState()
|
||||||
{
|
{
|
||||||
|
var settings = DecorationDataManager.Instance?.Settings;
|
||||||
|
|
||||||
// Check if persistence is enabled
|
// Check if persistence is enabled
|
||||||
if (_settings == null || !_settings.EnableStatePersistence)
|
if (settings == null || !settings.EnableStatePersistence)
|
||||||
{
|
{
|
||||||
Logging.Debug("[StatueDecorationController] State persistence disabled");
|
Logging.Debug("[StatueDecorationController] State persistence disabled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement load system
|
// Check if DecorationData is loaded
|
||||||
// Restore decorations from saved state
|
if (DecorationDataManager.Instance == null || !DecorationDataManager.Instance.IsLoaded)
|
||||||
|
{
|
||||||
|
Logging.Warning("[StatueDecorationController] DecorationData not loaded yet. Cannot restore state.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Logging.Debug($"[StatueDecorationController] State loaded from {_settings.StateSaveKey} (TODO: implement persistence)");
|
// Load latest decoration metadata
|
||||||
|
StatueDecorationData data = PhotoManager.LoadLatestDecorationMetadata<StatueDecorationData>(CaptureType.StatueMinigame);
|
||||||
|
|
||||||
|
if (data == null || data.placements == null || data.placements.Count == 0)
|
||||||
|
{
|
||||||
|
Logging.Debug("[StatueDecorationController] No saved state found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[StatueDecorationController] Restoring {data.placements.Count} decorations from saved state");
|
||||||
|
|
||||||
|
// Spawn each decoration
|
||||||
|
int successCount = 0;
|
||||||
|
foreach (var placement in data.placements)
|
||||||
|
{
|
||||||
|
if (SpawnSavedDecoration(placement))
|
||||||
|
{
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[StatueDecorationController] Successfully restored {successCount}/{data.placements.Count} decorations");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawn a saved decoration from placement data
|
||||||
|
/// </summary>
|
||||||
|
private bool SpawnSavedDecoration(DecorationPlacement placement)
|
||||||
|
{
|
||||||
|
// Look up DecorationData from manager
|
||||||
|
if (!DecorationDataManager.Instance.TryGetData(placement.decorationId, out DecorationData decorationData))
|
||||||
|
{
|
||||||
|
Logging.Warning($"[StatueDecorationController] DecorationData not found for ID: {placement.decorationId}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draggablePrefab == null || statueParent == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[StatueDecorationController] Missing draggable prefab or statue parent");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn decoration instance
|
||||||
|
DecorationDraggableInstance instance = Instantiate(draggablePrefab, statueParent);
|
||||||
|
|
||||||
|
// Get canvas parent for drag context (used when picking up)
|
||||||
|
Transform canvasParent = statueParent.parent; // Typically the canvas or draggable container
|
||||||
|
|
||||||
|
// Create callbacks for outline show/hide
|
||||||
|
System.Action showOutlineCallback = DecorationMenuController.Instance != null ? (System.Action)DecorationMenuController.Instance.ShowStatueOutline : null;
|
||||||
|
System.Action hideOutlineCallback = DecorationMenuController.Instance != null ? (System.Action)DecorationMenuController.Instance.HideStatueOutline : null;
|
||||||
|
|
||||||
|
// Create context for placed decoration
|
||||||
|
var context = DecorationDragContext.CreateForPlaced(
|
||||||
|
decorationData,
|
||||||
|
this,
|
||||||
|
DecorationDataManager.Instance.Settings,
|
||||||
|
statueArea,
|
||||||
|
canvasParent,
|
||||||
|
showOutlineCallback,
|
||||||
|
hideOutlineCallback,
|
||||||
|
hideOutlineCallback
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize in "placed" state (skip drag logic)
|
||||||
|
instance.InitializeWithContext(context);
|
||||||
|
|
||||||
|
// Apply saved transform
|
||||||
|
instance.transform.localPosition = placement.localPosition;
|
||||||
|
instance.transform.localScale = placement.localScale;
|
||||||
|
instance.transform.localEulerAngles = new Vector3(0, 0, placement.rotation);
|
||||||
|
|
||||||
|
// Apply saved sizeDelta if available (overrides AuthoredSize from InitializeAsPlaced)
|
||||||
|
if (placement.sizeDelta != Vector2.zero)
|
||||||
|
{
|
||||||
|
RectTransform rectTransform = instance.GetComponent<RectTransform>();
|
||||||
|
if (rectTransform != null)
|
||||||
|
{
|
||||||
|
rectTransform.sizeDelta = placement.sizeDelta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sorting order if has SpriteRenderer
|
||||||
|
SpriteRenderer spriteRenderer = instance.GetComponent<SpriteRenderer>();
|
||||||
|
if (spriteRenderer != null)
|
||||||
|
{
|
||||||
|
spriteRenderer.sortingOrder = placement.sortingOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register with controller
|
||||||
|
RegisterDecoration(instance);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatueDecorationController] Restored decoration: {placement.decorationId} at {placement.localPosition}");
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -275,12 +462,33 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
{
|
{
|
||||||
base.OnManagedDestroy();
|
base.OnManagedDestroy();
|
||||||
|
|
||||||
// Cleanup button listener
|
// Cleanup button listeners
|
||||||
if (takePhotoButton != null)
|
if (takePhotoButton != null)
|
||||||
{
|
{
|
||||||
takePhotoButton.onClick.RemoveListener(OnTakePhoto);
|
takePhotoButton.onClick.RemoveListener(OnTakePhoto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: If ever picture gallery
|
||||||
|
// Gallery button cleanup (DISABLED - kept for future use)
|
||||||
|
// if (openGalleryButton != null)
|
||||||
|
// {
|
||||||
|
// openGalleryButton.onClick.RemoveListener(OnOpenGallery);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Singleton cleanup
|
||||||
|
if (Instance == this)
|
||||||
|
{
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void ExitToAppleHills()
|
||||||
|
{
|
||||||
|
// Replace with the actual scene name as set in Build Settings
|
||||||
|
var progress = new Progress<float>(p => Logging.Debug($"Loading progress: {p * 100:F0}%"));
|
||||||
|
await SceneManagerService.Instance.SwitchSceneAsync("AppleHillsOverworld", progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Core;
|
using Core;
|
||||||
using Core.Lifecycle;
|
using Core.Lifecycle;
|
||||||
using Minigames.StatueDressup.Utils;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
namespace Minigames.StatueDressup.Controllers
|
namespace Minigames.StatueDressup.Controllers
|
||||||
{
|
{
|
||||||
@@ -15,52 +15,81 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class StatuePhotoGalleryController : ManagedBehaviour
|
public class StatuePhotoGalleryController : ManagedBehaviour
|
||||||
{
|
{
|
||||||
|
public static StatuePhotoGalleryController Instance { get; private set; }
|
||||||
|
|
||||||
[Header("Gallery UI")]
|
[Header("Gallery UI")]
|
||||||
[SerializeField] private Transform gridContainer;
|
[SerializeField] private Transform gridContainer;
|
||||||
[SerializeField] private PhotoGridItem gridItemPrefab;
|
[SerializeField] private PhotoGridItem gridItemPrefab;
|
||||||
[SerializeField] private ScrollRect scrollRect;
|
|
||||||
|
|
||||||
[Header("Enlarged View")]
|
[Header("Enlarged View")]
|
||||||
[SerializeField] private GameObject enlargedViewPanel;
|
[SerializeField] private Transform enlargedContainer; // Container for enlarged preview (top layer)
|
||||||
[SerializeField] private Image enlargedPhotoImage;
|
[SerializeField] private GameObject backdrop; // Dark backdrop for enlarged view
|
||||||
[SerializeField] private Button closeEnlargedButton;
|
[SerializeField] private GameObject enlargedPreviewPrefab; // Prefab for enlarged preview (same as grid item)
|
||||||
[SerializeField] private Button deletePhotoButton;
|
|
||||||
[SerializeField] private Text photoInfoText;
|
|
||||||
|
|
||||||
[Header("Pagination")]
|
[Header("Pagination")]
|
||||||
[SerializeField] private Button loadMoreButton;
|
[SerializeField] private Button previousPageButton;
|
||||||
[SerializeField] private Text statusText;
|
[SerializeField] private Button nextPageButton;
|
||||||
|
[SerializeField] private Text pageStatusText;
|
||||||
|
|
||||||
[Header("Settings")]
|
private int currentPage;
|
||||||
[SerializeField] private int itemsPerPage = 20;
|
private List<string> allPhotoIds = new List<string>();
|
||||||
[SerializeField] private int thumbnailSize = 256;
|
private Dictionary<string, PhotoGridItem> activeGridItems = new Dictionary<string, PhotoGridItem>();
|
||||||
[SerializeField] private int maxCachedThumbnails = 50; // Keep recent thumbnails in memory
|
private Dictionary<string, Texture2D> thumbnailCache = new Dictionary<string, Texture2D>();
|
||||||
|
private Dictionary<string, Texture2D> fullPhotoCache = new Dictionary<string, Texture2D>(); // Cache full photos for enlargement
|
||||||
|
private Queue<string> thumbnailCacheOrder = new Queue<string>();
|
||||||
|
private bool isLoadingPage;
|
||||||
|
private PhotoEnlargeController enlargeController;
|
||||||
|
|
||||||
private int _currentPage = 0;
|
internal override void OnManagedAwake()
|
||||||
private List<string> _allPhotoIds = new List<string>();
|
{
|
||||||
private Dictionary<string, PhotoGridItem> _activeGridItems = new Dictionary<string, PhotoGridItem>();
|
base.OnManagedAwake();
|
||||||
private Dictionary<string, Texture2D> _thumbnailCache = new Dictionary<string, Texture2D>();
|
|
||||||
private Queue<string> _thumbnailCacheOrder = new Queue<string>();
|
// Singleton pattern
|
||||||
private string _currentEnlargedPhotoId = null;
|
if (Instance != null && Instance != this)
|
||||||
private Texture2D _currentEnlargedTexture = null;
|
{
|
||||||
|
Logging.Warning("[StatuePhotoGalleryController] Duplicate instance detected. Destroying duplicate.");
|
||||||
|
Destroy(gameObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
internal override void OnManagedStart()
|
internal override void OnManagedStart()
|
||||||
{
|
{
|
||||||
base.OnManagedStart();
|
base.OnManagedStart();
|
||||||
|
|
||||||
// Setup buttons
|
// Wait for data manager to be ready before initializing
|
||||||
if (closeEnlargedButton != null)
|
DecorationDataManager.WhenReady(() =>
|
||||||
closeEnlargedButton.onClick.AddListener(CloseEnlargedView);
|
{
|
||||||
|
InitializeGallery();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (deletePhotoButton != null)
|
/// <summary>
|
||||||
deletePhotoButton.onClick.AddListener(DeleteCurrentPhoto);
|
/// Initialize gallery once data manager is ready
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeGallery()
|
||||||
|
{
|
||||||
|
var settings = DecorationDataManager.Instance?.Settings;
|
||||||
|
|
||||||
if (loadMoreButton != null)
|
// Initialize enlarge controller
|
||||||
loadMoreButton.onClick.AddListener(LoadNextPage);
|
enlargeController = new PhotoEnlargeController(backdrop, enlargedContainer,
|
||||||
|
settings?.GalleryAnimationDuration ?? StatueDressupConstants.DefaultAnimationDuration);
|
||||||
|
|
||||||
// Hide enlarged view initially
|
// Setup page navigation buttons
|
||||||
if (enlargedViewPanel != null)
|
if (previousPageButton != null)
|
||||||
enlargedViewPanel.SetActive(false);
|
previousPageButton.onClick.AddListener(OnPreviousPageClicked);
|
||||||
|
|
||||||
|
if (nextPageButton != null)
|
||||||
|
nextPageButton.onClick.AddListener(OnNextPageClicked);
|
||||||
|
|
||||||
|
// Hide backdrop initially
|
||||||
|
if (backdrop != null)
|
||||||
|
backdrop.SetActive(false);
|
||||||
|
|
||||||
|
// Clear grid initially (in case there are leftover items from scene setup)
|
||||||
|
ClearGrid();
|
||||||
|
|
||||||
// Load first page
|
// Load first page
|
||||||
RefreshGallery();
|
RefreshGallery();
|
||||||
@@ -72,35 +101,35 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
public void RefreshGallery()
|
public void RefreshGallery()
|
||||||
{
|
{
|
||||||
// Clear existing items
|
// Clear existing items
|
||||||
ClearGallery();
|
ClearGrid();
|
||||||
|
|
||||||
// Get all photo IDs
|
// Get all photo IDs
|
||||||
_allPhotoIds = StatuePhotoManager.GetAllPhotoIds();
|
allPhotoIds = PhotoManager.GetAllPhotoIds(CaptureType.StatueMinigame);
|
||||||
_currentPage = 0;
|
currentPage = 0;
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoGalleryController] Gallery refreshed: {_allPhotoIds.Count} photos");
|
Logging.Debug($"[StatuePhotoGalleryController] Gallery refreshed: {allPhotoIds.Count} photos");
|
||||||
|
|
||||||
// Load first page
|
// Display first page
|
||||||
LoadNextPage();
|
DisplayCurrentPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load next page of photos
|
/// Display the current page of photos (clears grid and shows only current page)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void LoadNextPage()
|
private void DisplayCurrentPage()
|
||||||
{
|
{
|
||||||
List<string> pagePhotoIds = StatuePhotoManager.GetPhotoIdsPage(_currentPage, itemsPerPage);
|
if (isLoadingPage) return;
|
||||||
|
|
||||||
if (pagePhotoIds.Count == 0)
|
isLoadingPage = true;
|
||||||
{
|
|
||||||
if (loadMoreButton != null)
|
|
||||||
loadMoreButton.gameObject.SetActive(false);
|
|
||||||
|
|
||||||
UpdateStatusText($"All photos loaded ({_allPhotoIds.Count} total)");
|
// Clear current grid
|
||||||
return;
|
ClearGrid();
|
||||||
}
|
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoGalleryController] Loading page {_currentPage}: {pagePhotoIds.Count} items");
|
// Get photos for current page
|
||||||
|
int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage;
|
||||||
|
List<string> pagePhotoIds = PhotoManager.GetPhotoIdsPage(CaptureType.StatueMinigame, currentPage, itemsPerPage);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatuePhotoGalleryController] Displaying page {currentPage + 1}: {pagePhotoIds.Count} items");
|
||||||
|
|
||||||
// Spawn grid items for this page
|
// Spawn grid items for this page
|
||||||
foreach (string photoId in pagePhotoIds)
|
foreach (string photoId in pagePhotoIds)
|
||||||
@@ -108,14 +137,64 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
SpawnGridItem(photoId);
|
SpawnGridItem(photoId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentPage++;
|
// Update button states
|
||||||
|
UpdatePageButtons();
|
||||||
|
|
||||||
// Update UI state
|
// Update status text
|
||||||
bool hasMore = _currentPage * itemsPerPage < _allPhotoIds.Count;
|
int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage);
|
||||||
if (loadMoreButton != null)
|
UpdateStatusText($"Page {currentPage + 1}/{totalPages} ({allPhotoIds.Count} photos)");
|
||||||
loadMoreButton.gameObject.SetActive(hasMore);
|
|
||||||
|
|
||||||
UpdateStatusText($"Showing {_activeGridItems.Count} of {_allPhotoIds.Count} photos");
|
isLoadingPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update page navigation button states
|
||||||
|
/// </summary>
|
||||||
|
private void UpdatePageButtons()
|
||||||
|
{
|
||||||
|
int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage;
|
||||||
|
int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage);
|
||||||
|
|
||||||
|
// Enable/disable previous button
|
||||||
|
if (previousPageButton != null)
|
||||||
|
{
|
||||||
|
previousPageButton.interactable = currentPage > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/disable next button
|
||||||
|
if (nextPageButton != null)
|
||||||
|
{
|
||||||
|
nextPageButton.interactable = currentPage < totalPages - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigate to previous page
|
||||||
|
/// </summary>
|
||||||
|
private void OnPreviousPageClicked()
|
||||||
|
{
|
||||||
|
if (currentPage > 0)
|
||||||
|
{
|
||||||
|
currentPage--;
|
||||||
|
DisplayCurrentPage();
|
||||||
|
Logging.Debug($"[StatuePhotoGalleryController] Navigated to previous page: {currentPage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigate to next page
|
||||||
|
/// </summary>
|
||||||
|
private void OnNextPageClicked()
|
||||||
|
{
|
||||||
|
int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage;
|
||||||
|
int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage);
|
||||||
|
|
||||||
|
if (currentPage < totalPages - 1)
|
||||||
|
{
|
||||||
|
currentPage++;
|
||||||
|
DisplayCurrentPage();
|
||||||
|
Logging.Debug($"[StatuePhotoGalleryController] Navigated to next page: {currentPage}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -123,7 +202,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void SpawnGridItem(string photoId)
|
private void SpawnGridItem(string photoId)
|
||||||
{
|
{
|
||||||
if (_activeGridItems.ContainsKey(photoId))
|
if (activeGridItems.ContainsKey(photoId))
|
||||||
{
|
{
|
||||||
Logging.Warning($"[StatuePhotoGalleryController] Grid item already exists: {photoId}");
|
Logging.Warning($"[StatuePhotoGalleryController] Grid item already exists: {photoId}");
|
||||||
return;
|
return;
|
||||||
@@ -132,7 +211,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
PhotoGridItem gridItem = Instantiate(gridItemPrefab, gridContainer);
|
PhotoGridItem gridItem = Instantiate(gridItemPrefab, gridContainer);
|
||||||
gridItem.Initialize(photoId, this);
|
gridItem.Initialize(photoId, this);
|
||||||
|
|
||||||
_activeGridItems[photoId] = gridItem;
|
activeGridItems[photoId] = gridItem;
|
||||||
|
|
||||||
// Load thumbnail asynchronously
|
// Load thumbnail asynchronously
|
||||||
StartCoroutine(LoadThumbnailAsync(photoId, gridItem));
|
StartCoroutine(LoadThumbnailAsync(photoId, gridItem));
|
||||||
@@ -144,7 +223,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
private IEnumerator LoadThumbnailAsync(string photoId, PhotoGridItem gridItem)
|
private IEnumerator LoadThumbnailAsync(string photoId, PhotoGridItem gridItem)
|
||||||
{
|
{
|
||||||
// Check cache first
|
// Check cache first
|
||||||
if (_thumbnailCache.TryGetValue(photoId, out Texture2D cachedThumbnail))
|
if (thumbnailCache.TryGetValue(photoId, out Texture2D cachedThumbnail))
|
||||||
{
|
{
|
||||||
gridItem.SetThumbnail(cachedThumbnail);
|
gridItem.SetThumbnail(cachedThumbnail);
|
||||||
yield break;
|
yield break;
|
||||||
@@ -154,7 +233,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
yield return null;
|
yield return null;
|
||||||
|
|
||||||
// Load full photo
|
// Load full photo
|
||||||
Texture2D fullPhoto = StatuePhotoManager.LoadPhoto(photoId);
|
Texture2D fullPhoto = PhotoManager.LoadPhoto(CaptureType.StatueMinigame, photoId);
|
||||||
|
|
||||||
if (fullPhoto == null)
|
if (fullPhoto == null)
|
||||||
{
|
{
|
||||||
@@ -163,7 +242,8 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create thumbnail
|
// Create thumbnail
|
||||||
Texture2D thumbnail = StatuePhotoManager.CreateThumbnail(fullPhoto, thumbnailSize);
|
int thumbSize = DecorationDataManager.Instance?.Settings?.GalleryThumbnailSize ?? StatueDressupConstants.DefaultThumbnailSize;
|
||||||
|
Texture2D thumbnail = PhotoManager.CreateThumbnail(fullPhoto, thumbSize);
|
||||||
|
|
||||||
// Destroy full photo immediately (we only need thumbnail)
|
// Destroy full photo immediately (we only need thumbnail)
|
||||||
Destroy(fullPhoto);
|
Destroy(fullPhoto);
|
||||||
@@ -184,211 +264,162 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
private void CacheThumbnail(string photoId, Texture2D thumbnail)
|
private void CacheThumbnail(string photoId, Texture2D thumbnail)
|
||||||
{
|
{
|
||||||
// Add to cache
|
// Add to cache
|
||||||
_thumbnailCache[photoId] = thumbnail;
|
thumbnailCache[photoId] = thumbnail;
|
||||||
_thumbnailCacheOrder.Enqueue(photoId);
|
thumbnailCacheOrder.Enqueue(photoId);
|
||||||
|
|
||||||
// Evict oldest if over limit
|
// Evict oldest if over limit
|
||||||
while (_thumbnailCache.Count > maxCachedThumbnails && _thumbnailCacheOrder.Count > 0)
|
int maxCached = DecorationDataManager.Instance?.Settings?.GalleryMaxCachedThumbnails ?? StatueDressupConstants.DefaultMaxCachedThumbnails;
|
||||||
|
while (thumbnailCache.Count > maxCached && thumbnailCacheOrder.Count > 0)
|
||||||
{
|
{
|
||||||
string oldestId = _thumbnailCacheOrder.Dequeue();
|
string oldestId = thumbnailCacheOrder.Dequeue();
|
||||||
|
|
||||||
if (_thumbnailCache.TryGetValue(oldestId, out Texture2D oldThumbnail))
|
if (thumbnailCache.TryGetValue(oldestId, out Texture2D oldThumbnail))
|
||||||
{
|
{
|
||||||
Destroy(oldThumbnail);
|
Destroy(oldThumbnail);
|
||||||
_thumbnailCache.Remove(oldestId);
|
thumbnailCache.Remove(oldestId);
|
||||||
Logging.Debug($"[StatuePhotoGalleryController] Evicted thumbnail from cache: {oldestId}");
|
Logging.Debug($"[StatuePhotoGalleryController] Evicted thumbnail from cache: {oldestId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show enlarged view of a photo (called by PhotoGridItem)
|
/// Enlarge a photo (called by PhotoGridItem)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ShowEnlargedView(string photoId)
|
public void OnGridItemClicked(PhotoGridItem gridItem, string photoId)
|
||||||
{
|
{
|
||||||
if (enlargedViewPanel == null || enlargedPhotoImage == null)
|
if (enlargeController == null)
|
||||||
{
|
{
|
||||||
Logging.Warning("[StatuePhotoGalleryController] Enlarged view UI not configured");
|
Logging.Error("[StatuePhotoGalleryController] Enlarge controller not initialized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoGalleryController] Showing enlarged view: {photoId}");
|
// If already enlarged, shrink it
|
||||||
|
if (enlargeController.IsPhotoEnlarged)
|
||||||
// Clear previous enlarged texture
|
|
||||||
if (_currentEnlargedTexture != null)
|
|
||||||
{
|
{
|
||||||
Destroy(_currentEnlargedTexture);
|
enlargeController.ShrinkPhoto();
|
||||||
_currentEnlargedTexture = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load full-size photo
|
|
||||||
_currentEnlargedTexture = StatuePhotoManager.LoadPhoto(photoId);
|
|
||||||
|
|
||||||
if (_currentEnlargedTexture == null)
|
|
||||||
{
|
|
||||||
Logging.Error($"[StatuePhotoGalleryController] Failed to load enlarged photo: {photoId}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create sprite from texture
|
Logging.Debug($"[StatuePhotoGalleryController] Enlarging photo: {photoId}");
|
||||||
Sprite enlargedSprite = Sprite.Create(
|
|
||||||
_currentEnlargedTexture,
|
|
||||||
new Rect(0, 0, _currentEnlargedTexture.width, _currentEnlargedTexture.height),
|
|
||||||
new Vector2(0.5f, 0.5f)
|
|
||||||
);
|
|
||||||
|
|
||||||
enlargedPhotoImage.sprite = enlargedSprite;
|
float enlargedScale = DecorationDataManager.Instance?.Settings?.GalleryEnlargedScale ?? StatueDressupConstants.DefaultEnlargedScale;
|
||||||
_currentEnlargedPhotoId = photoId;
|
|
||||||
|
|
||||||
// Update photo info
|
// Check cache first
|
||||||
UpdatePhotoInfo(photoId);
|
if (fullPhotoCache.TryGetValue(photoId, out Texture2D fullPhoto))
|
||||||
|
|
||||||
// Show panel
|
|
||||||
enlargedViewPanel.SetActive(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Close enlarged view
|
|
||||||
/// </summary>
|
|
||||||
private void CloseEnlargedView()
|
|
||||||
{
|
|
||||||
if (enlargedViewPanel != null)
|
|
||||||
enlargedViewPanel.SetActive(false);
|
|
||||||
|
|
||||||
// Clean up texture
|
|
||||||
if (_currentEnlargedTexture != null)
|
|
||||||
{
|
{
|
||||||
Destroy(_currentEnlargedTexture);
|
// Use cached photo
|
||||||
_currentEnlargedTexture = null;
|
enlargeController.EnlargePhoto(gridItem, enlargedPreviewPrefab != null ? enlargedPreviewPrefab : gridItem.gameObject, fullPhoto, enlargedScale);
|
||||||
}
|
|
||||||
|
|
||||||
_currentEnlargedPhotoId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delete currently viewed photo
|
|
||||||
/// </summary>
|
|
||||||
private void DeleteCurrentPhoto()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(_currentEnlargedPhotoId))
|
|
||||||
{
|
|
||||||
Logging.Warning("[StatuePhotoGalleryController] No photo selected for deletion");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string photoIdToDelete = _currentEnlargedPhotoId;
|
|
||||||
|
|
||||||
// Close enlarged view first
|
|
||||||
CloseEnlargedView();
|
|
||||||
|
|
||||||
// Delete photo
|
|
||||||
bool deleted = StatuePhotoManager.DeletePhoto(photoIdToDelete);
|
|
||||||
|
|
||||||
if (deleted)
|
|
||||||
{
|
|
||||||
// Remove from grid
|
|
||||||
if (_activeGridItems.TryGetValue(photoIdToDelete, out PhotoGridItem gridItem))
|
|
||||||
{
|
|
||||||
Destroy(gridItem.gameObject);
|
|
||||||
_activeGridItems.Remove(photoIdToDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from cache
|
|
||||||
if (_thumbnailCache.TryGetValue(photoIdToDelete, out Texture2D thumbnail))
|
|
||||||
{
|
|
||||||
Destroy(thumbnail);
|
|
||||||
_thumbnailCache.Remove(photoIdToDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh photo list
|
|
||||||
_allPhotoIds.Remove(photoIdToDelete);
|
|
||||||
|
|
||||||
UpdateStatusText($"Photo deleted. {_allPhotoIds.Count} photos remaining");
|
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoGalleryController] Photo deleted: {photoIdToDelete}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update photo info text in enlarged view
|
|
||||||
/// </summary>
|
|
||||||
private void UpdatePhotoInfo(string photoId)
|
|
||||||
{
|
|
||||||
if (photoInfoText == null) return;
|
|
||||||
|
|
||||||
StatuePhotoManager.PhotoMetadata metadata = StatuePhotoManager.GetPhotoMetadata(photoId);
|
|
||||||
|
|
||||||
if (metadata != null)
|
|
||||||
{
|
|
||||||
System.DateTime timestamp = System.DateTime.Parse(metadata.timestamp);
|
|
||||||
string dateStr = timestamp.ToString("MMM dd, yyyy hh:mm tt");
|
|
||||||
|
|
||||||
float fileSizeMB = metadata.fileSizeBytes / (1024f * 1024f);
|
|
||||||
|
|
||||||
photoInfoText.text = $"Date: {dateStr}\n" +
|
|
||||||
$"Decorations: {metadata.decorationCount}\n" +
|
|
||||||
$"Size: {fileSizeMB:F2} MB";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
photoInfoText.text = "Photo information unavailable";
|
// Load full-size photo
|
||||||
|
fullPhoto = PhotoManager.LoadPhoto(CaptureType.StatueMinigame, photoId);
|
||||||
|
|
||||||
|
if (fullPhoto == null)
|
||||||
|
{
|
||||||
|
Logging.Error($"[StatuePhotoGalleryController] Failed to load photo: {photoId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache it (limited cache)
|
||||||
|
if (fullPhotoCache.Count < 10) // Keep only recent 10 full photos
|
||||||
|
{
|
||||||
|
fullPhotoCache[photoId] = fullPhoto;
|
||||||
|
}
|
||||||
|
|
||||||
|
enlargeController.EnlargePhoto(gridItem, enlargedPreviewPrefab != null ? enlargedPreviewPrefab : gridItem.gameObject, fullPhoto, enlargedScale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleanup when gallery is closed
|
||||||
|
/// </summary>
|
||||||
|
public void CleanupGallery()
|
||||||
|
{
|
||||||
|
if (enlargeController != null)
|
||||||
|
{
|
||||||
|
enlargeController.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up cached full photos
|
||||||
|
foreach (var photo in fullPhotoCache.Values)
|
||||||
|
{
|
||||||
|
if (photo != null)
|
||||||
|
{
|
||||||
|
Destroy(photo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fullPhotoCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update status text
|
/// Update status text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateStatusText(string message)
|
private void UpdateStatusText(string message)
|
||||||
{
|
{
|
||||||
if (statusText != null)
|
if (pageStatusText != null)
|
||||||
statusText.text = message;
|
pageStatusText.text = message;
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoGalleryController] Status: {message}");
|
Logging.Debug($"[StatuePhotoGalleryController] Status: {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clear all grid items and cached data
|
/// Clear only the grid items (used when switching pages)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ClearGallery()
|
private void ClearGrid()
|
||||||
{
|
{
|
||||||
// Destroy grid items
|
// Destroy grid items
|
||||||
foreach (var gridItem in _activeGridItems.Values)
|
foreach (var gridItem in activeGridItems.Values)
|
||||||
{
|
{
|
||||||
if (gridItem != null)
|
if (gridItem != null)
|
||||||
Destroy(gridItem.gameObject);
|
Destroy(gridItem.gameObject);
|
||||||
}
|
}
|
||||||
_activeGridItems.Clear();
|
activeGridItems.Clear();
|
||||||
|
|
||||||
|
Logging.Debug("[StatuePhotoGalleryController] Grid cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all grid items and cached data (full cleanup)
|
||||||
|
/// </summary>
|
||||||
|
private void ClearGallery()
|
||||||
|
{
|
||||||
|
ClearGrid();
|
||||||
|
|
||||||
// Clear thumbnail cache
|
// Clear thumbnail cache
|
||||||
foreach (var thumbnail in _thumbnailCache.Values)
|
foreach (var thumbnail in thumbnailCache.Values)
|
||||||
{
|
{
|
||||||
if (thumbnail != null)
|
if (thumbnail != null)
|
||||||
Destroy(thumbnail);
|
Destroy(thumbnail);
|
||||||
}
|
}
|
||||||
_thumbnailCache.Clear();
|
thumbnailCache.Clear();
|
||||||
_thumbnailCacheOrder.Clear();
|
thumbnailCacheOrder.Clear();
|
||||||
|
|
||||||
Logging.Debug("[StatuePhotoGalleryController] Gallery cleared");
|
Logging.Debug("[StatuePhotoGalleryController] Gallery fully cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void OnManagedDestroy()
|
internal override void OnManagedDestroy()
|
||||||
{
|
{
|
||||||
base.OnManagedDestroy();
|
base.OnManagedDestroy();
|
||||||
|
|
||||||
// Cleanup
|
// Singleton cleanup
|
||||||
|
if (Instance == this)
|
||||||
|
{
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up cached textures
|
||||||
ClearGallery();
|
ClearGallery();
|
||||||
CloseEnlargedView();
|
CleanupGallery();
|
||||||
|
|
||||||
// Unsubscribe buttons
|
// Unsubscribe buttons
|
||||||
if (closeEnlargedButton != null)
|
if (previousPageButton != null)
|
||||||
closeEnlargedButton.onClick.RemoveListener(CloseEnlargedView);
|
previousPageButton.onClick.RemoveListener(OnPreviousPageClicked);
|
||||||
|
|
||||||
if (deletePhotoButton != null)
|
if (nextPageButton != null)
|
||||||
deletePhotoButton.onClick.RemoveListener(DeleteCurrentPhoto);
|
nextPageButton.onClick.RemoveListener(OnNextPageClicked);
|
||||||
|
|
||||||
if (loadMoreButton != null)
|
|
||||||
loadMoreButton.onClick.RemoveListener(LoadNextPage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event data passed with decoration events for VFX/SFX responses
|
||||||
|
/// </summary>
|
||||||
|
public class DecorationEventData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The decoration data (sprite, id, etc.)
|
||||||
|
/// </summary>
|
||||||
|
public DecorationData DecorationData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The GameObject instance of the decoration (if applicable)
|
||||||
|
/// </summary>
|
||||||
|
public GameObject Instance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position where the event occurred (world space)
|
||||||
|
/// </summary>
|
||||||
|
public Vector3 Position { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this decoration was dragged from the menu or from the statue
|
||||||
|
/// </summary>
|
||||||
|
public bool FromStatue { get; set; }
|
||||||
|
|
||||||
|
public DecorationEventData(DecorationData data, GameObject instance, Vector3 position, bool fromStatue = false)
|
||||||
|
{
|
||||||
|
DecorationData = data;
|
||||||
|
Instance = instance;
|
||||||
|
Position = position;
|
||||||
|
FromStatue = fromStatue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5d9b3e7728c0420c8290986c31d5b738
|
||||||
|
timeCreated: 1764248890
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata for a single decoration placement
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class DecorationPlacement
|
||||||
|
{
|
||||||
|
public string decorationId; // Unique ID to load decoration
|
||||||
|
public Vector2 localPosition; // Position relative to statue
|
||||||
|
public Vector2 localScale; // Scale relative to statue
|
||||||
|
public Vector2 sizeDelta; // UI RectTransform size (for UI decorations)
|
||||||
|
public float rotation; // Z rotation in degrees
|
||||||
|
public int sortingOrder; // Sprite sorting order
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coordinate system type used when saving decoration positions
|
||||||
|
/// </summary>
|
||||||
|
public enum CoordinateSystemType
|
||||||
|
{
|
||||||
|
WorldSpace, // Regular Transform (world units)
|
||||||
|
UIRectTransform // UI RectTransform (pixel coordinates)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of decoration placements for a statue
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class StatueDecorationData
|
||||||
|
{
|
||||||
|
public string photoId; // Associated photo ID
|
||||||
|
public string timestamp; // When captured
|
||||||
|
public CoordinateSystemType coordinateSystem; // Source coordinate system
|
||||||
|
public Vector2 sourceStatueSize; // Size of statue in source units (for conversion)
|
||||||
|
public List<DecorationPlacement> placements = new List<DecorationPlacement>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bde3a2b8ef7247d29967999bb5e9dbd8
|
||||||
|
timeCreated: 1764163703
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/Display.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/Display.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 21f5742e8db64d52a293d822c93df4f3
|
||||||
|
timeCreated: 1764163758
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
using Core;
|
||||||
|
using Core.Lifecycle;
|
||||||
|
using Minigames.StatueDressup.Controllers;
|
||||||
|
using Minigames.StatueDressup.Data;
|
||||||
|
using UnityEngine;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Display
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads decoration metadata and reconstructs decorations on a statue sprite.
|
||||||
|
/// Place this component on a GameObject with a SpriteRenderer showing the statue.
|
||||||
|
/// On Start, loads all DecorationData via Addressables label, then spawns decorations from metadata.
|
||||||
|
/// </summary>
|
||||||
|
public class StatueDecorationLoader : ManagedBehaviour
|
||||||
|
{
|
||||||
|
[Header("Settings")]
|
||||||
|
[SerializeField] private SpriteRenderer statueSpriteRenderer;
|
||||||
|
|
||||||
|
[Tooltip("Load specific photo ID, or leave empty to load latest")]
|
||||||
|
[SerializeField] private string specificPhotoId = "";
|
||||||
|
|
||||||
|
[Tooltip("Apply pivot offset to position decorations relative to sprite's visual center instead of pivot point")]
|
||||||
|
[SerializeField] private bool applyPivotOffset = true;
|
||||||
|
|
||||||
|
[Header("Debug")]
|
||||||
|
[SerializeField] private bool showDebugInfo = true;
|
||||||
|
|
||||||
|
internal override void OnManagedStart()
|
||||||
|
{
|
||||||
|
base.OnManagedStart();
|
||||||
|
|
||||||
|
if (statueSpriteRenderer == null)
|
||||||
|
statueSpriteRenderer = GetComponent<SpriteRenderer>();
|
||||||
|
|
||||||
|
if (statueSpriteRenderer == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[StatueDecorationLoader] No SpriteRenderer found! Please assign statueSpriteRenderer.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for decoration data manager to be ready (static method handles null instance)
|
||||||
|
DecorationDataManager.WhenReady(() =>
|
||||||
|
{
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug("[StatueDecorationLoader] DecorationData ready, displaying decorations");
|
||||||
|
}
|
||||||
|
LoadAndDisplayDecorations();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load decoration metadata and spawn decorations
|
||||||
|
/// </summary>
|
||||||
|
public void LoadAndDisplayDecorations()
|
||||||
|
{
|
||||||
|
// Check if DecorationData is loaded via manager
|
||||||
|
if (DecorationDataManager.Instance == null || !DecorationDataManager.Instance.IsReady)
|
||||||
|
{
|
||||||
|
Logging.Warning("[StatueDecorationLoader] DecorationDataManager not ready. Cannot display decorations.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load metadata
|
||||||
|
StatueDecorationData data = string.IsNullOrEmpty(specificPhotoId)
|
||||||
|
? PhotoManager.LoadLatestDecorationMetadata<StatueDecorationData>(CaptureType.StatueMinigame)
|
||||||
|
: PhotoManager.LoadDecorationMetadata<StatueDecorationData>(CaptureType.StatueMinigame, specificPhotoId);
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[StatueDecorationLoader] No decoration metadata found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Loading {data.placements.Count} decorations from {data.photoId}");
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Source coordinate system: {data.coordinateSystem}, statue size: {data.sourceStatueSize}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing decorations (in case reloading)
|
||||||
|
ClearDecorations();
|
||||||
|
|
||||||
|
// Calculate coordinate conversion factor if needed
|
||||||
|
float conversionFactor = CalculateCoordinateConversion(data, out Vector2 targetStatueWorldSize);
|
||||||
|
|
||||||
|
// Spawn each decoration synchronously (data already loaded)
|
||||||
|
int successCount = 0;
|
||||||
|
foreach (var placement in data.placements)
|
||||||
|
{
|
||||||
|
if (SpawnDecoration(placement, conversionFactor, data.sourceStatueSize, targetStatueWorldSize))
|
||||||
|
{
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Successfully loaded {successCount}/{data.placements.Count} decorations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate coordinate conversion factor between source and target coordinate systems
|
||||||
|
/// </summary>
|
||||||
|
private float CalculateCoordinateConversion(StatueDecorationData data, out Vector2 targetStatueWorldSize)
|
||||||
|
{
|
||||||
|
// If source was world space and we're also world space, no conversion needed
|
||||||
|
if (data.coordinateSystem == CoordinateSystemType.WorldSpace)
|
||||||
|
{
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug("[StatueDecorationLoader] No coordinate conversion needed (WorldSpace → WorldSpace)");
|
||||||
|
}
|
||||||
|
targetStatueWorldSize = Vector2.one;
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source was UI RectTransform (pixels), target is WorldSpace (units)
|
||||||
|
// Need to convert from source statue pixel size to target statue VISUAL world size
|
||||||
|
|
||||||
|
// Get target statue VISUAL size (including transform scale)
|
||||||
|
Vector2 spriteNativeSize = statueSpriteRenderer.sprite.bounds.size;
|
||||||
|
Vector3 spriteScale = statueSpriteRenderer.transform.localScale;
|
||||||
|
targetStatueWorldSize = new Vector2(
|
||||||
|
spriteNativeSize.x * spriteScale.x,
|
||||||
|
spriteNativeSize.y * spriteScale.y
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate conversion factor (target size / source size)
|
||||||
|
float conversionX = targetStatueWorldSize.x / data.sourceStatueSize.x;
|
||||||
|
float conversionY = targetStatueWorldSize.y / data.sourceStatueSize.y;
|
||||||
|
|
||||||
|
// Use average of X and Y for uniform scaling (or could use separate X/Y)
|
||||||
|
float conversionFactor = (conversionX + conversionY) / 2f;
|
||||||
|
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Coordinate conversion: UI({data.sourceStatueSize}px) → World({targetStatueWorldSize}units)");
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Sprite native: {spriteNativeSize}, scale: {spriteScale}, conversion factor: {conversionFactor:F3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversionFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawn a single decoration from placement data
|
||||||
|
/// Looks up DecorationData from manager and applies coordinate conversion
|
||||||
|
/// </summary>
|
||||||
|
private bool SpawnDecoration(DecorationPlacement placement, float conversionFactor, Vector2 sourceStatueSize, Vector2 targetStatueWorldSize)
|
||||||
|
{
|
||||||
|
// Look up DecorationData from manager
|
||||||
|
if (!DecorationDataManager.Instance.TryGetData(placement.decorationId, out DecorationData decorationData))
|
||||||
|
{
|
||||||
|
Logging.Warning($"[StatueDecorationLoader] DecorationData not found for ID: {placement.decorationId}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sprite from DecorationData
|
||||||
|
Sprite decorationSprite = decorationData.DecorationSprite;
|
||||||
|
|
||||||
|
if (decorationSprite == null)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[StatueDecorationLoader] DecorationData has null sprite: {placement.decorationId}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GameObject for decoration as child of statue sprite renderer
|
||||||
|
GameObject decorationObj = new GameObject($"Decoration_{placement.decorationId}");
|
||||||
|
decorationObj.transform.SetParent(statueSpriteRenderer.transform, false);
|
||||||
|
|
||||||
|
// Add SpriteRenderer
|
||||||
|
SpriteRenderer spriteRenderer = decorationObj.AddComponent<SpriteRenderer>();
|
||||||
|
spriteRenderer.sprite = decorationSprite;
|
||||||
|
spriteRenderer.sortingLayerName = "Foreground";
|
||||||
|
spriteRenderer.sortingOrder = statueSpriteRenderer.sortingOrder + placement.sortingOrder;
|
||||||
|
|
||||||
|
// ===== POSITION CALCULATION =====
|
||||||
|
// Calculate pivot offset - decorations should be positioned relative to sprite's visual center, not pivot
|
||||||
|
Sprite statueSprite = statueSpriteRenderer.sprite;
|
||||||
|
Bounds spriteBounds = statueSprite.bounds;
|
||||||
|
|
||||||
|
// Sprite.bounds.center gives us the offset from pivot to visual center in local space
|
||||||
|
Vector2 pivotToCenterOffset = spriteBounds.center;
|
||||||
|
|
||||||
|
// Convert UI pixel position to world space position
|
||||||
|
Vector3 worldPosition = placement.localPosition * conversionFactor;
|
||||||
|
|
||||||
|
// Convert world position to local position (accounting for parent scale)
|
||||||
|
Vector3 parentScale = statueSpriteRenderer.transform.localScale;
|
||||||
|
Vector3 localPosition = new Vector3(
|
||||||
|
worldPosition.x / parentScale.x,
|
||||||
|
worldPosition.y / parentScale.y,
|
||||||
|
worldPosition.z
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply pivot offset IN LOCAL SPACE (after conversion from world to local)
|
||||||
|
// This ensures both values are in the same coordinate system
|
||||||
|
if (applyPivotOffset)
|
||||||
|
{
|
||||||
|
localPosition += new Vector3(pivotToCenterOffset.x, pivotToCenterOffset.y, 0f);
|
||||||
|
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Pivot offset APPLIED: {pivotToCenterOffset} (bounds center: {spriteBounds.center}, pivot normalized: {statueSprite.pivot / statueSprite.rect.size})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Pivot offset SKIPPED (applyPivotOffset = false)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== SCALE CALCULATION =====
|
||||||
|
Vector3 localScale = placement.localScale;
|
||||||
|
|
||||||
|
if (placement.sizeDelta != Vector2.zero)
|
||||||
|
{
|
||||||
|
// Calculate relative size in UI (decoration size / statue size)
|
||||||
|
Vector2 relativeSizeUI = new Vector2(
|
||||||
|
placement.sizeDelta.x / sourceStatueSize.x,
|
||||||
|
placement.sizeDelta.y / sourceStatueSize.y
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate target world size for decoration (relative size × statue world size)
|
||||||
|
Vector2 targetDecorationWorldSize = new Vector2(
|
||||||
|
relativeSizeUI.x * targetStatueWorldSize.x,
|
||||||
|
relativeSizeUI.y * targetStatueWorldSize.y
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get decoration sprite's native world size
|
||||||
|
Vector2 decorationNativeWorldSize = decorationSprite.bounds.size;
|
||||||
|
|
||||||
|
// Calculate world scale needed to achieve target size
|
||||||
|
Vector2 worldScaleNeeded = new Vector2(
|
||||||
|
targetDecorationWorldSize.x / decorationNativeWorldSize.x,
|
||||||
|
targetDecorationWorldSize.y / decorationNativeWorldSize.y
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply saved scale multiplier
|
||||||
|
worldScaleNeeded = new Vector2(
|
||||||
|
worldScaleNeeded.x * placement.localScale.x,
|
||||||
|
worldScaleNeeded.y * placement.localScale.y
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert world scale to local scale (accounting for parent scale)
|
||||||
|
localScale = new Vector3(
|
||||||
|
worldScaleNeeded.x / parentScale.x,
|
||||||
|
worldScaleNeeded.y / parentScale.y,
|
||||||
|
1f
|
||||||
|
);
|
||||||
|
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Size calc: UI sizeDelta={placement.sizeDelta}, relativeUI={relativeSizeUI}");
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Target world size={targetDecorationWorldSize}, native={decorationNativeWorldSize}, worldScale={worldScaleNeeded}, localScale={localScale}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No sizeDelta saved, just apply saved scale divided by parent scale
|
||||||
|
localScale = new Vector3(
|
||||||
|
placement.localScale.x / parentScale.x,
|
||||||
|
placement.localScale.y / parentScale.y,
|
||||||
|
1f
|
||||||
|
);
|
||||||
|
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] No sizeDelta saved, using scale directly (compensated): {localScale}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply transform
|
||||||
|
decorationObj.transform.localPosition = localPosition;
|
||||||
|
decorationObj.transform.localScale = localScale;
|
||||||
|
decorationObj.transform.localEulerAngles = new Vector3(0, 0, placement.rotation);
|
||||||
|
|
||||||
|
if (showDebugInfo)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Spawned: {placement.decorationId}");
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Position: UI={placement.localPosition} → world={worldPosition} → local={localPosition}");
|
||||||
|
Logging.Debug($"[StatueDecorationLoader] Parent scale: {parentScale}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all existing decorations (children of statue sprite renderer that are decorations)
|
||||||
|
/// </summary>
|
||||||
|
public void ClearDecorations()
|
||||||
|
{
|
||||||
|
if (statueSpriteRenderer == null) return;
|
||||||
|
|
||||||
|
Transform parent = statueSpriteRenderer.transform;
|
||||||
|
|
||||||
|
// Remove all children that are decorations (identified by name pattern)
|
||||||
|
for (int i = parent.childCount - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
GameObject child = parent.GetChild(i).gameObject;
|
||||||
|
|
||||||
|
// Only destroy objects that look like decorations (by name pattern)
|
||||||
|
if (child.name.StartsWith("Decoration_"))
|
||||||
|
{
|
||||||
|
if (Application.isPlaying)
|
||||||
|
{
|
||||||
|
Destroy(child);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DestroyImmediate(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup handled by DecorationDataManager - no need for OnDestroy here
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reload decorations (useful for testing)
|
||||||
|
/// </summary>
|
||||||
|
[ContextMenu("Reload Decorations")]
|
||||||
|
public void ReloadDecorations()
|
||||||
|
{
|
||||||
|
LoadAndDisplayDecorations();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load specific photo's decorations
|
||||||
|
/// </summary>
|
||||||
|
public void LoadSpecificPhoto(string photoId)
|
||||||
|
{
|
||||||
|
specificPhotoId = photoId;
|
||||||
|
LoadAndDisplayDecorations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 50d0f4591bbd40fc81dc615fa465e0c5
|
||||||
|
timeCreated: 1764163758
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
using Minigames.StatueDressup.Data;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.DragDrop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Context object for initializing draggable decorations.
|
||||||
|
/// Consolidates multiple parameters into a single, cohesive object.
|
||||||
|
/// </summary>
|
||||||
|
public class DecorationDragContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The decoration data to display
|
||||||
|
/// </summary>
|
||||||
|
public DecorationData Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The statue outline area for overlap detection
|
||||||
|
/// </summary>
|
||||||
|
public RectTransform StatueOutline { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent transform for decorations placed on statue
|
||||||
|
/// </summary>
|
||||||
|
public Transform StatueParent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent transform for dragging (usually canvas or draggable container)
|
||||||
|
/// </summary>
|
||||||
|
public Transform CanvasParent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controller for registering/unregistering decorations
|
||||||
|
/// </summary>
|
||||||
|
public Controllers.StatueDecorationController Controller { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Settings for the minigame
|
||||||
|
/// </summary>
|
||||||
|
public AppleHills.Core.Settings.IStatueDressupSettings Settings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback when drag operation finishes (success or failure)
|
||||||
|
/// </summary>
|
||||||
|
public System.Action OnFinished { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback to show statue outline during drag
|
||||||
|
/// </summary>
|
||||||
|
public System.Action OnShowOutline { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback to hide statue outline after drag
|
||||||
|
/// </summary>
|
||||||
|
public System.Action OnHideOutline { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this decoration is being initialized as already placed (from saved state)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPlaced { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a context for a new decoration being dragged from menu
|
||||||
|
/// </summary>
|
||||||
|
public static DecorationDragContext CreateForNewDrag(
|
||||||
|
DecorationData data,
|
||||||
|
RectTransform statueOutline,
|
||||||
|
Transform statueParent,
|
||||||
|
Controllers.StatueDecorationController controller,
|
||||||
|
AppleHills.Core.Settings.IStatueDressupSettings settings,
|
||||||
|
System.Action onFinished,
|
||||||
|
System.Action onShowOutline,
|
||||||
|
System.Action onHideOutline)
|
||||||
|
{
|
||||||
|
return new DecorationDragContext
|
||||||
|
{
|
||||||
|
Data = data,
|
||||||
|
StatueOutline = statueOutline,
|
||||||
|
StatueParent = statueParent,
|
||||||
|
Controller = controller,
|
||||||
|
Settings = settings,
|
||||||
|
OnFinished = onFinished,
|
||||||
|
OnShowOutline = onShowOutline,
|
||||||
|
OnHideOutline = onHideOutline,
|
||||||
|
IsPlaced = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a context for a decoration being loaded from saved state
|
||||||
|
/// </summary>
|
||||||
|
public static DecorationDragContext CreateForPlaced(
|
||||||
|
DecorationData data,
|
||||||
|
Controllers.StatueDecorationController controller,
|
||||||
|
AppleHills.Core.Settings.IStatueDressupSettings settings,
|
||||||
|
RectTransform statueOutline = null,
|
||||||
|
Transform canvasParent = null,
|
||||||
|
System.Action onShowOutline = null,
|
||||||
|
System.Action onHideOutline = null,
|
||||||
|
System.Action onFinished = null)
|
||||||
|
{
|
||||||
|
return new DecorationDragContext
|
||||||
|
{
|
||||||
|
Data = data,
|
||||||
|
StatueOutline = statueOutline,
|
||||||
|
CanvasParent = canvasParent,
|
||||||
|
Controller = controller,
|
||||||
|
Settings = settings,
|
||||||
|
OnShowOutline = onShowOutline,
|
||||||
|
OnHideOutline = onHideOutline,
|
||||||
|
OnFinished = onFinished,
|
||||||
|
IsPlaced = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8f2a2c34f6ce482ba117f39cb669e11f
|
||||||
|
timeCreated: 1764240188
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using Core;
|
using Core;
|
||||||
using Minigames.StatueDressup.Controllers;
|
using Minigames.StatueDressup.Controllers;
|
||||||
using Minigames.StatueDressup.Data;
|
using Minigames.StatueDressup.Data;
|
||||||
using Minigames.StatueDressup.Utils;
|
using Minigames.StatueDressup.Events;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
namespace Minigames.StatueDressup.DragDrop
|
namespace Minigames.StatueDressup.DragDrop
|
||||||
{
|
{
|
||||||
@@ -12,68 +13,140 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// Draggable instance of a decoration that can be placed on the statue.
|
/// Draggable instance of a decoration that can be placed on the statue.
|
||||||
/// Created dynamically when dragging from menu or picking up from statue.
|
/// Created dynamically when dragging from menu or picking up from statue.
|
||||||
/// Destroyed if dropped outside statue area.
|
/// Destroyed if dropped outside statue area.
|
||||||
|
/// Supports tapping and dragging when placed on statue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DecorationDraggableInstance : MonoBehaviour
|
public class DecorationDraggableInstance : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||||
{
|
{
|
||||||
[Header("References")]
|
[Header("References")]
|
||||||
[SerializeField] private Image decorationImage;
|
[SerializeField] private Image decorationImage;
|
||||||
[SerializeField] private CanvasGroup canvasGroup;
|
[SerializeField] private CanvasGroup canvasGroup;
|
||||||
|
|
||||||
private DecorationData _decorationData;
|
private DecorationData decorationData;
|
||||||
private RectTransform _rectTransform;
|
private RectTransform rectTransform;
|
||||||
private Canvas _canvas;
|
private Canvas canvas;
|
||||||
private RectTransform _statueOutline;
|
private Transform canvasParent; // Parent transform for dragging (usually canvas or draggable container)
|
||||||
private Transform _statueParent;
|
private RectTransform statueOutline;
|
||||||
private StatueDecorationController _controller;
|
private Transform statueParent;
|
||||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
private StatueDecorationController controller;
|
||||||
private System.Action _onFinishedCallback;
|
private AppleHills.Core.Settings.IStatueDressupSettings settings;
|
||||||
|
private System.Action onFinishedCallback;
|
||||||
|
private System.Action onShowOutlineCallback;
|
||||||
|
private System.Action onHideOutlineCallback;
|
||||||
|
|
||||||
private bool _isDragging;
|
private bool isDragging;
|
||||||
private bool _isPlacedOnStatue;
|
private bool isPlacedOnStatue;
|
||||||
private Vector3 _dragOffset;
|
private Vector3 dragOffset;
|
||||||
|
private bool dragStarted; // Track if drag actually started (vs just a click)
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
public DecorationData Data => _decorationData;
|
public DecorationData Data => decorationData;
|
||||||
public bool IsPlacedOnStatue => _isPlacedOnStatue;
|
public bool IsPlacedOnStatue => isPlacedOnStatue;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_rectTransform = GetComponent<RectTransform>();
|
rectTransform = GetComponent<RectTransform>();
|
||||||
_canvas = GetComponentInParent<Canvas>();
|
canvas = GetComponentInParent<Canvas>();
|
||||||
|
|
||||||
|
// Store initial parent for dragging context
|
||||||
|
if (transform.parent != null)
|
||||||
|
{
|
||||||
|
canvasParent = transform.parent;
|
||||||
|
}
|
||||||
|
|
||||||
if (canvasGroup == null)
|
if (canvasGroup == null)
|
||||||
{
|
{
|
||||||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the decoration image can receive raycasts
|
||||||
|
if (decorationImage != null)
|
||||||
|
{
|
||||||
|
decorationImage.raycastTarget = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize the draggable instance
|
/// Initialize with context object (preferred method)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(DecorationData data, RectTransform statueOutline, Transform statueParent,
|
public void InitializeWithContext(DecorationDragContext context)
|
||||||
StatueDecorationController controller, AppleHills.Core.Settings.IStatueDressupSettings settings,
|
|
||||||
System.Action onFinishedCallback)
|
|
||||||
{
|
{
|
||||||
_decorationData = data;
|
if (context == null)
|
||||||
_statueOutline = statueOutline;
|
{
|
||||||
_statueParent = statueParent;
|
Logging.Error("[DecorationDraggableInstance] Null context provided!");
|
||||||
_controller = controller;
|
return;
|
||||||
_settings = settings;
|
}
|
||||||
_onFinishedCallback = onFinishedCallback;
|
|
||||||
|
decorationData = context.Data;
|
||||||
|
statueOutline = context.StatueOutline;
|
||||||
|
statueParent = context.StatueParent;
|
||||||
|
controller = context.Controller;
|
||||||
|
settings = context.Settings;
|
||||||
|
onFinishedCallback = context.OnFinished;
|
||||||
|
onShowOutlineCallback = context.OnShowOutline;
|
||||||
|
onHideOutlineCallback = context.OnHideOutline;
|
||||||
|
|
||||||
|
// Handle placed vs new drag
|
||||||
|
if (context.IsPlaced)
|
||||||
|
{
|
||||||
|
isPlacedOnStatue = true;
|
||||||
|
isDragging = false;
|
||||||
|
statueParent = transform.parent; // Already parented to statue
|
||||||
|
if (context.CanvasParent != null)
|
||||||
|
{
|
||||||
|
canvasParent = context.CanvasParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set sprite
|
// Set sprite
|
||||||
if (decorationImage != null && data != null && data.DecorationSprite != null)
|
if (decorationImage != null && context.Data != null && context.Data.DecorationSprite != null)
|
||||||
{
|
{
|
||||||
decorationImage.sprite = data.DecorationSprite;
|
decorationImage.sprite = context.Data.DecorationSprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set authored size
|
// Set authored size
|
||||||
if (_rectTransform != null && data != null)
|
if (rectTransform != null && context.Data != null)
|
||||||
{
|
{
|
||||||
_rectTransform.sizeDelta = data.AuthoredSize;
|
rectTransform.sizeDelta = context.Data.AuthoredSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.Debug($"[DecorationDraggableInstance] Initialized: {data?.DecorationName}");
|
// Make interactive if placed (so it can be picked up)
|
||||||
|
if (context.IsPlaced && canvasGroup != null)
|
||||||
|
{
|
||||||
|
canvasGroup.blocksRaycasts = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationDraggableInstance] Initialized with context: {context.Data?.DecorationName}, isPlaced={context.IsPlaced}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the draggable instance (legacy method - prefer InitializeWithContext)
|
||||||
|
/// </summary>
|
||||||
|
[System.Obsolete("Use InitializeWithContext instead")]
|
||||||
|
public void Initialize(DecorationData data, RectTransform pStatueOutline, Transform pStatueParent,
|
||||||
|
StatueDecorationController pController, AppleHills.Core.Settings.IStatueDressupSettings pSettings,
|
||||||
|
System.Action pOnFinishedCallback, System.Action onShowOutline = null, System.Action onHideOutline = null)
|
||||||
|
{
|
||||||
|
var context = DecorationDragContext.CreateForNewDrag(
|
||||||
|
data, pStatueOutline, pStatueParent, pController, pSettings,
|
||||||
|
pOnFinishedCallback, onShowOutline, onHideOutline
|
||||||
|
);
|
||||||
|
InitializeWithContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize as already placed decoration (legacy method - prefer InitializeWithContext)
|
||||||
|
/// </summary>
|
||||||
|
[System.Obsolete("Use InitializeWithContext instead")]
|
||||||
|
public void InitializeAsPlaced(DecorationData data, StatueDecorationController pController,
|
||||||
|
AppleHills.Core.Settings.IStatueDressupSettings pSettings, RectTransform pStatueOutline = null,
|
||||||
|
Transform pCanvasParent = null, System.Action onShowOutline = null, System.Action onHideOutline = null,
|
||||||
|
System.Action onFinished = null)
|
||||||
|
{
|
||||||
|
var context = DecorationDragContext.CreateForPlaced(
|
||||||
|
data, pController, pSettings, pStatueOutline, pCanvasParent,
|
||||||
|
onShowOutline, onHideOutline, onFinished
|
||||||
|
);
|
||||||
|
InitializeWithContext(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -81,18 +154,22 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartDragFromIcon(PointerEventData eventData)
|
public void StartDragFromIcon(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
_isDragging = true;
|
isDragging = true;
|
||||||
|
|
||||||
|
// Broadcast started dragging event (from grid)
|
||||||
|
var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false);
|
||||||
|
DecorationEventsManager.BroadcastDecorationStartedDragging(eventDataObj);
|
||||||
|
|
||||||
// Calculate offset from cursor to object center
|
// Calculate offset from cursor to object center
|
||||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||||
_canvas.transform as RectTransform,
|
canvas.transform as RectTransform,
|
||||||
eventData.position,
|
eventData.position,
|
||||||
eventData.pressEventCamera,
|
eventData.pressEventCamera,
|
||||||
out Vector2 localPoint);
|
out Vector2 localPoint);
|
||||||
|
|
||||||
_dragOffset = _rectTransform.localPosition - (Vector3)localPoint;
|
dragOffset = rectTransform.localPosition - (Vector3)localPoint;
|
||||||
|
|
||||||
Logging.Debug($"[DecorationDraggableInstance] Started drag from icon: {_decorationData?.DecorationName}");
|
Logging.Debug($"[DecorationDraggableInstance] Started drag from icon: {decorationData?.DecorationName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,16 +177,16 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ContinueDrag(PointerEventData eventData)
|
public void ContinueDrag(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
if (!_isDragging) return;
|
if (!isDragging) return;
|
||||||
|
|
||||||
// Update position to follow cursor
|
// Update position to follow cursor
|
||||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||||
_canvas.transform as RectTransform,
|
canvas.transform as RectTransform,
|
||||||
eventData.position,
|
eventData.position,
|
||||||
eventData.pressEventCamera,
|
eventData.pressEventCamera,
|
||||||
out Vector2 localPoint);
|
out Vector2 localPoint);
|
||||||
|
|
||||||
_rectTransform.localPosition = localPoint + (Vector2)_dragOffset;
|
rectTransform.localPosition = localPoint + (Vector2)dragOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -117,9 +194,13 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void EndDrag(PointerEventData eventData)
|
public void EndDrag(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
_isDragging = false;
|
isDragging = false;
|
||||||
|
|
||||||
Logging.Debug($"[DecorationDraggableInstance] Drag ended: {_decorationData?.DecorationName}");
|
Logging.Debug($"[DecorationDraggableInstance] Drag ended: {decorationData?.DecorationName}");
|
||||||
|
|
||||||
|
// Broadcast finished dragging event
|
||||||
|
var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: isPlacedOnStatue);
|
||||||
|
DecorationEventsManager.BroadcastDecorationFinishedDragging(eventDataObj);
|
||||||
|
|
||||||
// Check if overlapping with statue
|
// Check if overlapping with statue
|
||||||
if (IsOverlappingStatue())
|
if (IsOverlappingStatue())
|
||||||
@@ -137,20 +218,20 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool IsOverlappingStatue()
|
private bool IsOverlappingStatue()
|
||||||
{
|
{
|
||||||
if (_statueOutline == null || _rectTransform == null)
|
if (statueOutline == null || rectTransform == null)
|
||||||
{
|
{
|
||||||
Logging.Warning($"[DecorationDraggableInstance] Cannot check overlap - statueOutline or RectTransform is null");
|
Logging.Warning($"[DecorationDraggableInstance] Cannot check overlap - statueOutline or RectTransform is null");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get bounds of this item in world space
|
// Get bounds of this item in world space
|
||||||
Rect itemRect = GetWorldRect(_rectTransform);
|
Rect itemRect = GetWorldRect(rectTransform);
|
||||||
Rect outlineRect = GetWorldRect(_statueOutline);
|
Rect outlineRect = GetWorldRect(statueOutline);
|
||||||
|
|
||||||
// Check for any overlap
|
// Check for any overlap
|
||||||
bool overlaps = itemRect.Overlaps(outlineRect);
|
bool overlaps = itemRect.Overlaps(outlineRect);
|
||||||
|
|
||||||
Logging.Debug($"[DecorationDraggableInstance] Overlap check: {_decorationData?.DecorationName}, overlaps={overlaps}");
|
Logging.Debug($"[DecorationDraggableInstance] Overlap check: {decorationData?.DecorationName}, overlaps={overlaps}");
|
||||||
|
|
||||||
return overlaps;
|
return overlaps;
|
||||||
}
|
}
|
||||||
@@ -158,10 +239,10 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get world space rect for a RectTransform
|
/// Get world space rect for a RectTransform
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Rect GetWorldRect(RectTransform rectTransform)
|
private Rect GetWorldRect(RectTransform pRectTransform)
|
||||||
{
|
{
|
||||||
Vector3[] corners = new Vector3[4];
|
Vector3[] corners = new Vector3[4];
|
||||||
rectTransform.GetWorldCorners(corners);
|
pRectTransform.GetWorldCorners(corners);
|
||||||
|
|
||||||
Vector3 bottomLeft = corners[0];
|
Vector3 bottomLeft = corners[0];
|
||||||
Vector3 topRight = corners[2];
|
Vector3 topRight = corners[2];
|
||||||
@@ -174,24 +255,28 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void PlaceOnStatue()
|
private void PlaceOnStatue()
|
||||||
{
|
{
|
||||||
Logging.Debug($"[DecorationDraggableInstance] Placing on statue: {_decorationData?.DecorationName}");
|
Logging.Debug($"[DecorationDraggableInstance] Placing on statue: {decorationData?.DecorationName}");
|
||||||
|
|
||||||
_isPlacedOnStatue = true;
|
isPlacedOnStatue = true;
|
||||||
|
|
||||||
|
// Broadcast dropped on statue event
|
||||||
|
var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false);
|
||||||
|
DecorationEventsManager.BroadcastDecorationDroppedOnStatue(eventDataObj);
|
||||||
|
|
||||||
// Move to statue parent if specified
|
// Move to statue parent if specified
|
||||||
if (_statueParent != null && transform.parent != _statueParent)
|
if (statueParent != null && transform.parent != statueParent)
|
||||||
{
|
{
|
||||||
transform.SetParent(_statueParent, true); // Keep world position
|
transform.SetParent(statueParent, true); // Keep world position
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register with controller
|
// Register with controller
|
||||||
if (_controller != null)
|
if (controller != null)
|
||||||
{
|
{
|
||||||
_controller.RegisterDecoration(this);
|
controller.RegisterDecoration(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify menu controller to hide outline
|
// Notify menu controller to hide outline
|
||||||
_onFinishedCallback?.Invoke();
|
onFinishedCallback?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -199,16 +284,24 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void PlayPopOutAndDestroy()
|
private void PlayPopOutAndDestroy()
|
||||||
{
|
{
|
||||||
Logging.Debug($"[DecorationDraggableInstance] Pop-out and destroy: {_decorationData?.DecorationName}");
|
Logging.Debug($"[DecorationDraggableInstance] Pop-out and destroy: {decorationData?.DecorationName}");
|
||||||
|
|
||||||
|
// Broadcast dropped out event (animation starting)
|
||||||
|
var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false);
|
||||||
|
DecorationEventsManager.BroadcastDecorationDroppedOut(eventDataObj);
|
||||||
|
|
||||||
// Notify menu controller to hide outline immediately
|
// Notify menu controller to hide outline immediately
|
||||||
_onFinishedCallback?.Invoke();
|
onFinishedCallback?.Invoke();
|
||||||
|
|
||||||
float duration = _settings?.PlacementAnimationDuration ?? 0.3f;
|
float duration = settings?.PlacementAnimationDuration ?? StatueDressupConstants.DefaultAnimationDuration;
|
||||||
|
|
||||||
// Play pop-out with fade animation
|
// Play pop-out with fade animation
|
||||||
TweenAnimationUtility.PopOutWithFade(transform, canvasGroup, duration, () =>
|
TweenAnimationUtility.PopOutWithFade(transform, canvasGroup, duration, () =>
|
||||||
{
|
{
|
||||||
|
// Broadcast finished dropping out event (animation complete)
|
||||||
|
var finalEventData = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false);
|
||||||
|
DecorationEventsManager.BroadcastDecorationFinishedDroppingOut(finalEventData);
|
||||||
|
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -216,27 +309,108 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allow picking up from statue for repositioning
|
/// Allow picking up from statue for repositioning
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartDragFromStatue(Vector3 pointerPosition)
|
public void StartDragFromStatue(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
if (_controller != null)
|
Logging.Debug($"[DecorationDraggableInstance] StartDragFromStatue called for: {decorationData?.DecorationName}");
|
||||||
|
Logging.Debug($"[DecorationDraggableInstance] Show outline callback is null: {onShowOutlineCallback == null}");
|
||||||
|
|
||||||
|
if (controller != null)
|
||||||
{
|
{
|
||||||
_controller.UnregisterDecoration(this);
|
controller.UnregisterDecoration(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isPlacedOnStatue = false;
|
isPlacedOnStatue = false;
|
||||||
_isDragging = true;
|
isDragging = true;
|
||||||
|
|
||||||
// Calculate offset
|
// Broadcast started dragging event (from statue)
|
||||||
|
var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: true);
|
||||||
|
DecorationEventsManager.BroadcastDecorationStartedDragging(eventDataObj);
|
||||||
|
|
||||||
|
// Show statue outline when picking up from statue
|
||||||
|
if (onShowOutlineCallback != null)
|
||||||
|
{
|
||||||
|
Logging.Debug("[DecorationDraggableInstance] Invoking show outline callback");
|
||||||
|
onShowOutlineCallback.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Warning("[DecorationDraggableInstance] Show outline callback is null - cannot show outline!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reparent to canvas for dragging (so coordinates work correctly)
|
||||||
|
if (canvasParent != null && transform.parent != canvasParent)
|
||||||
|
{
|
||||||
|
// Store world position before reparenting
|
||||||
|
Vector3 worldPos = transform.position;
|
||||||
|
transform.SetParent(canvasParent, false);
|
||||||
|
transform.position = worldPos; // Restore world position
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate offset using proper camera
|
||||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||||
_canvas.transform as RectTransform,
|
canvas.transform as RectTransform,
|
||||||
pointerPosition,
|
eventData.position,
|
||||||
null,
|
eventData.pressEventCamera,
|
||||||
out Vector2 localPoint);
|
out Vector2 localPoint);
|
||||||
|
|
||||||
_dragOffset = _rectTransform.localPosition - (Vector3)localPoint;
|
dragOffset = rectTransform.localPosition - (Vector3)localPoint;
|
||||||
|
|
||||||
Logging.Debug($"[DecorationDraggableInstance] Started drag from statue: {_decorationData?.DecorationName}");
|
Logging.Debug($"[DecorationDraggableInstance] Started drag from statue: {decorationData?.DecorationName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Pointer Event Handlers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle pointer click - only when placed on statue
|
||||||
|
/// </summary>
|
||||||
|
public void OnPointerClick(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
// Only handle clicks when placed on statue and not currently dragging
|
||||||
|
if (!isPlacedOnStatue || dragStarted) return;
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationDraggableInstance] Decoration tapped: {decorationData?.DecorationName}");
|
||||||
|
|
||||||
|
// Broadcast tap event
|
||||||
|
var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: true);
|
||||||
|
DecorationEventsManager.BroadcastDecorationTappedOnStatue(eventDataObj);
|
||||||
|
|
||||||
|
// Future: Open detail view, play sound effect, show info popup, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle drag start - only when placed on statue
|
||||||
|
/// </summary>
|
||||||
|
public void OnBeginDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
// Only handle drag from statue if already placed
|
||||||
|
if (!isPlacedOnStatue) return;
|
||||||
|
|
||||||
|
dragStarted = true;
|
||||||
|
StartDragFromStatue(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle drag continuation
|
||||||
|
/// </summary>
|
||||||
|
public void OnDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
ContinueDrag(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle drag end
|
||||||
|
/// </summary>
|
||||||
|
public void OnEndDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
dragStarted = false;
|
||||||
|
EndDrag(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Core;
|
using Core;
|
||||||
|
using Minigames.StatueDressup.Controllers;
|
||||||
using Minigames.StatueDressup.Data;
|
using Minigames.StatueDressup.Data;
|
||||||
|
using Minigames.StatueDressup.Events;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@@ -17,7 +19,7 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
[SerializeField] private Image iconImage;
|
[SerializeField] private Image iconImage;
|
||||||
[SerializeField] private DecorationData decorationData;
|
[SerializeField] private DecorationData decorationData;
|
||||||
|
|
||||||
private Controllers.DecorationMenuController _menuController;
|
private DecorationMenuController _menuController;
|
||||||
private DecorationDraggableInstance _activeDraggableInstance;
|
private DecorationDraggableInstance _activeDraggableInstance;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
@@ -26,7 +28,7 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize the icon with decoration data
|
/// Initialize the icon with decoration data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(DecorationData data, Controllers.DecorationMenuController controller)
|
public void Initialize(DecorationData data, DecorationMenuController controller)
|
||||||
{
|
{
|
||||||
decorationData = data;
|
decorationData = data;
|
||||||
_menuController = controller;
|
_menuController = controller;
|
||||||
@@ -46,6 +48,11 @@ namespace Minigames.StatueDressup.DragDrop
|
|||||||
if (_activeDraggableInstance == null)
|
if (_activeDraggableInstance == null)
|
||||||
{
|
{
|
||||||
Logging.Debug($"[DecorationGridIcon] Item tapped: {decorationData?.DecorationName}");
|
Logging.Debug($"[DecorationGridIcon] Item tapped: {decorationData?.DecorationName}");
|
||||||
|
|
||||||
|
// Broadcast tapped in grid event
|
||||||
|
var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false);
|
||||||
|
DecorationEventsManager.BroadcastDecorationTappedInGrid(eventDataObj);
|
||||||
|
|
||||||
// Future: Open detail view, preview, etc.
|
// Future: Open detail view, preview, etc.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
Assets/Scripts/Minigames/StatueDressup/Events.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/Events.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fd293a0b6d2e4b28bd74bc8aff5fea01
|
||||||
|
timeCreated: 1764248911
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
using Core;
|
||||||
|
using Core.Lifecycle;
|
||||||
|
using Minigames.StatueDressup.Data;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Events
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manager for decoration VFX/SFX events.
|
||||||
|
/// Listens to decoration state changes and triggers audio/visual feedback.
|
||||||
|
/// </summary>
|
||||||
|
public class DecorationEventsManager : ManagedBehaviour
|
||||||
|
{
|
||||||
|
public static DecorationEventsManager Instance { get; private set; }
|
||||||
|
|
||||||
|
// Static events for decoration state changes
|
||||||
|
public static event System.Action<DecorationEventData> OnDecorationTappedInGrid;
|
||||||
|
public static event System.Action<DecorationEventData> OnDecorationTappedOnStatue;
|
||||||
|
public static event System.Action<DecorationEventData> OnDecorationStartedDragging;
|
||||||
|
public static event System.Action<DecorationEventData> OnDecorationFinishedDragging;
|
||||||
|
public static event System.Action<DecorationEventData> OnDecorationDroppedOnStatue;
|
||||||
|
public static event System.Action<DecorationEventData> OnDecorationDroppedOut;
|
||||||
|
public static event System.Action<DecorationEventData> OnDecorationFinishedDroppingOut;
|
||||||
|
|
||||||
|
internal override void OnManagedAwake()
|
||||||
|
{
|
||||||
|
base.OnManagedAwake();
|
||||||
|
|
||||||
|
// Singleton pattern
|
||||||
|
if (Instance != null && Instance != this)
|
||||||
|
{
|
||||||
|
Logging.Warning("[DecorationEventsManager] Duplicate instance detected. Destroying duplicate.");
|
||||||
|
Destroy(gameObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
// Subscribe to all events
|
||||||
|
OnDecorationTappedInGrid += HandleDecorationTappedInGrid;
|
||||||
|
OnDecorationTappedOnStatue += HandleDecorationTappedOnStatue;
|
||||||
|
OnDecorationStartedDragging += HandleDecorationStartedDragging;
|
||||||
|
OnDecorationFinishedDragging += HandleDecorationFinishedDragging;
|
||||||
|
OnDecorationDroppedOnStatue += HandleDecorationDroppedOnStatue;
|
||||||
|
OnDecorationDroppedOut += HandleDecorationDroppedOut;
|
||||||
|
OnDecorationFinishedDroppingOut += HandleDecorationFinishedDroppingOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
// Unsubscribe from all events
|
||||||
|
if (Instance == this)
|
||||||
|
{
|
||||||
|
OnDecorationTappedInGrid -= HandleDecorationTappedInGrid;
|
||||||
|
OnDecorationTappedOnStatue -= HandleDecorationTappedOnStatue;
|
||||||
|
OnDecorationStartedDragging -= HandleDecorationStartedDragging;
|
||||||
|
OnDecorationFinishedDragging -= HandleDecorationFinishedDragging;
|
||||||
|
OnDecorationDroppedOnStatue -= HandleDecorationDroppedOnStatue;
|
||||||
|
OnDecorationDroppedOut -= HandleDecorationDroppedOut;
|
||||||
|
OnDecorationFinishedDroppingOut -= HandleDecorationFinishedDroppingOut;
|
||||||
|
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Static Broadcasting Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast that a decoration was tapped in the grid menu
|
||||||
|
/// </summary>
|
||||||
|
public static void BroadcastDecorationTappedInGrid(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
OnDecorationTappedInGrid?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast that a decoration already on the statue was tapped
|
||||||
|
/// </summary>
|
||||||
|
public static void BroadcastDecorationTappedOnStatue(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
OnDecorationTappedOnStatue?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast that a decoration started being dragged
|
||||||
|
/// </summary>
|
||||||
|
public static void BroadcastDecorationStartedDragging(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
OnDecorationStartedDragging?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast that a decoration finished being dragged (released)
|
||||||
|
/// </summary>
|
||||||
|
public static void BroadcastDecorationFinishedDragging(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
OnDecorationFinishedDragging?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast that a decoration was successfully dropped on the statue
|
||||||
|
/// </summary>
|
||||||
|
public static void BroadcastDecorationDroppedOnStatue(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
OnDecorationDroppedOnStatue?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast that a decoration was dropped outside the statue (animation starts)
|
||||||
|
/// </summary>
|
||||||
|
public static void BroadcastDecorationDroppedOut(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
OnDecorationDroppedOut?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast that a decoration finished its drop-out animation
|
||||||
|
/// </summary>
|
||||||
|
public static void BroadcastDecorationFinishedDroppingOut(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
OnDecorationFinishedDroppingOut?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers (Stubbed with Logs)
|
||||||
|
|
||||||
|
private void HandleDecorationTappedInGrid(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationEventsManager] Decoration tapped in grid: {eventData.DecorationData?.DecorationId}");
|
||||||
|
// TODO: Play tap SFX/VFX
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDecorationTappedOnStatue(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationEventsManager] Decoration tapped on statue: {eventData.DecorationData?.DecorationId}");
|
||||||
|
// TODO: Play tap SFX/VFX (different from grid tap?)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDecorationStartedDragging(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationEventsManager] Decoration started dragging: {eventData.DecorationData?.DecorationId} (FromStatue: {eventData.FromStatue})");
|
||||||
|
// TODO: Play drag start SFX, maybe show drag VFX
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDecorationFinishedDragging(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationEventsManager] Decoration finished dragging: {eventData.DecorationData?.DecorationId}");
|
||||||
|
// TODO: Play drag release SFX
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDecorationDroppedOnStatue(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationEventsManager] Decoration dropped on statue: {eventData.DecorationData?.DecorationId}");
|
||||||
|
// TODO: Play success SFX, maybe show placement VFX
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDecorationDroppedOut(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationEventsManager] Decoration dropped out (animation starting): {eventData.DecorationData?.DecorationId}");
|
||||||
|
// TODO: Play drop-out SFX (whoosh/disappear sound?)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDecorationFinishedDroppingOut(DecorationEventData eventData)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationEventsManager] Decoration finished dropping out: {eventData.DecorationData?.DecorationId}");
|
||||||
|
// TODO: Play finish SFX (poof sound?), maybe show VFX
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5c9796e0044a4fcd95b02a19925a6b2b
|
||||||
|
timeCreated: 1764248911
|
||||||
63
Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs
Normal file
63
Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for managers that load asynchronously and notify when ready.
|
||||||
|
/// Allows dependent components to safely access the manager via WhenReady callbacks.
|
||||||
|
/// </summary>
|
||||||
|
public interface IReadyNotifier
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True when the manager has finished initialization and is ready to use
|
||||||
|
/// </summary>
|
||||||
|
bool IsReady { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event invoked when the manager becomes ready
|
||||||
|
/// </summary>
|
||||||
|
event Action OnReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for IReadyNotifier to provide common callback behavior
|
||||||
|
/// </summary>
|
||||||
|
public static class ReadyNotifierExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Execute callback when ready. If already ready, executes immediately.
|
||||||
|
/// If not ready yet, subscribes to OnReady event and executes when fired.
|
||||||
|
/// </summary>
|
||||||
|
public static void WhenReady(this IReadyNotifier notifier, Action callback)
|
||||||
|
{
|
||||||
|
if (notifier == null)
|
||||||
|
{
|
||||||
|
Core.Logging.Warning("[ReadyNotifierExtensions] Notifier is null, cannot execute callback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifier.IsReady)
|
||||||
|
{
|
||||||
|
// Already ready - execute immediately
|
||||||
|
callback.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not ready yet - subscribe to event and auto-unsubscribe after invocation
|
||||||
|
Action handler = null;
|
||||||
|
handler = () =>
|
||||||
|
{
|
||||||
|
callback.Invoke();
|
||||||
|
notifier.OnReady -= handler; // Unsubscribe to prevent memory leaks
|
||||||
|
};
|
||||||
|
notifier.OnReady += handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: da5633d4a0f84ceeaca54bb2c542dca4
|
||||||
|
timeCreated: 1764244569
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/PhotoGallery.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/PhotoGallery.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2ad8b8c5b4f24f52af870512f5d18592
|
||||||
|
timeCreated: 1764241594
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
using Core;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages enlarged photo preview display.
|
||||||
|
/// Handles backdrop, preview spawning, and click-to-dismiss.
|
||||||
|
/// Similar to CardEnlargeController but simpler (no state machine).
|
||||||
|
/// </summary>
|
||||||
|
public class PhotoEnlargeController
|
||||||
|
{
|
||||||
|
private readonly GameObject backdrop;
|
||||||
|
private readonly Transform enlargedContainer;
|
||||||
|
private readonly float animationDuration;
|
||||||
|
|
||||||
|
private GameObject currentEnlargedPreview;
|
||||||
|
private PhotoGridItem currentSourceItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public PhotoEnlargeController(GameObject backdrop, Transform enlargedContainer, float animationDuration = 0.3f)
|
||||||
|
{
|
||||||
|
this.backdrop = backdrop;
|
||||||
|
this.enlargedContainer = enlargedContainer;
|
||||||
|
this.animationDuration = animationDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if a photo is currently enlarged
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPhotoEnlarged => currentEnlargedPreview != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enlarge a photo from a grid item
|
||||||
|
/// </summary>
|
||||||
|
public void EnlargePhoto(PhotoGridItem sourceItem, GameObject previewPrefab, Texture2D photoTexture, float enlargedScale)
|
||||||
|
{
|
||||||
|
if (sourceItem == null || previewPrefab == null || photoTexture == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[PhotoEnlargeController] Invalid parameters for EnlargePhoto");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow multiple enlargements
|
||||||
|
if (currentEnlargedPreview != null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[PhotoEnlargeController] Photo already enlarged");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSourceItem = sourceItem;
|
||||||
|
|
||||||
|
// Show backdrop
|
||||||
|
if (backdrop != null)
|
||||||
|
{
|
||||||
|
backdrop.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn preview clone
|
||||||
|
currentEnlargedPreview = Object.Instantiate(previewPrefab, enlargedContainer);
|
||||||
|
currentEnlargedPreview.transform.SetAsLastSibling();
|
||||||
|
|
||||||
|
// Position at source item's world position
|
||||||
|
currentEnlargedPreview.transform.position = sourceItem.transform.position;
|
||||||
|
currentEnlargedPreview.transform.localScale = sourceItem.transform.localScale;
|
||||||
|
|
||||||
|
// Set photo texture on preview
|
||||||
|
var previewImage = currentEnlargedPreview.GetComponent<UnityEngine.UI.Image>();
|
||||||
|
if (previewImage != null)
|
||||||
|
{
|
||||||
|
// Create sprite from texture
|
||||||
|
Sprite photoSprite = Sprite.Create(
|
||||||
|
photoTexture,
|
||||||
|
new Rect(0, 0, photoTexture.width, photoTexture.height),
|
||||||
|
new Vector2(0.5f, 0.5f)
|
||||||
|
);
|
||||||
|
previewImage.sprite = photoSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click handler to preview
|
||||||
|
var clickHandler = currentEnlargedPreview.GetComponent<EventTrigger>();
|
||||||
|
if (clickHandler == null)
|
||||||
|
{
|
||||||
|
clickHandler = currentEnlargedPreview.AddComponent<EventTrigger>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointerClickEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerClick };
|
||||||
|
pointerClickEntry.callback.AddListener((_) => { ShrinkPhoto(); });
|
||||||
|
clickHandler.triggers.Add(pointerClickEntry);
|
||||||
|
|
||||||
|
// Animate to center and scale up
|
||||||
|
TweenAnimationUtility.AnimateLocalPosition(currentEnlargedPreview.transform, Vector3.zero, animationDuration);
|
||||||
|
TweenAnimationUtility.AnimateScale(currentEnlargedPreview.transform, Vector3.one * enlargedScale, animationDuration);
|
||||||
|
|
||||||
|
// Play audio feedback
|
||||||
|
AudioManager.Instance.LoadAndPlayUIAudio("card_albumdrop_deep", false);
|
||||||
|
|
||||||
|
Logging.Debug("[PhotoEnlargeController] Photo enlarged");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shrink the currently enlarged photo back to grid
|
||||||
|
/// </summary>
|
||||||
|
public void ShrinkPhoto()
|
||||||
|
{
|
||||||
|
if (currentEnlargedPreview == null || currentSourceItem == null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[PhotoEnlargeController] No photo to shrink");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide backdrop
|
||||||
|
if (backdrop != null)
|
||||||
|
{
|
||||||
|
backdrop.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get target position from source item
|
||||||
|
Vector3 targetWorldPos = currentSourceItem.transform.position;
|
||||||
|
Vector3 targetScale = currentSourceItem.transform.localScale;
|
||||||
|
|
||||||
|
GameObject previewToDestroy = currentEnlargedPreview;
|
||||||
|
|
||||||
|
// Animate back to source position
|
||||||
|
Pixelplacement.Tween.Position(previewToDestroy.transform, targetWorldPos, animationDuration, 0f, Pixelplacement.Tween.EaseInOut);
|
||||||
|
TweenAnimationUtility.AnimateScale(previewToDestroy.transform, targetScale, animationDuration, () =>
|
||||||
|
{
|
||||||
|
// Destroy preview after animation
|
||||||
|
Object.Destroy(previewToDestroy);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear references
|
||||||
|
currentEnlargedPreview = null;
|
||||||
|
currentSourceItem = null;
|
||||||
|
|
||||||
|
Logging.Debug("[PhotoEnlargeController] Photo shrunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleanup - call when gallery is closed
|
||||||
|
/// </summary>
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
if (currentEnlargedPreview != null)
|
||||||
|
{
|
||||||
|
Object.Destroy(currentEnlargedPreview);
|
||||||
|
currentEnlargedPreview = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backdrop != null)
|
||||||
|
{
|
||||||
|
backdrop.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSourceItem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8ba20462f9a647ee90389c7480147d2e
|
||||||
|
timeCreated: 1764151481
|
||||||
@@ -15,16 +15,16 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
[SerializeField] private Image thumbnailImage;
|
[SerializeField] private Image thumbnailImage;
|
||||||
[SerializeField] private GameObject loadingIndicator;
|
[SerializeField] private GameObject loadingIndicator;
|
||||||
|
|
||||||
private string _photoId;
|
private string photoId;
|
||||||
private StatuePhotoGalleryController _galleryController;
|
private StatuePhotoGalleryController galleryController;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize grid item with photo ID
|
/// Initialize grid item with photo ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(string photoId, StatuePhotoGalleryController controller)
|
public void Initialize(string newPhotoId, StatuePhotoGalleryController controller)
|
||||||
{
|
{
|
||||||
_photoId = photoId;
|
this.photoId = newPhotoId;
|
||||||
_galleryController = controller;
|
galleryController = controller;
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
if (loadingIndicator != null)
|
if (loadingIndicator != null)
|
||||||
@@ -41,7 +41,7 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
{
|
{
|
||||||
if (thumbnail == null)
|
if (thumbnail == null)
|
||||||
{
|
{
|
||||||
Logging.Warning($"[PhotoGridItem] Null thumbnail for photo: {_photoId}");
|
Logging.Warning($"[PhotoGridItem] Null thumbnail for photo: {photoId}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,14 +64,14 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handle click to show enlarged view
|
/// Handle click to enlarge/shrink photo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnPointerClick(PointerEventData eventData)
|
public void OnPointerClick(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
if (_galleryController != null && !string.IsNullOrEmpty(_photoId))
|
if (galleryController != null && !string.IsNullOrEmpty(photoId))
|
||||||
{
|
{
|
||||||
Logging.Debug($"[PhotoGridItem] Clicked: {_photoId}");
|
Logging.Debug($"[PhotoGridItem] Clicked: {photoId}");
|
||||||
_galleryController.ShowEnlargedView(_photoId);
|
galleryController.OnGridItemClicked(this, photoId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Minigames.StatueDressup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constants for StatueDressup minigame.
|
||||||
|
/// These serve as fallbacks when settings fail to load.
|
||||||
|
/// Prefer using settings configuration over these constants.
|
||||||
|
/// </summary>
|
||||||
|
public static class StatueDressupConstants
|
||||||
|
{
|
||||||
|
// Pagination
|
||||||
|
public const int DefaultMenuItemsPerPage = 20;
|
||||||
|
public const int DefaultGalleryItemsPerPage = 20;
|
||||||
|
|
||||||
|
// Animations
|
||||||
|
public const float DefaultAnimationDuration = 0.3f;
|
||||||
|
|
||||||
|
// Gallery/Photos - Performance Critical
|
||||||
|
// Note: These affect memory usage and should ideally come from settings
|
||||||
|
public const int DefaultThumbnailSize = 256;
|
||||||
|
public const int DefaultMaxCachedThumbnails = 50;
|
||||||
|
public const float DefaultEnlargedScale = 2.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 86c9225c5cc54b8b99b1964c393de5a3
|
||||||
|
timeCreated: 1764242098
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/Test.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/Test.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dc39e89d2088421187d48d89e0eed813
|
||||||
|
timeCreated: 1764241512
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Test
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Minimal test for photo capture - just capture and save to disk
|
||||||
|
/// </summary>
|
||||||
|
public class PhotoCaptureTestController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Required")]
|
||||||
|
[SerializeField] private RectTransform captureArea;
|
||||||
|
[SerializeField] private Button captureButton;
|
||||||
|
|
||||||
|
[Header("Optional - UI to hide during capture")]
|
||||||
|
[SerializeField] private GameObject[] hideTheseObjects;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (captureButton != null)
|
||||||
|
{
|
||||||
|
captureButton.onClick.AddListener(OnCaptureClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[PhotoCaptureTest] Ready. Photo save path: {PhotoManager.GetCaptureDirectory(CaptureType.StatueMinigame)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCaptureClicked()
|
||||||
|
{
|
||||||
|
if (captureArea == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[PhotoCaptureTest] Capture Area not assigned!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("[PhotoCaptureTest] Starting capture...");
|
||||||
|
StartCoroutine(CaptureCoroutine());
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Collections.IEnumerator CaptureCoroutine()
|
||||||
|
{
|
||||||
|
// Use new PhotoManager plug-and-play coroutine
|
||||||
|
yield return PhotoManager.CaptureAndSaveCoroutine(
|
||||||
|
CaptureType.StatueMinigame,
|
||||||
|
captureArea,
|
||||||
|
hideTheseObjects,
|
||||||
|
onSuccess: (photoId) =>
|
||||||
|
{
|
||||||
|
string path = $"{PhotoManager.GetCaptureDirectory(CaptureType.StatueMinigame)}/{photoId}.png";
|
||||||
|
Debug.Log($"[PhotoCaptureTest] ✅ SUCCESS! Photo saved: {path}");
|
||||||
|
|
||||||
|
// Load back to verify
|
||||||
|
Texture2D loadedPhoto = PhotoManager.LoadPhoto(CaptureType.StatueMinigame, photoId);
|
||||||
|
if (loadedPhoto != null)
|
||||||
|
{
|
||||||
|
Debug.Log($"[PhotoCaptureTest] Photo size: {loadedPhoto.width}x{loadedPhoto.height}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure: (error) =>
|
||||||
|
{
|
||||||
|
Debug.LogError($"[PhotoCaptureTest] ❌ Failed: {error}");
|
||||||
|
},
|
||||||
|
metadata: 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (captureButton != null)
|
||||||
|
captureButton.onClick.RemoveListener(OnCaptureClicked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/UI.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/UI.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 49800fa9079d485cbc65922d238d214a
|
||||||
|
timeCreated: 1764151454
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using UI.Core;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UIPage wrapper for the photo gallery.
|
||||||
|
/// Simple stock page with no transition animations.
|
||||||
|
/// </summary>
|
||||||
|
public class PhotoGalleryPage : UIPage
|
||||||
|
{
|
||||||
|
protected override void DoTransitionIn(System.Action onComplete)
|
||||||
|
{
|
||||||
|
// Instant transition - just show
|
||||||
|
gameObject.SetActive(true);
|
||||||
|
onComplete?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoTransitionOut(System.Action onComplete)
|
||||||
|
{
|
||||||
|
// Instant transition - just hide
|
||||||
|
gameObject.SetActive(false);
|
||||||
|
onComplete?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnBackPressed uses default behavior (pops the page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3136a7edf8de421e9860c6bd4e1dca10
|
||||||
|
timeCreated: 1764151460
|
||||||
32
Assets/Scripts/Minigames/StatueDressup/UI/PlayAreaPage.cs
Normal file
32
Assets/Scripts/Minigames/StatueDressup/UI/PlayAreaPage.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using UI.Core;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UIPage wrapper for the statue decoration play area.
|
||||||
|
/// Simple stock page with no transition animations.
|
||||||
|
/// </summary>
|
||||||
|
public class PlayAreaPage : UIPage
|
||||||
|
{
|
||||||
|
protected override void DoTransitionIn(System.Action onComplete)
|
||||||
|
{
|
||||||
|
// Instant transition - just show
|
||||||
|
gameObject.SetActive(true);
|
||||||
|
onComplete?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoTransitionOut(System.Action onComplete)
|
||||||
|
{
|
||||||
|
// Instant transition - just hide
|
||||||
|
gameObject.SetActive(false);
|
||||||
|
onComplete?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBackPressed()
|
||||||
|
{
|
||||||
|
// Play area is the root page - don't allow back navigation
|
||||||
|
// Override if you want custom behavior (e.g., quit minigame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: edd2867b8a4f42d8af202f215c3ecc47
|
||||||
|
timeCreated: 1764151454
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: fe03648f638e4872abafaf49234a3f55
|
|
||||||
timeCreated: 1763745490
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Core;
|
|
||||||
using UnityEngine;
|
|
||||||
using SDev;
|
|
||||||
|
|
||||||
namespace Minigames.StatueDressup.Utils
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Manages statue photo capture, storage, and retrieval.
|
|
||||||
/// Supports area-limited screenshots, persistent file storage (mobile + PC),
|
|
||||||
/// and optimized gallery loading with pagination.
|
|
||||||
/// </summary>
|
|
||||||
public static class StatuePhotoManager
|
|
||||||
{
|
|
||||||
private const string PHOTO_FOLDER = "StatuePhotos";
|
|
||||||
private const string PHOTO_PREFIX = "MrCementStatue_";
|
|
||||||
private const string METADATA_KEY_PREFIX = "StatuePhoto_Meta_";
|
|
||||||
private const string PHOTO_INDEX_KEY = "StatuePhoto_Index";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Photo metadata stored in PlayerPrefs
|
|
||||||
/// </summary>
|
|
||||||
[System.Serializable]
|
|
||||||
public class PhotoMetadata
|
|
||||||
{
|
|
||||||
public string photoId;
|
|
||||||
public string timestamp;
|
|
||||||
public int decorationCount;
|
|
||||||
public long fileSizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Capture
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Capture a specific area of the screen using Screenshot Helper
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="captureArea">RectTransform defining the capture region</param>
|
|
||||||
/// <param name="onComplete">Callback with captured Texture2D</param>
|
|
||||||
/// <param name="mainCamera">Camera used for coordinate conversion (null = Camera.main)</param>
|
|
||||||
public static void CaptureAreaPhoto(RectTransform captureArea, Action<Texture2D> onComplete, Camera mainCamera = null)
|
|
||||||
{
|
|
||||||
if (captureArea == null)
|
|
||||||
{
|
|
||||||
Logging.Error("[StatuePhotoManager] CaptureArea RectTransform is null!");
|
|
||||||
onComplete?.Invoke(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainCamera == null) mainCamera = Camera.main;
|
|
||||||
|
|
||||||
// Get screen rect from RectTransform
|
|
||||||
Rect screenRect = GetScreenRectFromRectTransform(captureArea, mainCamera);
|
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}");
|
|
||||||
|
|
||||||
// Use Screenshot Helper's Capture method
|
|
||||||
ScreenshotHelper.Instance.Capture(
|
|
||||||
screenRect.position,
|
|
||||||
screenRect.size,
|
|
||||||
(Texture2D texture) =>
|
|
||||||
{
|
|
||||||
if (texture != null)
|
|
||||||
{
|
|
||||||
Logging.Debug($"[StatuePhotoManager] Photo captured: {texture.width}x{texture.height}");
|
|
||||||
onComplete?.Invoke(texture);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logging.Error("[StatuePhotoManager] Screenshot Helper returned null texture!");
|
|
||||||
onComplete?.Invoke(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert RectTransform world corners to screen space rect
|
|
||||||
/// </summary>
|
|
||||||
private static Rect GetScreenRectFromRectTransform(RectTransform rectTransform, Camera camera)
|
|
||||||
{
|
|
||||||
Vector3[] corners = new Vector3[4];
|
|
||||||
rectTransform.GetWorldCorners(corners);
|
|
||||||
|
|
||||||
Vector2 min = RectTransformUtility.WorldToScreenPoint(camera, corners[0]);
|
|
||||||
Vector2 max = RectTransformUtility.WorldToScreenPoint(camera, corners[2]);
|
|
||||||
|
|
||||||
// Ensure positive dimensions
|
|
||||||
float width = Mathf.Abs(max.x - min.x);
|
|
||||||
float height = Mathf.Abs(max.y - min.y);
|
|
||||||
|
|
||||||
return new Rect(min.x, min.y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Save/Load
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Save photo to persistent storage with metadata
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="photo">Texture2D to save</param>
|
|
||||||
/// <param name="decorationCount">Number of decorations placed (for metadata)</param>
|
|
||||||
/// <returns>Photo ID if successful, null if failed</returns>
|
|
||||||
public static string SavePhoto(Texture2D photo, int decorationCount = 0)
|
|
||||||
{
|
|
||||||
if (photo == null)
|
|
||||||
{
|
|
||||||
Logging.Error("[StatuePhotoManager] Cannot save null photo");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Generate unique photo ID
|
|
||||||
string photoId = $"{PHOTO_PREFIX}{DateTime.Now.Ticks}";
|
|
||||||
|
|
||||||
// Save texture using FileSaveUtil
|
|
||||||
string savedPath = FileSaveUtil.Instance.SaveTextureAsPNG(
|
|
||||||
photo,
|
|
||||||
FileSaveUtil.AppPath.PersistentDataPath,
|
|
||||||
PHOTO_FOLDER,
|
|
||||||
photoId
|
|
||||||
);
|
|
||||||
|
|
||||||
// Calculate file size
|
|
||||||
FileInfo fileInfo = new FileInfo(savedPath);
|
|
||||||
long fileSize = fileInfo.Exists ? fileInfo.Length : 0;
|
|
||||||
|
|
||||||
// Save metadata
|
|
||||||
PhotoMetadata metadata = new PhotoMetadata
|
|
||||||
{
|
|
||||||
photoId = photoId,
|
|
||||||
timestamp = DateTime.Now.ToString("o"),
|
|
||||||
decorationCount = decorationCount,
|
|
||||||
fileSizeBytes = fileSize
|
|
||||||
};
|
|
||||||
|
|
||||||
SaveMetadata(metadata);
|
|
||||||
AddToPhotoIndex(photoId);
|
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoManager] Photo saved: {savedPath} ({fileSize} bytes)");
|
|
||||||
return photoId;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logging.Error($"[StatuePhotoManager] Failed to save photo: {e.Message}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load photo texture from storage
|
|
||||||
/// </summary>
|
|
||||||
public static Texture2D LoadPhoto(string photoId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(photoId))
|
|
||||||
{
|
|
||||||
Logging.Warning("[StatuePhotoManager] PhotoId is null or empty");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string filePath = GetPhotoFilePath(photoId);
|
|
||||||
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
{
|
|
||||||
Logging.Warning($"[StatuePhotoManager] Photo not found: {filePath}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] fileData = File.ReadAllBytes(filePath);
|
|
||||||
Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
|
||||||
|
|
||||||
if (texture.LoadImage(fileData))
|
|
||||||
{
|
|
||||||
Logging.Debug($"[StatuePhotoManager] Photo loaded: {photoId} ({texture.width}x{texture.height})");
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logging.Error($"[StatuePhotoManager] Failed to decode image: {photoId}");
|
|
||||||
UnityEngine.Object.Destroy(texture);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logging.Error($"[StatuePhotoManager] Failed to load photo {photoId}: {e.Message}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delete photo and its metadata
|
|
||||||
/// </summary>
|
|
||||||
public static bool DeletePhoto(string photoId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(photoId)) return false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string filePath = GetPhotoFilePath(photoId);
|
|
||||||
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
File.Delete(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteMetadata(photoId);
|
|
||||||
RemoveFromPhotoIndex(photoId);
|
|
||||||
|
|
||||||
Logging.Debug($"[StatuePhotoManager] Photo deleted: {photoId}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logging.Error($"[StatuePhotoManager] Failed to delete photo {photoId}: {e.Message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Gallery Support
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all photo IDs sorted by timestamp (newest first)
|
|
||||||
/// </summary>
|
|
||||||
public static List<string> GetAllPhotoIds()
|
|
||||||
{
|
|
||||||
string indexJson = PlayerPrefs.GetString(PHOTO_INDEX_KEY, "[]");
|
|
||||||
List<string> photoIds = JsonUtility.FromJson<PhotoIdList>(WrapJsonArray(indexJson))?.ids ?? new List<string>();
|
|
||||||
|
|
||||||
// Sort by timestamp descending (newest first)
|
|
||||||
photoIds.Sort((a, b) =>
|
|
||||||
{
|
|
||||||
PhotoMetadata metaA = LoadMetadata(a);
|
|
||||||
PhotoMetadata metaB = LoadMetadata(b);
|
|
||||||
|
|
||||||
DateTime dateA = DateTime.Parse(metaA?.timestamp ?? DateTime.MinValue.ToString("o"));
|
|
||||||
DateTime dateB = DateTime.Parse(metaB?.timestamp ?? DateTime.MinValue.ToString("o"));
|
|
||||||
|
|
||||||
return dateB.CompareTo(dateA);
|
|
||||||
});
|
|
||||||
|
|
||||||
return photoIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get paginated photo IDs for optimized gallery loading
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="page">Page number (0-indexed)</param>
|
|
||||||
/// <param name="pageSize">Number of items per page</param>
|
|
||||||
public static List<string> GetPhotoIdsPage(int page, int pageSize)
|
|
||||||
{
|
|
||||||
List<string> allIds = GetAllPhotoIds();
|
|
||||||
int startIndex = page * pageSize;
|
|
||||||
|
|
||||||
if (startIndex >= allIds.Count) return new List<string>();
|
|
||||||
|
|
||||||
int count = Mathf.Min(pageSize, allIds.Count - startIndex);
|
|
||||||
return allIds.GetRange(startIndex, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get total number of saved photos
|
|
||||||
/// </summary>
|
|
||||||
public static int GetPhotoCount()
|
|
||||||
{
|
|
||||||
return GetAllPhotoIds().Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get latest photo ID (most recent)
|
|
||||||
/// </summary>
|
|
||||||
public static string GetLatestPhotoId()
|
|
||||||
{
|
|
||||||
List<string> allIds = GetAllPhotoIds();
|
|
||||||
return allIds.Count > 0 ? allIds[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load photo metadata
|
|
||||||
/// </summary>
|
|
||||||
public static PhotoMetadata GetPhotoMetadata(string photoId)
|
|
||||||
{
|
|
||||||
return LoadMetadata(photoId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create thumbnail from full-size photo (for gallery preview)
|
|
||||||
/// </summary>
|
|
||||||
public static Texture2D CreateThumbnail(Texture2D fullSizePhoto, int maxSize = 256)
|
|
||||||
{
|
|
||||||
if (fullSizePhoto == null) return null;
|
|
||||||
|
|
||||||
int width = fullSizePhoto.width;
|
|
||||||
int height = fullSizePhoto.height;
|
|
||||||
|
|
||||||
// Calculate thumbnail size maintaining aspect ratio
|
|
||||||
float scale = Mathf.Min((float)maxSize / width, (float)maxSize / height);
|
|
||||||
int thumbWidth = Mathf.RoundToInt(width * scale);
|
|
||||||
int thumbHeight = Mathf.RoundToInt(height * scale);
|
|
||||||
|
|
||||||
// Create thumbnail using bilinear filtering
|
|
||||||
RenderTexture rt = RenderTexture.GetTemporary(thumbWidth, thumbHeight, 0, RenderTextureFormat.ARGB32);
|
|
||||||
RenderTexture.active = rt;
|
|
||||||
|
|
||||||
Graphics.Blit(fullSizePhoto, rt);
|
|
||||||
|
|
||||||
Texture2D thumbnail = new Texture2D(thumbWidth, thumbHeight, TextureFormat.RGB24, false);
|
|
||||||
thumbnail.ReadPixels(new Rect(0, 0, thumbWidth, thumbHeight), 0, 0);
|
|
||||||
thumbnail.Apply();
|
|
||||||
|
|
||||||
RenderTexture.active = null;
|
|
||||||
RenderTexture.ReleaseTemporary(rt);
|
|
||||||
|
|
||||||
return thumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Internal Helpers
|
|
||||||
|
|
||||||
public static string GetPhotoDirectory()
|
|
||||||
{
|
|
||||||
return Path.Combine(Application.persistentDataPath, PHOTO_FOLDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetPhotoFilePath(string photoId)
|
|
||||||
{
|
|
||||||
return Path.Combine(GetPhotoDirectory(), $"{photoId}.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SaveMetadata(PhotoMetadata metadata)
|
|
||||||
{
|
|
||||||
string json = JsonUtility.ToJson(metadata);
|
|
||||||
PlayerPrefs.SetString(METADATA_KEY_PREFIX + metadata.photoId, json);
|
|
||||||
PlayerPrefs.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PhotoMetadata LoadMetadata(string photoId)
|
|
||||||
{
|
|
||||||
string json = PlayerPrefs.GetString(METADATA_KEY_PREFIX + photoId, null);
|
|
||||||
return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson<PhotoMetadata>(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DeleteMetadata(string photoId)
|
|
||||||
{
|
|
||||||
PlayerPrefs.DeleteKey(METADATA_KEY_PREFIX + photoId);
|
|
||||||
PlayerPrefs.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddToPhotoIndex(string photoId)
|
|
||||||
{
|
|
||||||
List<string> photoIds = GetAllPhotoIds();
|
|
||||||
if (!photoIds.Contains(photoId))
|
|
||||||
{
|
|
||||||
photoIds.Add(photoId);
|
|
||||||
SavePhotoIndex(photoIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RemoveFromPhotoIndex(string photoId)
|
|
||||||
{
|
|
||||||
List<string> photoIds = GetAllPhotoIds();
|
|
||||||
if (photoIds.Remove(photoId))
|
|
||||||
{
|
|
||||||
SavePhotoIndex(photoIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SavePhotoIndex(List<string> photoIds)
|
|
||||||
{
|
|
||||||
string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds });
|
|
||||||
PlayerPrefs.SetString(PHOTO_INDEX_KEY, json);
|
|
||||||
PlayerPrefs.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string WrapJsonArray(string json)
|
|
||||||
{
|
|
||||||
if (json.StartsWith("[")) return "{\"ids\":" + json + "}";
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
[System.Serializable]
|
|
||||||
private class PhotoIdList
|
|
||||||
{
|
|
||||||
public List<string> ids = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 7f3e9a2b4c5d6e7f8a9b0c1d2e3f4a5b
|
|
||||||
@@ -103,6 +103,7 @@ namespace UI
|
|||||||
public GameObject eagleEye;
|
public GameObject eagleEye;
|
||||||
public GameObject ramaSjangButton;
|
public GameObject ramaSjangButton;
|
||||||
public GameObject scrabBookButton;
|
public GameObject scrabBookButton;
|
||||||
|
public GameObject pauseButton;
|
||||||
|
|
||||||
[HideInInspector] public Image cinematicSprites;
|
[HideInInspector] public Image cinematicSprites;
|
||||||
[HideInInspector] public Image cinematicBackgroundSprites;
|
[HideInInspector] public Image cinematicBackgroundSprites;
|
||||||
@@ -235,9 +236,12 @@ namespace UI
|
|||||||
case "Quarry":
|
case "Quarry":
|
||||||
currentUIMode = UIMode.Puzzle;
|
currentUIMode = UIMode.Puzzle;
|
||||||
break;
|
break;
|
||||||
case "DivingForPictures" or "CardQualityControl" or "BirdPoop" or "StatueDecoration":
|
case "DivingForPictures" or "CardQualityControl" or "BirdPoop":
|
||||||
currentUIMode = UIMode.Minigame;
|
currentUIMode = UIMode.Minigame;
|
||||||
break;
|
break;
|
||||||
|
case "StatueDecoration":
|
||||||
|
currentUIMode = UIMode.HideAll;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowAllHud();
|
ShowAllHud();
|
||||||
@@ -336,6 +340,10 @@ namespace UI
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UIMode.HideAll:
|
case UIMode.HideAll:
|
||||||
|
eagleEye.SetActive(false);
|
||||||
|
ramaSjangButton.SetActive(false);
|
||||||
|
scrabBookButton.SetActive(false);
|
||||||
|
pauseButton.SetActive(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
104
Assets/Scripts/Utils/AddressablesUtility.cs
Normal file
104
Assets/Scripts/Utils/AddressablesUtility.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Core;
|
||||||
|
using UnityEngine.AddressableAssets;
|
||||||
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||||
|
|
||||||
|
namespace Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class for common Addressables operations.
|
||||||
|
/// Provides generic methods for loading assets by label and creating lookup dictionaries.
|
||||||
|
/// </summary>
|
||||||
|
public static class AddressablesUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Load all assets with a specific label and create a dictionary indexed by a key selector.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAsset">Type of asset to load</typeparam>
|
||||||
|
/// <typeparam name="TKey">Type of key for dictionary</typeparam>
|
||||||
|
/// <param name="label">Addressables label to filter by</param>
|
||||||
|
/// <param name="keySelector">Function to extract key from asset (e.g., asset => asset.Id)</param>
|
||||||
|
/// <param name="onProgress">Optional callback for progress updates (0-1)</param>
|
||||||
|
/// <returns>Dictionary of assets indexed by key, and operation handle for cleanup</returns>
|
||||||
|
public static async Task<(Dictionary<TKey, TAsset> dictionary, AsyncOperationHandle<IList<TAsset>> handle)>
|
||||||
|
LoadAssetsByLabelAsync<TAsset, TKey>(
|
||||||
|
string label,
|
||||||
|
Func<TAsset, TKey> keySelector,
|
||||||
|
Action<float> onProgress = null)
|
||||||
|
{
|
||||||
|
Dictionary<TKey, TAsset> dictionary = new Dictionary<TKey, TAsset>();
|
||||||
|
|
||||||
|
// Load all assets with the specified label
|
||||||
|
AsyncOperationHandle<IList<TAsset>> handle = Addressables.LoadAssetsAsync<TAsset>(
|
||||||
|
label,
|
||||||
|
asset =>
|
||||||
|
{
|
||||||
|
// This callback is invoked for each asset as it loads
|
||||||
|
if (asset != null)
|
||||||
|
{
|
||||||
|
TKey key = keySelector(asset);
|
||||||
|
if (key != null && !dictionary.ContainsKey(key))
|
||||||
|
{
|
||||||
|
dictionary[key] = asset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Report progress if callback provided
|
||||||
|
while (!handle.IsDone)
|
||||||
|
{
|
||||||
|
onProgress?.Invoke(handle.PercentComplete);
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final progress update
|
||||||
|
onProgress?.Invoke(1f);
|
||||||
|
|
||||||
|
// Check if load was successful
|
||||||
|
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||||||
|
{
|
||||||
|
Logging.Error($"[AddressablesUtility] Failed to load assets with label '{label}': {handle.OperationException?.Message}");
|
||||||
|
return (dictionary, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[AddressablesUtility] Loaded {dictionary.Count} assets with label '{label}'");
|
||||||
|
|
||||||
|
return (dictionary, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load a single asset by address
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAsset">Type of asset to load</typeparam>
|
||||||
|
/// <param name="address">Addressable address/key</param>
|
||||||
|
/// <returns>Loaded asset and operation handle for cleanup</returns>
|
||||||
|
public static async Task<(TAsset asset, AsyncOperationHandle<TAsset> handle)> LoadAssetAsync<TAsset>(string address)
|
||||||
|
{
|
||||||
|
AsyncOperationHandle<TAsset> handle = Addressables.LoadAssetAsync<TAsset>(address);
|
||||||
|
|
||||||
|
await handle.Task;
|
||||||
|
|
||||||
|
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[AddressablesUtility] Failed to load asset '{address}': {handle.OperationException?.Message}");
|
||||||
|
return (default(TAsset), handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (handle.Result, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Safely release an Addressables handle
|
||||||
|
/// </summary>
|
||||||
|
public static void ReleaseHandle<T>(AsyncOperationHandle<T> handle)
|
||||||
|
{
|
||||||
|
if (handle.IsValid())
|
||||||
|
{
|
||||||
|
Addressables.Release(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
Assets/Scripts/Utils/AddressablesUtility.cs.meta
Normal file
3
Assets/Scripts/Utils/AddressablesUtility.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 81470e29c2d54df3967e373b71d18a0d
|
||||||
|
timeCreated: 1764164457
|
||||||
71
Assets/Scripts/Utils/PhotoCaptureConfig.cs
Normal file
71
Assets/Scripts/Utils/PhotoCaptureConfig.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Capture types for different photo contexts
|
||||||
|
/// </summary>
|
||||||
|
public enum CaptureType
|
||||||
|
{
|
||||||
|
StatueMinigame,
|
||||||
|
DivingMinigame
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for a specific capture type
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class CaptureConfig
|
||||||
|
{
|
||||||
|
public string subFolder;
|
||||||
|
public string photoPrefix;
|
||||||
|
public string metadataPrefix;
|
||||||
|
public string indexKey;
|
||||||
|
|
||||||
|
public CaptureConfig(string subFolder, string photoPrefix, string metadataPrefix, string indexKey)
|
||||||
|
{
|
||||||
|
this.subFolder = subFolder;
|
||||||
|
this.photoPrefix = photoPrefix;
|
||||||
|
this.metadataPrefix = metadataPrefix;
|
||||||
|
this.indexKey = indexKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static configuration registry for all capture types
|
||||||
|
/// </summary>
|
||||||
|
public static class PhotoCaptureConfigs
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<CaptureType, CaptureConfig> Configs = new Dictionary<CaptureType, CaptureConfig>
|
||||||
|
{
|
||||||
|
[CaptureType.StatueMinigame] = new CaptureConfig(
|
||||||
|
subFolder: "StatueMinigame",
|
||||||
|
photoPrefix: "Statue_",
|
||||||
|
metadataPrefix: "StatuePhoto_Meta_",
|
||||||
|
indexKey: "StatuePhoto_Index"
|
||||||
|
),
|
||||||
|
|
||||||
|
[CaptureType.DivingMinigame] = new CaptureConfig(
|
||||||
|
subFolder: "DivingMinigame",
|
||||||
|
photoPrefix: "Diving_",
|
||||||
|
metadataPrefix: "DivingPhoto_Meta_",
|
||||||
|
indexKey: "DivingPhoto_Index"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get configuration for a specific capture type
|
||||||
|
/// </summary>
|
||||||
|
public static CaptureConfig GetConfig(CaptureType type)
|
||||||
|
{
|
||||||
|
if (Configs.TryGetValue(type, out CaptureConfig config))
|
||||||
|
{
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"No configuration found for CaptureType: {type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
Assets/Scripts/Utils/PhotoCaptureConfig.cs.meta
Normal file
3
Assets/Scripts/Utils/PhotoCaptureConfig.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 96dbe33216574c7f95d9a829f7768e69
|
||||||
|
timeCreated: 1764159658
|
||||||
635
Assets/Scripts/Utils/PhotoManager.cs
Normal file
635
Assets/Scripts/Utils/PhotoManager.cs
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Core;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generalized photo capture, storage, and retrieval manager.
|
||||||
|
/// Supports multiple capture types (minigames, screenshots, etc.) with type-based configuration.
|
||||||
|
/// </summary>
|
||||||
|
public static class PhotoManager
|
||||||
|
{
|
||||||
|
private const string RootCapturesFolder = "Captures";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Photo metadata stored in PlayerPrefs
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class PhotoMetadata
|
||||||
|
{
|
||||||
|
public string photoId;
|
||||||
|
public CaptureType captureType;
|
||||||
|
public string timestamp;
|
||||||
|
public int genericMetadata; // Can be decoration count, score, collectibles, etc.
|
||||||
|
public long fileSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Plug-and-Play Coroutine
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Capture and save photo in one coroutine call. Handles UI hiding, capture, save, and restoration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="captureType">Type of capture (determines folder/prefix)</param>
|
||||||
|
/// <param name="captureArea">RectTransform defining the capture region</param>
|
||||||
|
/// <param name="uiToHide">Optional UI elements to hide during capture</param>
|
||||||
|
/// <param name="onSuccess">Callback with photoId on success</param>
|
||||||
|
/// <param name="onFailure">Callback with error message on failure</param>
|
||||||
|
/// <param name="metadata">Generic metadata (decoration count, score, etc.)</param>
|
||||||
|
/// <param name="mainCamera">Camera for coordinate conversion (null = Camera.main)</param>
|
||||||
|
/// <param name="clampToScreenBounds">If true, clamps capture area to visible screen</param>
|
||||||
|
public static IEnumerator CaptureAndSaveCoroutine(
|
||||||
|
CaptureType captureType,
|
||||||
|
RectTransform captureArea,
|
||||||
|
GameObject[] uiToHide = null,
|
||||||
|
Action<string> onSuccess = null,
|
||||||
|
Action<string> onFailure = null,
|
||||||
|
int metadata = 0,
|
||||||
|
Camera mainCamera = null,
|
||||||
|
bool clampToScreenBounds = true)
|
||||||
|
{
|
||||||
|
if (captureArea == null)
|
||||||
|
{
|
||||||
|
string error = "[PhotoManager] CaptureArea RectTransform is null!";
|
||||||
|
Logging.Error(error);
|
||||||
|
onFailure?.Invoke(error);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide UI elements
|
||||||
|
if (uiToHide != null)
|
||||||
|
{
|
||||||
|
foreach (var obj in uiToHide)
|
||||||
|
{
|
||||||
|
if (obj != null) obj.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for UI to hide
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
|
||||||
|
// Capture photo
|
||||||
|
bool captureComplete = false;
|
||||||
|
Texture2D capturedPhoto = null;
|
||||||
|
|
||||||
|
CaptureAreaPhoto(captureArea, (texture) =>
|
||||||
|
{
|
||||||
|
capturedPhoto = texture;
|
||||||
|
captureComplete = true;
|
||||||
|
}, mainCamera, clampToScreenBounds);
|
||||||
|
|
||||||
|
// Wait for capture to complete
|
||||||
|
yield return new WaitUntil(() => captureComplete);
|
||||||
|
|
||||||
|
// Restore UI elements
|
||||||
|
if (uiToHide != null)
|
||||||
|
{
|
||||||
|
foreach (var obj in uiToHide)
|
||||||
|
{
|
||||||
|
if (obj != null) obj.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save photo
|
||||||
|
if (capturedPhoto != null)
|
||||||
|
{
|
||||||
|
string photoId = SavePhoto(captureType, capturedPhoto, metadata);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(photoId))
|
||||||
|
{
|
||||||
|
Logging.Debug($"[PhotoManager] Photo saved successfully: {photoId}");
|
||||||
|
onSuccess?.Invoke(photoId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string error = "[PhotoManager] Failed to save photo!";
|
||||||
|
Logging.Error(error);
|
||||||
|
onFailure?.Invoke(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string error = "[PhotoManager] Photo capture returned null!";
|
||||||
|
Logging.Error(error);
|
||||||
|
onFailure?.Invoke(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Capture
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Capture a specific area of the screen using Screenshot Helper
|
||||||
|
/// </summary>
|
||||||
|
public static void CaptureAreaPhoto(
|
||||||
|
RectTransform captureArea,
|
||||||
|
Action<Texture2D> onComplete,
|
||||||
|
Camera mainCamera = null,
|
||||||
|
bool clampToScreenBounds = true)
|
||||||
|
{
|
||||||
|
if (captureArea == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[PhotoManager] CaptureArea RectTransform is null!");
|
||||||
|
onComplete?.Invoke(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainCamera == null) mainCamera = Camera.main;
|
||||||
|
|
||||||
|
// Use ScreenSpaceUtility to convert RectTransform to screen rect
|
||||||
|
Rect screenRect = ScreenSpaceUtility.RectTransformToScreenRect(
|
||||||
|
captureArea,
|
||||||
|
mainCamera,
|
||||||
|
clampToScreenBounds,
|
||||||
|
returnCenterPosition: true
|
||||||
|
);
|
||||||
|
|
||||||
|
Logging.Debug($"[PhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}");
|
||||||
|
|
||||||
|
// Use Screenshot Helper's Capture method
|
||||||
|
ScreenshotHelper.Instance.Capture(
|
||||||
|
screenRect.position,
|
||||||
|
screenRect.size,
|
||||||
|
(texture) =>
|
||||||
|
{
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[PhotoManager] Photo captured: {texture.width}x{texture.height}");
|
||||||
|
onComplete?.Invoke(texture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Error("[PhotoManager] Screenshot Helper returned null texture!");
|
||||||
|
onComplete?.Invoke(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Save/Load
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save photo to persistent storage with metadata
|
||||||
|
/// </summary>
|
||||||
|
public static string SavePhoto(CaptureType captureType, Texture2D photo, int metadata = 0)
|
||||||
|
{
|
||||||
|
if (photo == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[PhotoManager] Cannot save null photo");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
|
||||||
|
// Generate unique photo ID
|
||||||
|
string photoId = $"{config.photoPrefix}{DateTime.Now.Ticks}";
|
||||||
|
|
||||||
|
// Get capture directory for this type
|
||||||
|
string captureDirectory = GetCaptureDirectory(captureType);
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
if (!Directory.Exists(captureDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(captureDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save texture
|
||||||
|
string filePath = Path.Combine(captureDirectory, $"{photoId}.png");
|
||||||
|
byte[] pngData = photo.EncodeToPNG();
|
||||||
|
File.WriteAllBytes(filePath, pngData);
|
||||||
|
|
||||||
|
// Calculate file size
|
||||||
|
FileInfo fileInfo = new FileInfo(filePath);
|
||||||
|
long fileSize = fileInfo.Exists ? fileInfo.Length : 0;
|
||||||
|
|
||||||
|
// Save metadata
|
||||||
|
PhotoMetadata photoMetadata = new PhotoMetadata
|
||||||
|
{
|
||||||
|
photoId = photoId,
|
||||||
|
captureType = captureType,
|
||||||
|
timestamp = DateTime.Now.ToString("o"),
|
||||||
|
genericMetadata = metadata,
|
||||||
|
fileSizeBytes = fileSize
|
||||||
|
};
|
||||||
|
|
||||||
|
SaveMetadata(captureType, photoMetadata);
|
||||||
|
AddToPhotoIndex(captureType, photoId);
|
||||||
|
|
||||||
|
Logging.Debug($"[PhotoManager] Photo saved: {filePath} ({fileSize} bytes)");
|
||||||
|
return photoId;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[PhotoManager] Failed to save photo: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load photo texture from storage
|
||||||
|
/// </summary>
|
||||||
|
public static Texture2D LoadPhoto(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(photoId))
|
||||||
|
{
|
||||||
|
Logging.Warning("[PhotoManager] PhotoId is null or empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string filePath = GetPhotoFilePath(captureType, photoId);
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
Logging.Warning($"[PhotoManager] Photo not found: {filePath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fileData = File.ReadAllBytes(filePath);
|
||||||
|
Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
||||||
|
|
||||||
|
if (texture.LoadImage(fileData))
|
||||||
|
{
|
||||||
|
Logging.Debug($"[PhotoManager] Photo loaded: {photoId} ({texture.width}x{texture.height})");
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Error($"[PhotoManager] Failed to decode image: {photoId}");
|
||||||
|
UnityEngine.Object.Destroy(texture);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[PhotoManager] Failed to load photo {photoId}: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load multiple photos (most recent first)
|
||||||
|
/// </summary>
|
||||||
|
public static List<Texture2D> LoadPhotos(CaptureType captureType, int count)
|
||||||
|
{
|
||||||
|
List<string> photoIds = GetPhotoIds(captureType, count);
|
||||||
|
List<Texture2D> photos = new List<Texture2D>();
|
||||||
|
|
||||||
|
foreach (string photoId in photoIds)
|
||||||
|
{
|
||||||
|
Texture2D photo = LoadPhoto(captureType, photoId);
|
||||||
|
if (photo != null)
|
||||||
|
{
|
||||||
|
photos.Add(photo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return photos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load all photos for a capture type
|
||||||
|
/// </summary>
|
||||||
|
public static List<Texture2D> LoadAllPhotos(CaptureType captureType)
|
||||||
|
{
|
||||||
|
return LoadPhotos(captureType, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete photo and its metadata
|
||||||
|
/// </summary>
|
||||||
|
public static bool DeletePhoto(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(photoId)) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string filePath = GetPhotoFilePath(captureType, photoId);
|
||||||
|
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteMetadata(captureType, photoId);
|
||||||
|
RemoveFromPhotoIndex(captureType, photoId);
|
||||||
|
|
||||||
|
Logging.Debug($"[PhotoManager] Photo deleted: {photoId}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[PhotoManager] Failed to delete photo {photoId}: {e.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Retrieval & Queries
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get photo IDs for a capture type (most recent first)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="captureType">Type of capture</param>
|
||||||
|
/// <param name="count">Number of IDs to return (-1 = all)</param>
|
||||||
|
public static List<string> GetPhotoIds(CaptureType captureType, int count = -1)
|
||||||
|
{
|
||||||
|
List<string> allIds = GetAllPhotoIds(captureType);
|
||||||
|
|
||||||
|
if (count < 0 || count >= allIds.Count)
|
||||||
|
{
|
||||||
|
return allIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allIds.GetRange(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all photo IDs sorted by timestamp (newest first)
|
||||||
|
/// </summary>
|
||||||
|
public static List<string> GetAllPhotoIds(CaptureType captureType)
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
string indexJson = PlayerPrefs.GetString(config.indexKey, "[]");
|
||||||
|
List<string> photoIds = JsonUtility.FromJson<PhotoIdList>(WrapJsonArray(indexJson))?.ids ?? new List<string>();
|
||||||
|
|
||||||
|
// Sort by timestamp descending (newest first)
|
||||||
|
photoIds.Sort((a, b) =>
|
||||||
|
{
|
||||||
|
PhotoMetadata metaA = LoadMetadata(captureType, a);
|
||||||
|
PhotoMetadata metaB = LoadMetadata(captureType, b);
|
||||||
|
|
||||||
|
DateTime dateA = DateTime.Parse(metaA?.timestamp ?? DateTime.MinValue.ToString("o"));
|
||||||
|
DateTime dateB = DateTime.Parse(metaB?.timestamp ?? DateTime.MinValue.ToString("o"));
|
||||||
|
|
||||||
|
return dateB.CompareTo(dateA);
|
||||||
|
});
|
||||||
|
|
||||||
|
return photoIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get paginated photo IDs for optimized gallery loading
|
||||||
|
/// </summary>
|
||||||
|
public static List<string> GetPhotoIdsPage(CaptureType captureType, int page, int pageSize)
|
||||||
|
{
|
||||||
|
List<string> allIds = GetAllPhotoIds(captureType);
|
||||||
|
int startIndex = page * pageSize;
|
||||||
|
|
||||||
|
if (startIndex >= allIds.Count) return new List<string>();
|
||||||
|
|
||||||
|
int count = Mathf.Min(pageSize, allIds.Count - startIndex);
|
||||||
|
return allIds.GetRange(startIndex, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get total number of saved photos
|
||||||
|
/// </summary>
|
||||||
|
public static int GetPhotoCount(CaptureType captureType)
|
||||||
|
{
|
||||||
|
return GetAllPhotoIds(captureType).Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get latest photo ID (most recent)
|
||||||
|
/// </summary>
|
||||||
|
public static string GetLatestPhotoId(CaptureType captureType)
|
||||||
|
{
|
||||||
|
List<string> allIds = GetAllPhotoIds(captureType);
|
||||||
|
return allIds.Count > 0 ? allIds[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load photo metadata
|
||||||
|
/// </summary>
|
||||||
|
public static PhotoMetadata GetPhotoMetadata(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
return LoadMetadata(captureType, photoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Utility Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create thumbnail from full-size photo (for gallery preview)
|
||||||
|
/// </summary>
|
||||||
|
public static Texture2D CreateThumbnail(Texture2D fullSizePhoto, int maxSize = 256)
|
||||||
|
{
|
||||||
|
if (fullSizePhoto == null) return null;
|
||||||
|
|
||||||
|
int width = fullSizePhoto.width;
|
||||||
|
int height = fullSizePhoto.height;
|
||||||
|
|
||||||
|
// Calculate thumbnail size maintaining aspect ratio
|
||||||
|
float scale = Mathf.Min((float)maxSize / width, (float)maxSize / height);
|
||||||
|
int thumbWidth = Mathf.RoundToInt(width * scale);
|
||||||
|
int thumbHeight = Mathf.RoundToInt(height * scale);
|
||||||
|
|
||||||
|
// Create thumbnail using bilinear filtering
|
||||||
|
RenderTexture rt = RenderTexture.GetTemporary(thumbWidth, thumbHeight, 0, RenderTextureFormat.ARGB32);
|
||||||
|
RenderTexture.active = rt;
|
||||||
|
|
||||||
|
Graphics.Blit(fullSizePhoto, rt);
|
||||||
|
|
||||||
|
Texture2D thumbnail = new Texture2D(thumbWidth, thumbHeight, TextureFormat.RGB24, false);
|
||||||
|
thumbnail.ReadPixels(new Rect(0, 0, thumbWidth, thumbHeight), 0, 0);
|
||||||
|
thumbnail.Apply();
|
||||||
|
|
||||||
|
RenderTexture.active = null;
|
||||||
|
RenderTexture.ReleaseTemporary(rt);
|
||||||
|
|
||||||
|
return thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get capture directory for a specific type
|
||||||
|
/// </summary>
|
||||||
|
public static string GetCaptureDirectory(CaptureType captureType)
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
return Path.Combine(Application.persistentDataPath, RootCapturesFolder, config.subFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Decoration Metadata
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save decoration metadata for a photo
|
||||||
|
/// Saves both with photo ID and as "latest" for easy reference
|
||||||
|
/// </summary>
|
||||||
|
public static void SaveDecorationMetadata<T>(CaptureType captureType, string photoId, T metadata) where T : class
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string directory = GetCaptureDirectory(captureType);
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = JsonUtility.ToJson(metadata, true);
|
||||||
|
|
||||||
|
// Save with photo ID (for loading specific photo's decorations)
|
||||||
|
string specificPath = Path.Combine(directory, $"{photoId}_decorations.json");
|
||||||
|
File.WriteAllText(specificPath, json);
|
||||||
|
|
||||||
|
// Also save as "latest" for easy reference
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
string latestPath = Path.Combine(directory, $"{config.subFolder}_latest.json");
|
||||||
|
File.WriteAllText(latestPath, json);
|
||||||
|
|
||||||
|
Logging.Debug($"[PhotoManager] Decoration metadata saved: {specificPath}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[PhotoManager] Failed to save decoration metadata: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load decoration metadata for a specific photo
|
||||||
|
/// </summary>
|
||||||
|
public static T LoadDecorationMetadata<T>(CaptureType captureType, string photoId) where T : class
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string directory = GetCaptureDirectory(captureType);
|
||||||
|
string filePath = Path.Combine(directory, $"{photoId}_decorations.json");
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
Logging.Warning($"[PhotoManager] Decoration metadata not found: {filePath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = File.ReadAllText(filePath);
|
||||||
|
T metadata = JsonUtility.FromJson<T>(json);
|
||||||
|
|
||||||
|
Logging.Debug($"[PhotoManager] Decoration metadata loaded: {filePath}");
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[PhotoManager] Failed to load decoration metadata: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load the latest decoration metadata (most recent capture)
|
||||||
|
/// </summary>
|
||||||
|
public static T LoadLatestDecorationMetadata<T>(CaptureType captureType) where T : class
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
string directory = GetCaptureDirectory(captureType);
|
||||||
|
string filePath = Path.Combine(directory, $"{config.subFolder}_latest.json");
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
Logging.Warning($"[PhotoManager] Latest decoration metadata not found: {filePath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = File.ReadAllText(filePath);
|
||||||
|
T metadata = JsonUtility.FromJson<T>(json);
|
||||||
|
|
||||||
|
Logging.Debug($"[PhotoManager] Latest decoration metadata loaded: {filePath}");
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[PhotoManager] Failed to load latest decoration metadata: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Helpers
|
||||||
|
|
||||||
|
private static string GetPhotoFilePath(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
return Path.Combine(GetCaptureDirectory(captureType), $"{photoId}.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SaveMetadata(CaptureType captureType, PhotoMetadata metadata)
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
string json = JsonUtility.ToJson(metadata);
|
||||||
|
PlayerPrefs.SetString(config.metadataPrefix + metadata.photoId, json);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PhotoMetadata LoadMetadata(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
string json = PlayerPrefs.GetString(config.metadataPrefix + photoId, null);
|
||||||
|
return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson<PhotoMetadata>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteMetadata(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
PlayerPrefs.DeleteKey(config.metadataPrefix + photoId);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddToPhotoIndex(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
List<string> photoIds = GetAllPhotoIds(captureType);
|
||||||
|
if (!photoIds.Contains(photoId))
|
||||||
|
{
|
||||||
|
photoIds.Add(photoId);
|
||||||
|
SavePhotoIndex(captureType, photoIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveFromPhotoIndex(CaptureType captureType, string photoId)
|
||||||
|
{
|
||||||
|
List<string> photoIds = GetAllPhotoIds(captureType);
|
||||||
|
if (photoIds.Remove(photoId))
|
||||||
|
{
|
||||||
|
SavePhotoIndex(captureType, photoIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SavePhotoIndex(CaptureType captureType, List<string> photoIds)
|
||||||
|
{
|
||||||
|
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||||
|
string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds });
|
||||||
|
PlayerPrefs.SetString(config.indexKey, json);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string WrapJsonArray(string json)
|
||||||
|
{
|
||||||
|
if (json.StartsWith("[")) return "{\"ids\":" + json + "}";
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
private class PhotoIdList
|
||||||
|
{
|
||||||
|
public List<string> ids = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
Assets/Scripts/Utils/PhotoManager.cs.meta
Normal file
3
Assets/Scripts/Utils/PhotoManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c558069d1a8e46febc1c3716e9b76490
|
||||||
|
timeCreated: 1764159714
|
||||||
198
Assets/Scripts/Utils/ScreenSpaceUtility.cs
Normal file
198
Assets/Scripts/Utils/ScreenSpaceUtility.cs
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
using Core;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for screen space and UI coordinate conversions
|
||||||
|
/// </summary>
|
||||||
|
public static class ScreenSpaceUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert RectTransform to screen space rect with center position.
|
||||||
|
/// Useful for screenshot capture or screen-based calculations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rectTransform">RectTransform to convert</param>
|
||||||
|
/// <param name="camera">Camera used for coordinate conversion (null = Camera.main)</param>
|
||||||
|
/// <param name="clampToScreenBounds">If true, clamps the rect to visible screen area</param>
|
||||||
|
/// <param name="returnCenterPosition">If true, returns center position; if false, returns bottom-left position</param>
|
||||||
|
/// <returns>Screen space rect (position is center or bottom-left based on returnCenterPosition)</returns>
|
||||||
|
public static Rect RectTransformToScreenRect(
|
||||||
|
RectTransform rectTransform,
|
||||||
|
Camera camera = null,
|
||||||
|
bool clampToScreenBounds = true,
|
||||||
|
bool returnCenterPosition = true)
|
||||||
|
{
|
||||||
|
if (rectTransform == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[ScreenSpaceUtility] RectTransform is null!");
|
||||||
|
return new Rect(Screen.width / 2f, Screen.height / 2f, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera == null) camera = Camera.main;
|
||||||
|
|
||||||
|
// Get world corners (0=bottom-left, 1=top-left, 2=top-right, 3=bottom-right)
|
||||||
|
Vector3[] corners = new Vector3[4];
|
||||||
|
rectTransform.GetWorldCorners(corners);
|
||||||
|
|
||||||
|
// Determine correct camera based on Canvas render mode
|
||||||
|
Camera canvasCamera = GetCanvasCamera(rectTransform, camera);
|
||||||
|
|
||||||
|
// Convert corners to screen space
|
||||||
|
Vector2 min = RectTransformUtility.WorldToScreenPoint(canvasCamera, corners[0]);
|
||||||
|
Vector2 max = RectTransformUtility.WorldToScreenPoint(canvasCamera, corners[2]);
|
||||||
|
|
||||||
|
Logging.Debug($"[ScreenSpaceUtility] Canvas mode: {rectTransform.GetComponentInParent<Canvas>()?.renderMode}, Camera: {canvasCamera?.name ?? "null"}");
|
||||||
|
Logging.Debug($"[ScreenSpaceUtility] Raw screen coords - min: {min}, max: {max}");
|
||||||
|
|
||||||
|
// Apply screen bounds clamping if requested
|
||||||
|
if (clampToScreenBounds)
|
||||||
|
{
|
||||||
|
return ClampRectToScreenBounds(min, max, returnCenterPosition);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No clamping - use raw values
|
||||||
|
float width = Mathf.Abs(max.x - min.x);
|
||||||
|
float height = Mathf.Abs(max.y - min.y);
|
||||||
|
|
||||||
|
if (returnCenterPosition)
|
||||||
|
{
|
||||||
|
float centerX = min.x + width / 2f;
|
||||||
|
float centerY = min.y + height / 2f;
|
||||||
|
return new Rect(centerX, centerY, width, height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new Rect(min.x, min.y, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the appropriate camera for UI coordinate conversion based on Canvas render mode
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rectTransform">RectTransform to find canvas for</param>
|
||||||
|
/// <param name="fallbackCamera">Fallback camera if no canvas found</param>
|
||||||
|
/// <returns>Camera to use for coordinate conversion (null for Overlay mode)</returns>
|
||||||
|
public static Camera GetCanvasCamera(RectTransform rectTransform, Camera fallbackCamera = null)
|
||||||
|
{
|
||||||
|
Canvas canvas = rectTransform.GetComponentInParent<Canvas>();
|
||||||
|
|
||||||
|
if (canvas == null)
|
||||||
|
{
|
||||||
|
return fallbackCamera;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (canvas.renderMode)
|
||||||
|
{
|
||||||
|
case RenderMode.ScreenSpaceOverlay:
|
||||||
|
// For Screen Space - Overlay, use null (direct pixel coords)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case RenderMode.ScreenSpaceCamera:
|
||||||
|
// For Screen Space - Camera, use the canvas's worldCamera
|
||||||
|
return canvas.worldCamera;
|
||||||
|
|
||||||
|
case RenderMode.WorldSpace:
|
||||||
|
// For World Space, use canvas camera or fallback
|
||||||
|
return canvas.worldCamera ?? fallbackCamera;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fallbackCamera;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamp rect coordinates to screen bounds and calculate final rect
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="min">Bottom-left corner in screen space</param>
|
||||||
|
/// <param name="max">Top-right corner in screen space</param>
|
||||||
|
/// <param name="returnCenterPosition">If true, returns center position; if false, returns bottom-left</param>
|
||||||
|
/// <returns>Clamped screen rect</returns>
|
||||||
|
private static Rect ClampRectToScreenBounds(Vector2 min, Vector2 max, bool returnCenterPosition)
|
||||||
|
{
|
||||||
|
// Clamp to screen bounds
|
||||||
|
float minX = Mathf.Max(0, min.x);
|
||||||
|
float minY = Mathf.Max(0, min.y);
|
||||||
|
float maxX = Mathf.Min(Screen.width, max.x);
|
||||||
|
float maxY = Mathf.Min(Screen.height, max.y);
|
||||||
|
|
||||||
|
// Check if rect is completely outside screen bounds
|
||||||
|
if (minX >= Screen.width || minY >= Screen.height || maxX <= 0 || maxY <= 0)
|
||||||
|
{
|
||||||
|
Logging.Warning("[ScreenSpaceUtility] RectTransform is completely outside screen bounds!");
|
||||||
|
return new Rect(Screen.width / 2f, Screen.height / 2f, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate dimensions from clamped bounds
|
||||||
|
float width = maxX - minX;
|
||||||
|
float height = maxY - minY;
|
||||||
|
|
||||||
|
// Validate dimensions
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[ScreenSpaceUtility] Invalid dimensions after clamping: {width}x{height}");
|
||||||
|
return new Rect(Screen.width / 2f, Screen.height / 2f, 100, 100); // Fallback small rect
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[ScreenSpaceUtility] Clamped bounds - min: ({minX}, {minY}), max: ({maxX}, {maxY})");
|
||||||
|
|
||||||
|
if (returnCenterPosition)
|
||||||
|
{
|
||||||
|
// Calculate center position from clamped bounds
|
||||||
|
float centerX = minX + width / 2f;
|
||||||
|
float centerY = minY + height / 2f;
|
||||||
|
|
||||||
|
Logging.Debug($"[ScreenSpaceUtility] Final rect - center: ({centerX}, {centerY}), size: ({width}, {height})");
|
||||||
|
return new Rect(centerX, centerY, width, height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Return bottom-left position
|
||||||
|
Logging.Debug($"[ScreenSpaceUtility] Final rect - position: ({minX}, {minY}), size: ({width}, {height})");
|
||||||
|
return new Rect(minX, minY, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if a screen rect is completely outside screen bounds
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsRectOutsideScreenBounds(Rect rect)
|
||||||
|
{
|
||||||
|
return rect.xMax < 0 || rect.xMin > Screen.width ||
|
||||||
|
rect.yMax < 0 || rect.yMin > Screen.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the visible portion of a rect when clamped to screen bounds
|
||||||
|
/// </summary>
|
||||||
|
public static Rect GetVisibleScreenRect(Rect rect)
|
||||||
|
{
|
||||||
|
float minX = Mathf.Max(0, rect.xMin);
|
||||||
|
float minY = Mathf.Max(0, rect.yMin);
|
||||||
|
float maxX = Mathf.Min(Screen.width, rect.xMax);
|
||||||
|
float maxY = Mathf.Min(Screen.height, rect.yMax);
|
||||||
|
|
||||||
|
float width = Mathf.Max(0, maxX - minX);
|
||||||
|
float height = Mathf.Max(0, maxY - minY);
|
||||||
|
|
||||||
|
return new Rect(minX, minY, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the percentage of a rect that is visible on screen
|
||||||
|
/// </summary>
|
||||||
|
public static float GetVisiblePercentage(Rect rect)
|
||||||
|
{
|
||||||
|
if (rect.width <= 0 || rect.height <= 0) return 0f;
|
||||||
|
|
||||||
|
Rect visibleRect = GetVisibleScreenRect(rect);
|
||||||
|
float totalArea = rect.width * rect.height;
|
||||||
|
float visibleArea = visibleRect.width * visibleRect.height;
|
||||||
|
|
||||||
|
return visibleArea / totalArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
Assets/Scripts/Utils/ScreenSpaceUtility.cs.meta
Normal file
3
Assets/Scripts/Utils/ScreenSpaceUtility.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f9a38b4740894e6ea8c32d8f62bc5bd6
|
||||||
|
timeCreated: 1764148562
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using Pixelplacement;
|
using System;
|
||||||
|
using Pixelplacement;
|
||||||
using Pixelplacement.TweenSystem;
|
using Pixelplacement.TweenSystem;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Minigames.StatueDressup.Utils
|
namespace Utils
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Common animation utilities extracted from CardAnimator pattern.
|
/// Common animation utilities extracted from CardAnimator pattern.
|
||||||
@@ -16,7 +16,7 @@ MonoBehaviour:
|
|||||||
pauseTimeOnPauseGame: 0
|
pauseTimeOnPauseGame: 0
|
||||||
useSaveLoadSystem: 1
|
useSaveLoadSystem: 1
|
||||||
autoClearSaves: 0
|
autoClearSaves: 0
|
||||||
dontSaveOnQuit: 1
|
dontSaveOnQuit: 0
|
||||||
bootstrapLogVerbosity: 0
|
bootstrapLogVerbosity: 0
|
||||||
settingsLogVerbosity: 0
|
settingsLogVerbosity: 0
|
||||||
gameManagerLogVerbosity: 0
|
gameManagerLogVerbosity: 0
|
||||||
|
|||||||
154
docs/statue_minigame_primer.md
Normal file
154
docs/statue_minigame_primer.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Statue Decoration Minigame - Primer
|
||||||
|
|
||||||
|
A quick-start guide for designers and programmers working on the statue decoration minigame.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Quick References](#quick-references)
|
||||||
|
- [Adding Audio/Visual Feedback](#adding-audiovisual-feedback)
|
||||||
|
- [Loading Decoration Data & Settings](#loading-decoration-data--settings)
|
||||||
|
- [Adding New Decorations](#adding-new-decorations)
|
||||||
|
- [Changing UI Layout](#changing-ui-layout)
|
||||||
|
- [Tweaking Behavior](#tweaking-behavior)
|
||||||
|
- [Photo Saving/Loading](#photo-savingloading)
|
||||||
|
- [Class Reference](#class-reference)
|
||||||
|
- [Controllers](#controllers)
|
||||||
|
- [Drag & Drop](#drag--drop)
|
||||||
|
- [Data & Events](#data--events)
|
||||||
|
- [Display](#display)
|
||||||
|
- [Settings](#settings)
|
||||||
|
- [Common Tasks](#common-tasks)
|
||||||
|
- [Known TODOs](#known-todos)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Players drag sticker decorations from a menu onto a statue, decorate it however they like, and take photos. Photos are saved to a gallery with decoration metadata so the statue can be restored in the town map miniature.
|
||||||
|
|
||||||
|
## Quick References
|
||||||
|
|
||||||
|
### Adding Audio/Visual Feedback
|
||||||
|
**Location:** `DecorationEventsManager.cs` in `Events/`
|
||||||
|
All decoration interactions broadcast events (tap, drag start, drag end, place, etc.). Subscribe to events or edit the stub handler methods to add your SFX/VFX. Just add a `DecorationEventsManager` component to the scene.
|
||||||
|
|
||||||
|
### Loading Decoration Data & Settings
|
||||||
|
**Location:** `DecorationDataManager.cs` in `Controllers/`
|
||||||
|
This singleton loads settings from GameManager, then loads all decoration sprites via Addressables. Everything happens automatically on scene start. Use `DecorationDataManager.WhenReady(() => { ... })` to wait for data before accessing.
|
||||||
|
|
||||||
|
### Adding New Decorations
|
||||||
|
**Location:** Create a `DecorationData` ScriptableObject in Unity
|
||||||
|
Set the sprite, ID, and name. Add it to the Addressables group specified in `StatueDressupSettings`. It'll appear in the menu automatically.
|
||||||
|
|
||||||
|
### Changing UI Layout
|
||||||
|
**Location:** `StatueDressupMinigame` scene
|
||||||
|
- Menu grid: `DecorationMenuController` → `ItemsContainer`
|
||||||
|
- Statue area: `StatueDecorationController` → `StatueImage` and `DecorationsParent`
|
||||||
|
- Photo gallery: `StatuePhotoGalleryController` → Grid and pagination UI
|
||||||
|
|
||||||
|
### Tweaking Behavior
|
||||||
|
**Location:** `StatueDressupSettings` ScriptableObject
|
||||||
|
Contains all tunable values: animation durations, menu items per page, gallery settings, drag thresholds, etc.
|
||||||
|
|
||||||
|
### Photo Saving/Loading
|
||||||
|
**Location:** `StatueDecorationController.cs` - `TakePhoto()` method
|
||||||
|
Photos are captured with metadata (decoration positions, scales, IDs). Use `PhotoManager` for save/load operations. Metadata is read by `StatueDecorationLoader` to restore decorations on the town map.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Class Reference
|
||||||
|
|
||||||
|
### Controllers
|
||||||
|
|
||||||
|
**`DecorationDataManager`**
|
||||||
|
- **What it does:** Loads settings and all decoration data from Addressables. Single source of truth for both.
|
||||||
|
- **When you might need it:** Accessing decoration sprites/data, checking if a decoration ID exists, waiting for data to be ready.
|
||||||
|
|
||||||
|
**`StatueDecorationController`**
|
||||||
|
- **What it does:** Main minigame controller - manages decoration placement, photo capture, and save/load of decoration state.
|
||||||
|
- **When you might need it:** Taking photos, getting/setting decoration placements, loading saved decorations, accessing statue UI references.
|
||||||
|
|
||||||
|
**`DecorationMenuController`**
|
||||||
|
- **What it does:** Manages the scrollable decoration picker menu with pagination and spawns draggable instances.
|
||||||
|
- **When you might need it:** Changing menu behavior, adding categories/filters, modifying how decorations are displayed.
|
||||||
|
|
||||||
|
**`StatuePhotoGalleryController`**
|
||||||
|
- **What it does:** Displays saved photos in a paginated grid with thumbnail caching and enlarged preview.
|
||||||
|
- **When you might need it:** Modifying gallery UI, changing thumbnail behavior, adding photo delete/share functionality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Drag & Drop
|
||||||
|
|
||||||
|
**`DecorationDraggableInstance`**
|
||||||
|
- **What it does:** The actual draggable decoration instance that follows the cursor and can be placed on the statue.
|
||||||
|
- **When you might need it:** Changing drag behavior, adding visual effects during drag, modifying placement validation.
|
||||||
|
|
||||||
|
**`DecorationGridIcon`**
|
||||||
|
- **What it does:** Static menu icon that spawns a draggable instance when clicked/dragged.
|
||||||
|
- **When you might need it:** Adding icon animations, tooltips, or preview functionality to menu items.
|
||||||
|
|
||||||
|
**`DecorationDragContext`**
|
||||||
|
- **What it does:** Data container passed to draggable instances with all references and callbacks needed for dragging.
|
||||||
|
- **When you might need it:** Adding new data that draggables need during their lifecycle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Data & Events
|
||||||
|
|
||||||
|
**`DecorationData`** (ScriptableObject)
|
||||||
|
- **What it does:** Defines a single decoration (sprite, ID, name, authored size).
|
||||||
|
- **When you might need it:** Creating new decorations or querying decoration properties.
|
||||||
|
|
||||||
|
**`DecorationPlacement`**
|
||||||
|
- **What it does:** Serializable data for a placed decoration (position, scale, rotation, sorting order).
|
||||||
|
- **When you might need it:** Saving/loading decoration state, modifying what gets persisted.
|
||||||
|
|
||||||
|
**`DecorationMetadata`**
|
||||||
|
- **What it does:** Complete metadata for all decorations on a photo (list of placements + coordinate system type).
|
||||||
|
- **When you might need it:** Working with photo save/load system, adding new metadata fields.
|
||||||
|
|
||||||
|
**`DecorationEventData`**
|
||||||
|
- **What it does:** Event payload containing decoration, instance GameObject, position, and context flags.
|
||||||
|
- **When you might need it:** Handling decoration events for SFX/VFX, adding new event data fields.
|
||||||
|
|
||||||
|
**`DecorationEventsManager`**
|
||||||
|
- **What it does:** Broadcasts and handles all decoration interaction events (tap, drag, place, etc.).
|
||||||
|
- **When you might need it:** Playing sounds/effects in response to decoration interactions, adding telemetry.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Display
|
||||||
|
|
||||||
|
**`StatueDecorationLoader`**
|
||||||
|
- **What it does:** Loads saved decoration metadata and spawns decorations on a statue (used in town map).
|
||||||
|
- **When you might need it:** Displaying decorations outside the minigame, fixing coordinate conversion bugs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
**`StatueDressupSettings`** (ScriptableObject in `Core/Settings`)
|
||||||
|
- **What it does:** All configuration values for the minigame (durations, counts, labels, thresholds).
|
||||||
|
- **When you might need it:** Tuning any minigame behavior without touching code.
|
||||||
|
|
||||||
|
**`StatueDressupConstants`**
|
||||||
|
- **What it does:** Fallback constant values if settings are missing.
|
||||||
|
- **When you might need it:** Setting default values or adding new configurable parameters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
**Add a new decoration:**
|
||||||
|
Create `DecorationData` ScriptableObject → Set sprite/ID → Add to Addressables group
|
||||||
|
|
||||||
|
**Change drag feel:**
|
||||||
|
Edit `StatueDressupSettings` → Adjust `DragSnapThreshold` or animation durations
|
||||||
|
|
||||||
|
**Add sound to decoration tap:**
|
||||||
|
Edit `DecorationEventsManager.HandleDecorationTappedInGrid()` → Play your audio clip
|
||||||
|
|
||||||
|
**Change menu layout:**
|
||||||
|
Edit `StatueDressupSettings` → Adjust `ItemsPerPage` or edit menu prefab
|
||||||
|
|
||||||
|
**Debug save/load:**
|
||||||
|
Check `PhotoManager.SavePhoto()` and `StatueDecorationLoader.LoadAndDisplayDecorations()`
|
||||||
|
|
||||||
Reference in New Issue
Block a user