stash work
This commit is contained in:
committed by
Michal Pikulski
parent
8d410b42d3
commit
5bab6d9596
@@ -13,6 +13,7 @@ MonoBehaviour:
|
||||
m_Name: AddressableAssetGroupSortSettings
|
||||
m_EditorClassIdentifier:
|
||||
sortOrder:
|
||||
- b58f7c15a2ffcbc4483cb2bd0a0dee90
|
||||
- 21420e71d44619f468badaed8efc42a7
|
||||
- 0d5d36d6da388314b92b9c6967d23f39
|
||||
- 75e1f68b7bf77f34f8ad4aeab74d4244
|
||||
|
||||
@@ -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
|
||||
androidETC2FallbackOverride: 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:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
@@ -129,12 +155,12 @@ TextureImporter:
|
||||
name: DecorationMinigame_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 143
|
||||
y: 59
|
||||
width: 701
|
||||
height: 983
|
||||
x: 0
|
||||
y: 0
|
||||
width: 962
|
||||
height: 1109
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
@@ -151,7 +177,7 @@ TextureImporter:
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
spriteID: 99610a41e95434e42bc921e55c481562
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
|
||||
@@ -102,4 +102,4 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.DragDrop.DecorationDraggableInstance
|
||||
decorationImage: {fileID: 597267714783345863}
|
||||
canvasGroup: {fileID: 0}
|
||||
canvasGroup: {fileID: 3617977973382190563}
|
||||
|
||||
@@ -468,6 +468,11 @@ PrefabInstance:
|
||||
addedObject: {fileID: 706319199}
|
||||
m_AddedComponents: []
|
||||
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
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2885,6 +2890,37 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1292467736
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1292467737}
|
||||
m_Layer: 10
|
||||
m_Name: DecorationsRoot
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1292467737
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1292467736}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1460027142}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!4 &1298174946 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 5179840719513743426, guid: 4c8aa6e55c410284aaa57c461eba5bdf, type: 3}
|
||||
@@ -3060,11 +3096,11 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8766058064105819536, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||
propertyPath: m_SpriteTilingProperty.oldSize.x
|
||||
value: 7.01
|
||||
value: 9.62
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8766058064105819536, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||
propertyPath: m_SpriteTilingProperty.oldSize.y
|
||||
value: 9.83
|
||||
value: 11.09
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
@@ -3075,13 +3111,35 @@ PrefabInstance:
|
||||
- targetCorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1253055066}
|
||||
m_AddedComponents: []
|
||||
- targetCorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1292467737}
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 7404622075362872657, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1460027146}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||
--- !u!4 &1460027142 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 5727794563305583620, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3}
|
||||
m_PrefabInstance: {fileID: 1460027141}
|
||||
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
|
||||
decorationDataLabel: StatueDecorations
|
||||
decorationRoot: {fileID: 1292467737}
|
||||
specificPhotoId:
|
||||
showDebugInfo: 1
|
||||
--- !u!4 &1572710089 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -188,10 +188,21 @@ namespace AppleHills.Core.Settings
|
||||
float HoverAnimationDuration { get; }
|
||||
float PlacementAnimationDuration { get; }
|
||||
|
||||
// Addressables
|
||||
string DecorationDataLabel { get; }
|
||||
|
||||
// Photo Settings
|
||||
string PhotoSaveKey { get; }
|
||||
int PhotoQuality { 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
|
||||
int CardsRewardCount { get; }
|
||||
|
||||
@@ -49,6 +49,10 @@ namespace Core.Settings
|
||||
[Tooltip("Duration for placement animation")]
|
||||
[SerializeField] private float placementAnimationDuration = 0.15f;
|
||||
|
||||
[Header("Addressables")]
|
||||
[Tooltip("Addressables label for loading DecorationData assets")]
|
||||
[SerializeField] private string decorationDataLabel = "StatueDecorations";
|
||||
|
||||
[Header("Photo Settings")]
|
||||
[Tooltip("PlayerPrefs key for saving the statue photo")]
|
||||
[SerializeField] private string photoSaveKey = "MrCementStatuePhoto";
|
||||
@@ -59,6 +63,16 @@ namespace Core.Settings
|
||||
[Tooltip("Whether to capture full screen or just statue area")]
|
||||
[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")]
|
||||
[Tooltip("Number of Blokkemon cards awarded on completion")]
|
||||
[SerializeField] private int cardsRewardCount = 3;
|
||||
@@ -97,10 +111,21 @@ namespace Core.Settings
|
||||
public float HoverAnimationDuration => hoverAnimationDuration;
|
||||
public float PlacementAnimationDuration => placementAnimationDuration;
|
||||
|
||||
// IStatueDressupSettings implementation - Addressables
|
||||
public string DecorationDataLabel => decorationDataLabel;
|
||||
|
||||
// IStatueDressupSettings implementation - Photo Settings
|
||||
public string PhotoSaveKey => photoSaveKey;
|
||||
public int PhotoQuality => photoQuality;
|
||||
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
|
||||
public int CardsRewardCount => cardsRewardCount;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Core;
|
||||
using Minigames.StatueDressup.Utils;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Utils;
|
||||
|
||||
namespace Minigames.StatueDressup.Controllers
|
||||
{
|
||||
@@ -24,7 +23,7 @@ namespace Minigames.StatueDressup.Controllers
|
||||
captureButton.onClick.AddListener(OnCaptureClicked);
|
||||
}
|
||||
|
||||
Debug.Log($"[PhotoCaptureTest] Ready. Photo save path: {StatuePhotoManager.GetPhotoDirectory()}");
|
||||
Debug.Log($"[PhotoCaptureTest] Ready. Photo save path: {PhotoManager.GetCaptureDirectory(CaptureType.StatueMinigame)}");
|
||||
}
|
||||
|
||||
private void OnCaptureClicked()
|
||||
@@ -41,47 +40,29 @@ namespace Minigames.StatueDressup.Controllers
|
||||
|
||||
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))
|
||||
// Use new PhotoManager plug-and-play coroutine
|
||||
yield return PhotoManager.CaptureAndSaveCoroutine(
|
||||
CaptureType.StatueMinigame,
|
||||
captureArea,
|
||||
hideTheseObjects,
|
||||
onSuccess: (photoId) =>
|
||||
{
|
||||
string path = $"{StatuePhotoManager.GetPhotoDirectory()}/{photoId}.png";
|
||||
string path = $"{PhotoManager.GetCaptureDirectory(CaptureType.StatueMinigame)}/{photoId}.png";
|
||||
Debug.Log($"[PhotoCaptureTest] ✅ SUCCESS! Photo saved: {path}");
|
||||
Debug.Log($"[PhotoCaptureTest] Photo size: {photo.width}x{photo.height}");
|
||||
}
|
||||
else
|
||||
|
||||
// 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 to save photo");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[PhotoCaptureTest] ❌ Failed to capture photo");
|
||||
}
|
||||
Debug.LogError($"[PhotoCaptureTest] ❌ Failed: {error}");
|
||||
},
|
||||
metadata: 0
|
||||
);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
_backdrop = backdrop;
|
||||
_enlargedContainer = enlargedContainer;
|
||||
_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
|
||||
@@ -64,14 +64,14 @@ namespace Minigames.StatueDressup.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle click to show enlarged view
|
||||
/// Handle click to enlarge/shrink photo
|
||||
/// </summary>
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (_galleryController != null && !string.IsNullOrEmpty(_photoId))
|
||||
{
|
||||
Logging.Debug($"[PhotoGridItem] Clicked: {_photoId}");
|
||||
_galleryController.ShowEnlargedView(_photoId);
|
||||
_galleryController.OnGridItemClicked(this, _photoId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Minigames.StatueDressup.Data;
|
||||
using Minigames.StatueDressup.DragDrop;
|
||||
using UI.Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Utils;
|
||||
|
||||
namespace Minigames.StatueDressup.Controllers
|
||||
{
|
||||
@@ -19,17 +22,24 @@ namespace Minigames.StatueDressup.Controllers
|
||||
[SerializeField] private DecorationMenuController menuController;
|
||||
[SerializeField] private Button takePhotoButton;
|
||||
[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")]
|
||||
[SerializeField] private GameObject[] uiElementsToHideForPhoto;
|
||||
|
||||
[Header("Photo Settings")]
|
||||
[SerializeField] private RectTransform photoArea; // Area to capture
|
||||
[SerializeField] private string photoSaveKey = "MrCementStatuePhoto";
|
||||
|
||||
private List<DecorationDraggableInstance> _placedDecorations = new List<DecorationDraggableInstance>();
|
||||
private bool _minigameCompleted;
|
||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
||||
private Dictionary<string, DecorationData> _decorationDataDict;
|
||||
private UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<System.Collections.Generic.IList<DecorationData>> _decorationDataHandle;
|
||||
|
||||
// Public property for menu controller
|
||||
public Transform StatueParent => statueParent;
|
||||
@@ -48,19 +58,36 @@ namespace Minigames.StatueDressup.Controllers
|
||||
/// <summary>
|
||||
/// Main initialization after all managers are ready
|
||||
/// </summary>
|
||||
internal override void OnManagedStart()
|
||||
internal override async void OnManagedStart()
|
||||
{
|
||||
base.OnManagedStart();
|
||||
|
||||
Logging.Debug("[StatueDecorationController] Initializing minigame");
|
||||
|
||||
// Load all DecorationData via Addressables first
|
||||
await LoadDecorationDataAsync();
|
||||
|
||||
// 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
|
||||
if (takePhotoButton != null)
|
||||
{
|
||||
// TODO: Remove comment when ready
|
||||
// takePhotoButton.onClick.AddListener(OnTakePhoto);
|
||||
takePhotoButton.onClick.AddListener(OnTakePhoto);
|
||||
}
|
||||
|
||||
// TODO: If ever picture gallery
|
||||
// Setup gallery button (DISABLED - kept for future use)
|
||||
// if (openGalleryButton != null)
|
||||
// {
|
||||
// openGalleryButton.onClick.AddListener(OnOpenGallery);
|
||||
// }
|
||||
|
||||
// Subscribe to menu controller for tracking placed items
|
||||
// Items will manage their own placement via overlap detection
|
||||
if (menuController != null)
|
||||
@@ -73,6 +100,49 @@ namespace Minigames.StatueDressup.Controllers
|
||||
LoadStatueState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load all DecorationData assets via Addressables and build lookup dictionary
|
||||
/// </summary>
|
||||
private async System.Threading.Tasks.Task LoadDecorationDataAsync()
|
||||
{
|
||||
string label = _settings?.DecorationDataLabel;
|
||||
|
||||
if (string.IsNullOrEmpty(label))
|
||||
{
|
||||
Logging.Error("[StatueDecorationController] Decoration data label not set in settings!");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.Debug($"[StatueDecorationController] 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;
|
||||
|
||||
Logging.Debug($"[StatueDecorationController] Loaded {_decorationDataDict.Count} DecorationData assets");
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Register a decoration as placed on statue
|
||||
/// </summary>
|
||||
@@ -116,76 +186,66 @@ namespace Minigames.StatueDressup.Controllers
|
||||
|
||||
Logging.Debug("[StatueDecorationController] Taking photo of statue");
|
||||
|
||||
// Hide UI elements
|
||||
HideUIForPhoto(true);
|
||||
|
||||
// Wait a frame for UI to hide, then capture
|
||||
// CapturePhotoCoroutine handles all UI hiding/showing
|
||||
StartCoroutine(CapturePhotoCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Capture photo after UI is hidden
|
||||
/// Capture photo and save decoration metadata
|
||||
/// </summary>
|
||||
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
|
||||
bool captureComplete = false;
|
||||
Texture2D capturedPhoto = null;
|
||||
|
||||
Utils.StatuePhotoManager.CaptureAreaPhoto(
|
||||
// Capture photo
|
||||
yield return PhotoManager.CaptureAndSaveCoroutine(
|
||||
CaptureType.StatueMinigame,
|
||||
photoArea,
|
||||
(Texture2D texture) =>
|
||||
{
|
||||
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))
|
||||
uiElementsToHideForPhoto,
|
||||
onSuccess: (photoId) =>
|
||||
{
|
||||
Logging.Debug($"[StatueDecorationController] Photo saved: {photoId}");
|
||||
|
||||
// Award cards
|
||||
AwardCards();
|
||||
|
||||
// Update town icon
|
||||
UpdateTownIcon(capturedPhoto);
|
||||
|
||||
// Show completion feedback
|
||||
ShowCompletionFeedback();
|
||||
|
||||
_minigameCompleted = true;
|
||||
}
|
||||
else
|
||||
savedPhotoId = photoId;
|
||||
captureSuccess = true;
|
||||
},
|
||||
onFailure: (error) =>
|
||||
{
|
||||
Logging.Error("[StatueDecorationController] Failed to save photo!");
|
||||
Logging.Error($"[StatueDecorationController] Photo capture failed: {error}");
|
||||
DebugUIMessage.Show("Failed to save photo!", Color.red);
|
||||
}
|
||||
}
|
||||
else
|
||||
captureSuccess = false;
|
||||
},
|
||||
metadata: decorationCount
|
||||
);
|
||||
|
||||
// If photo failed, abort
|
||||
if (!captureSuccess)
|
||||
{
|
||||
Logging.Error("[StatueDecorationController] Failed to capture photo!");
|
||||
DebugUIMessage.Show("Failed to capture photo!", Color.red);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Restore UI
|
||||
HideUIForPhoto(false);
|
||||
// Save decoration metadata
|
||||
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>
|
||||
/// Award Blokkemon cards to player
|
||||
/// </summary>
|
||||
@@ -219,17 +279,60 @@ namespace Minigames.StatueDressup.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide/show UI elements for photo
|
||||
/// Save decoration metadata for reconstruction
|
||||
/// </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 = System.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;
|
||||
|
||||
SpriteRenderer spriteRenderer = decoration.GetComponent<SpriteRenderer>();
|
||||
|
||||
DecorationPlacement placement = new DecorationPlacement
|
||||
{
|
||||
decorationId = decoration.Data.DecorationId,
|
||||
localPosition = decoration.transform.localPosition,
|
||||
localScale = decoration.transform.localScale,
|
||||
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>
|
||||
@@ -252,7 +355,7 @@ namespace Minigames.StatueDressup.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load saved statue decoration state
|
||||
/// Load saved statue decoration state from metadata
|
||||
/// </summary>
|
||||
private void LoadStatueState()
|
||||
{
|
||||
@@ -263,10 +366,83 @@ namespace Minigames.StatueDressup.Controllers
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement load system
|
||||
// Restore decorations from saved state
|
||||
// Check if DecorationData is loaded
|
||||
if (_decorationDataDict == null || _decorationDataDict.Count == 0)
|
||||
{
|
||||
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 dictionary
|
||||
if (!_decorationDataDict.TryGetValue(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);
|
||||
|
||||
// Initialize in "placed" state (skip drag logic)
|
||||
instance.InitializeAsPlaced(
|
||||
decorationData,
|
||||
this,
|
||||
_settings
|
||||
);
|
||||
|
||||
// Apply saved transform
|
||||
instance.transform.localPosition = placement.localPosition;
|
||||
instance.transform.localScale = placement.localScale;
|
||||
instance.transform.localEulerAngles = new Vector3(0, 0, placement.rotation);
|
||||
|
||||
// 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>
|
||||
@@ -276,11 +452,21 @@ namespace Minigames.StatueDressup.Controllers
|
||||
{
|
||||
base.OnManagedDestroy();
|
||||
|
||||
// Cleanup button listener
|
||||
// Release Addressables handle
|
||||
AddressablesUtility.ReleaseHandle(_decorationDataHandle);
|
||||
|
||||
// Cleanup button listeners
|
||||
if (takePhotoButton != null)
|
||||
{
|
||||
takePhotoButton.onClick.RemoveListener(OnTakePhoto);
|
||||
}
|
||||
|
||||
// TODO: If ever picture gallery
|
||||
// Gallery button cleanup (DISABLED - kept for future use)
|
||||
// if (openGalleryButton != null)
|
||||
// {
|
||||
// openGalleryButton.onClick.RemoveListener(OnOpenGallery);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Minigames.StatueDressup.Utils;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Utils;
|
||||
|
||||
namespace Minigames.StatueDressup.Controllers
|
||||
{
|
||||
@@ -18,49 +18,50 @@ namespace Minigames.StatueDressup.Controllers
|
||||
[Header("Gallery UI")]
|
||||
[SerializeField] private Transform gridContainer;
|
||||
[SerializeField] private PhotoGridItem gridItemPrefab;
|
||||
[SerializeField] private ScrollRect scrollRect;
|
||||
|
||||
[Header("Enlarged View")]
|
||||
[SerializeField] private GameObject enlargedViewPanel;
|
||||
[SerializeField] private Image enlargedPhotoImage;
|
||||
[SerializeField] private Button closeEnlargedButton;
|
||||
[SerializeField] private Button deletePhotoButton;
|
||||
[SerializeField] private Text photoInfoText;
|
||||
[SerializeField] private Transform enlargedContainer; // Container for enlarged preview (top layer)
|
||||
[SerializeField] private GameObject backdrop; // Dark backdrop for enlarged view
|
||||
[SerializeField] private GameObject enlargedPreviewPrefab; // Prefab for enlarged preview (same as grid item)
|
||||
|
||||
[Header("Pagination")]
|
||||
[SerializeField] private Button loadMoreButton;
|
||||
[SerializeField] private Text statusText;
|
||||
[SerializeField] private Button previousPageButton;
|
||||
[SerializeField] private Button nextPageButton;
|
||||
[SerializeField] private Text pageStatusText;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private int itemsPerPage = 20;
|
||||
[SerializeField] private int thumbnailSize = 256;
|
||||
[SerializeField] private int maxCachedThumbnails = 50; // Keep recent thumbnails in memory
|
||||
|
||||
private int _currentPage = 0;
|
||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
||||
private int _currentPage;
|
||||
private List<string> _allPhotoIds = new List<string>();
|
||||
private Dictionary<string, PhotoGridItem> _activeGridItems = new Dictionary<string, PhotoGridItem>();
|
||||
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 string _currentEnlargedPhotoId = null;
|
||||
private Texture2D _currentEnlargedTexture = null;
|
||||
private bool _isLoadingPage;
|
||||
private PhotoEnlargeController _enlargeController;
|
||||
|
||||
internal override void OnManagedStart()
|
||||
{
|
||||
base.OnManagedStart();
|
||||
|
||||
// Setup buttons
|
||||
if (closeEnlargedButton != null)
|
||||
closeEnlargedButton.onClick.AddListener(CloseEnlargedView);
|
||||
// Get settings
|
||||
_settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
|
||||
|
||||
if (deletePhotoButton != null)
|
||||
deletePhotoButton.onClick.AddListener(DeleteCurrentPhoto);
|
||||
// Initialize enlarge controller
|
||||
_enlargeController = new PhotoEnlargeController(backdrop, enlargedContainer, _settings?.GalleryAnimationDuration ?? 0.3f);
|
||||
|
||||
if (loadMoreButton != null)
|
||||
loadMoreButton.onClick.AddListener(LoadNextPage);
|
||||
// Setup page navigation buttons
|
||||
if (previousPageButton != null)
|
||||
previousPageButton.onClick.AddListener(OnPreviousPageClicked);
|
||||
|
||||
// Hide enlarged view initially
|
||||
if (enlargedViewPanel != null)
|
||||
enlargedViewPanel.SetActive(false);
|
||||
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
|
||||
RefreshGallery();
|
||||
@@ -72,35 +73,35 @@ namespace Minigames.StatueDressup.Controllers
|
||||
public void RefreshGallery()
|
||||
{
|
||||
// Clear existing items
|
||||
ClearGallery();
|
||||
ClearGrid();
|
||||
|
||||
// Get all photo IDs
|
||||
_allPhotoIds = StatuePhotoManager.GetAllPhotoIds();
|
||||
_allPhotoIds = PhotoManager.GetAllPhotoIds(CaptureType.StatueMinigame);
|
||||
_currentPage = 0;
|
||||
|
||||
Logging.Debug($"[StatuePhotoGalleryController] Gallery refreshed: {_allPhotoIds.Count} photos");
|
||||
|
||||
// Load first page
|
||||
LoadNextPage();
|
||||
// Display first page
|
||||
DisplayCurrentPage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load next page of photos
|
||||
/// Display the current page of photos (clears grid and shows only current page)
|
||||
/// </summary>
|
||||
private void LoadNextPage()
|
||||
private void DisplayCurrentPage()
|
||||
{
|
||||
List<string> pagePhotoIds = StatuePhotoManager.GetPhotoIdsPage(_currentPage, itemsPerPage);
|
||||
if (_isLoadingPage) return;
|
||||
|
||||
if (pagePhotoIds.Count == 0)
|
||||
{
|
||||
if (loadMoreButton != null)
|
||||
loadMoreButton.gameObject.SetActive(false);
|
||||
|
||||
UpdateStatusText($"All photos loaded ({_allPhotoIds.Count} total)");
|
||||
return;
|
||||
}
|
||||
_isLoadingPage = true;
|
||||
|
||||
Logging.Debug($"[StatuePhotoGalleryController] Loading page {_currentPage}: {pagePhotoIds.Count} items");
|
||||
// Clear current grid
|
||||
ClearGrid();
|
||||
|
||||
// Get photos for current page
|
||||
int itemsPerPage = _settings?.GalleryItemsPerPage ?? 20;
|
||||
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
|
||||
foreach (string photoId in pagePhotoIds)
|
||||
@@ -108,14 +109,64 @@ namespace Minigames.StatueDressup.Controllers
|
||||
SpawnGridItem(photoId);
|
||||
}
|
||||
|
||||
_currentPage++;
|
||||
// Update button states
|
||||
UpdatePageButtons();
|
||||
|
||||
// Update UI state
|
||||
bool hasMore = _currentPage * itemsPerPage < _allPhotoIds.Count;
|
||||
if (loadMoreButton != null)
|
||||
loadMoreButton.gameObject.SetActive(hasMore);
|
||||
// Update status text
|
||||
int totalPages = Mathf.CeilToInt((float)_allPhotoIds.Count / itemsPerPage);
|
||||
UpdateStatusText($"Page {_currentPage + 1}/{totalPages} ({_allPhotoIds.Count} photos)");
|
||||
|
||||
UpdateStatusText($"Showing {_activeGridItems.Count} of {_allPhotoIds.Count} photos");
|
||||
_isLoadingPage = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update page navigation button states
|
||||
/// </summary>
|
||||
private void UpdatePageButtons()
|
||||
{
|
||||
int itemsPerPage = _settings?.GalleryItemsPerPage ?? 20;
|
||||
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 = _settings?.GalleryItemsPerPage ?? 20;
|
||||
int totalPages = Mathf.CeilToInt((float)_allPhotoIds.Count / itemsPerPage);
|
||||
|
||||
if (_currentPage < totalPages - 1)
|
||||
{
|
||||
_currentPage++;
|
||||
DisplayCurrentPage();
|
||||
Logging.Debug($"[StatuePhotoGalleryController] Navigated to next page: {_currentPage}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,7 +205,7 @@ namespace Minigames.StatueDressup.Controllers
|
||||
yield return null;
|
||||
|
||||
// Load full photo
|
||||
Texture2D fullPhoto = StatuePhotoManager.LoadPhoto(photoId);
|
||||
Texture2D fullPhoto = PhotoManager.LoadPhoto(CaptureType.StatueMinigame, photoId);
|
||||
|
||||
if (fullPhoto == null)
|
||||
{
|
||||
@@ -163,7 +214,8 @@ namespace Minigames.StatueDressup.Controllers
|
||||
}
|
||||
|
||||
// Create thumbnail
|
||||
Texture2D thumbnail = StatuePhotoManager.CreateThumbnail(fullPhoto, thumbnailSize);
|
||||
int thumbSize = _settings?.GalleryThumbnailSize ?? 256;
|
||||
Texture2D thumbnail = PhotoManager.CreateThumbnail(fullPhoto, thumbSize);
|
||||
|
||||
// Destroy full photo immediately (we only need thumbnail)
|
||||
Destroy(fullPhoto);
|
||||
@@ -188,7 +240,8 @@ namespace Minigames.StatueDressup.Controllers
|
||||
_thumbnailCacheOrder.Enqueue(photoId);
|
||||
|
||||
// Evict oldest if over limit
|
||||
while (_thumbnailCache.Count > maxCachedThumbnails && _thumbnailCacheOrder.Count > 0)
|
||||
int maxCached = _settings?.GalleryMaxCachedThumbnails ?? 50;
|
||||
while (_thumbnailCache.Count > maxCached && _thumbnailCacheOrder.Count > 0)
|
||||
{
|
||||
string oldestId = _thumbnailCacheOrder.Dequeue();
|
||||
|
||||
@@ -202,154 +255,90 @@ namespace Minigames.StatueDressup.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show enlarged view of a photo (called by PhotoGridItem)
|
||||
/// Enlarge a photo (called by PhotoGridItem)
|
||||
/// </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;
|
||||
}
|
||||
|
||||
Logging.Debug($"[StatuePhotoGalleryController] Showing enlarged view: {photoId}");
|
||||
|
||||
// Clear previous enlarged texture
|
||||
if (_currentEnlargedTexture != null)
|
||||
// If already enlarged, shrink it
|
||||
if (_enlargeController.IsPhotoEnlarged)
|
||||
{
|
||||
Destroy(_currentEnlargedTexture);
|
||||
_currentEnlargedTexture = null;
|
||||
}
|
||||
|
||||
// Load full-size photo
|
||||
_currentEnlargedTexture = StatuePhotoManager.LoadPhoto(photoId);
|
||||
|
||||
if (_currentEnlargedTexture == null)
|
||||
{
|
||||
Logging.Error($"[StatuePhotoGalleryController] Failed to load enlarged photo: {photoId}");
|
||||
_enlargeController.ShrinkPhoto();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create sprite from texture
|
||||
Sprite enlargedSprite = Sprite.Create(
|
||||
_currentEnlargedTexture,
|
||||
new Rect(0, 0, _currentEnlargedTexture.width, _currentEnlargedTexture.height),
|
||||
new Vector2(0.5f, 0.5f)
|
||||
);
|
||||
Logging.Debug($"[StatuePhotoGalleryController] Enlarging photo: {photoId}");
|
||||
|
||||
enlargedPhotoImage.sprite = enlargedSprite;
|
||||
_currentEnlargedPhotoId = photoId;
|
||||
float enlargedScale = _settings?.GalleryEnlargedScale ?? 2.5f;
|
||||
|
||||
// Update photo info
|
||||
UpdatePhotoInfo(photoId);
|
||||
|
||||
// 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)
|
||||
// Check cache first
|
||||
if (_fullPhotoCache.TryGetValue(photoId, out Texture2D fullPhoto))
|
||||
{
|
||||
Destroy(_currentEnlargedTexture);
|
||||
_currentEnlargedTexture = null;
|
||||
}
|
||||
|
||||
_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";
|
||||
// Use cached photo
|
||||
_enlargeController.EnlargePhoto(gridItem, enlargedPreviewPrefab != null ? enlargedPreviewPrefab : gridItem.gameObject, fullPhoto, enlargedScale);
|
||||
}
|
||||
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>
|
||||
/// Update status text
|
||||
/// </summary>
|
||||
private void UpdateStatusText(string message)
|
||||
{
|
||||
if (statusText != null)
|
||||
statusText.text = message;
|
||||
if (pageStatusText != null)
|
||||
pageStatusText.text = message;
|
||||
|
||||
Logging.Debug($"[StatuePhotoGalleryController] Status: {message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all grid items and cached data
|
||||
/// Clear only the grid items (used when switching pages)
|
||||
/// </summary>
|
||||
private void ClearGallery()
|
||||
private void ClearGrid()
|
||||
{
|
||||
// Destroy grid items
|
||||
foreach (var gridItem in _activeGridItems.Values)
|
||||
@@ -359,6 +348,16 @@ namespace Minigames.StatueDressup.Controllers
|
||||
}
|
||||
_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
|
||||
foreach (var thumbnail in _thumbnailCache.Values)
|
||||
{
|
||||
@@ -368,7 +367,7 @@ namespace Minigames.StatueDressup.Controllers
|
||||
_thumbnailCache.Clear();
|
||||
_thumbnailCacheOrder.Clear();
|
||||
|
||||
Logging.Debug("[StatuePhotoGalleryController] Gallery cleared");
|
||||
Logging.Debug("[StatuePhotoGalleryController] Gallery fully cleared");
|
||||
}
|
||||
|
||||
internal override void OnManagedDestroy()
|
||||
@@ -377,17 +376,14 @@ namespace Minigames.StatueDressup.Controllers
|
||||
|
||||
// Cleanup
|
||||
ClearGallery();
|
||||
CloseEnlargedView();
|
||||
CleanupGallery();
|
||||
|
||||
// Unsubscribe buttons
|
||||
if (closeEnlargedButton != null)
|
||||
closeEnlargedButton.onClick.RemoveListener(CloseEnlargedView);
|
||||
if (previousPageButton != null)
|
||||
previousPageButton.onClick.RemoveListener(OnPreviousPageClicked);
|
||||
|
||||
if (deletePhotoButton != null)
|
||||
deletePhotoButton.onClick.RemoveListener(DeleteCurrentPhoto);
|
||||
|
||||
if (loadMoreButton != null)
|
||||
loadMoreButton.onClick.RemoveListener(LoadNextPage);
|
||||
if (nextPageButton != null)
|
||||
nextPageButton.onClick.RemoveListener(OnNextPageClicked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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 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,305 @@
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Minigames.StatueDressup.Data;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
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>
|
||||
[RequireComponent(typeof(SpriteRenderer))]
|
||||
public class StatueDecorationLoader : ManagedBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
[Tooltip("Root GameObject for spawning decorations (clears only this, not statue children)")]
|
||||
[SerializeField] private Transform decorationRoot;
|
||||
|
||||
[Tooltip("Load specific photo ID, or leave empty to load latest")]
|
||||
[SerializeField] private string specificPhotoId = "";
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugInfo = true;
|
||||
|
||||
private SpriteRenderer _statueSpriteRenderer;
|
||||
private Dictionary<string, DecorationData> _decorationDataDict;
|
||||
private AsyncOperationHandle<IList<DecorationData>> _decorationDataHandle;
|
||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
||||
|
||||
internal override void OnManagedStart()
|
||||
{
|
||||
base.OnManagedStart();
|
||||
|
||||
_statueSpriteRenderer = GetComponent<SpriteRenderer>();
|
||||
|
||||
// Get settings
|
||||
_settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
|
||||
|
||||
// Ensure decoration root exists
|
||||
if (decorationRoot == null)
|
||||
{
|
||||
GameObject rootObj = new GameObject("DecorationRoot");
|
||||
rootObj.transform.SetParent(transform, false);
|
||||
decorationRoot = rootObj.transform;
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Logging.Debug("[StatueDecorationLoader] Created decoration root automatically");
|
||||
}
|
||||
}
|
||||
|
||||
// Start async loading via coroutine wrapper
|
||||
StartCoroutine(LoadAndDisplayDecorationsCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine wrapper for async loading and display
|
||||
/// </summary>
|
||||
private System.Collections.IEnumerator LoadAndDisplayDecorationsCoroutine()
|
||||
{
|
||||
// Convert async Task to coroutine-compatible operation
|
||||
var loadTask = LoadDecorationDataAsync();
|
||||
|
||||
// Wait for async operation to complete
|
||||
while (!loadTask.IsCompleted)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Check for exceptions
|
||||
if (loadTask.IsFaulted)
|
||||
{
|
||||
Logging.Error($"[StatueDecorationLoader] Failed to load decoration data: {loadTask.Exception?.GetBaseException().Message}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Load and display decorations
|
||||
LoadAndDisplayDecorations();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load all DecorationData assets via Addressables and build lookup dictionary
|
||||
/// </summary>
|
||||
private async System.Threading.Tasks.Task LoadDecorationDataAsync()
|
||||
{
|
||||
string label = _settings?.DecorationDataLabel;
|
||||
|
||||
if (string.IsNullOrEmpty(label))
|
||||
{
|
||||
Logging.Error("[StatueDecorationLoader] Decoration data label not set in settings!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Logging.Debug($"[StatueDecorationLoader] 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, // Key selector: use DecorationId as key
|
||||
progress => { /* Optional: could show loading bar */ }
|
||||
);
|
||||
|
||||
_decorationDataDict = result.dictionary;
|
||||
_decorationDataHandle = result.handle;
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Logging.Debug($"[StatueDecorationLoader] Loaded {_decorationDataDict.Count} DecorationData assets");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load decoration metadata and spawn decorations
|
||||
/// </summary>
|
||||
public void LoadAndDisplayDecorations()
|
||||
{
|
||||
// Check if DecorationData is loaded
|
||||
if (_decorationDataDict == null || _decorationDataDict.Count == 0)
|
||||
{
|
||||
Logging.Warning("[StatueDecorationLoader] DecorationData not loaded yet. 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);
|
||||
|
||||
// Spawn each decoration synchronously (data already loaded)
|
||||
int successCount = 0;
|
||||
foreach (var placement in data.placements)
|
||||
{
|
||||
if (SpawnDecoration(placement, conversionFactor))
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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)");
|
||||
}
|
||||
return 1f;
|
||||
}
|
||||
|
||||
// Source was UI RectTransform (pixels), target is WorldSpace (units)
|
||||
// Need to convert from source statue pixel size to target statue world size
|
||||
|
||||
// Get target statue size (world units)
|
||||
Vector2 targetStatueSize = Vector2.one;
|
||||
if (_statueSpriteRenderer != null && _statueSpriteRenderer.sprite != null)
|
||||
{
|
||||
targetStatueSize = _statueSpriteRenderer.sprite.bounds.size;
|
||||
}
|
||||
|
||||
// Calculate conversion factor (target size / source size)
|
||||
float conversionX = targetStatueSize.x / data.sourceStatueSize.x;
|
||||
float conversionY = targetStatueSize.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}) → World({targetStatueSize}) = factor {conversionFactor:F3}");
|
||||
}
|
||||
|
||||
return conversionFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a single decoration from placement data
|
||||
/// Looks up DecorationData from pre-loaded dictionary and applies coordinate conversion
|
||||
/// </summary>
|
||||
private bool SpawnDecoration(DecorationPlacement placement, float conversionFactor)
|
||||
{
|
||||
// Look up DecorationData from dictionary
|
||||
if (!_decorationDataDict.TryGetValue(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
|
||||
GameObject decorationObj = new GameObject($"Decoration_{placement.decorationId}");
|
||||
decorationObj.transform.SetParent(decorationRoot, false); // false = keep local position
|
||||
|
||||
// Add SpriteRenderer
|
||||
SpriteRenderer spriteRenderer = decorationObj.AddComponent<SpriteRenderer>();
|
||||
spriteRenderer.sprite = decorationSprite;
|
||||
spriteRenderer.sortingLayerName = "Foreground";
|
||||
spriteRenderer.sortingOrder = _statueSpriteRenderer.sortingOrder + placement.sortingOrder;
|
||||
|
||||
// Apply transform with coordinate conversion
|
||||
Vector3 convertedPosition = placement.localPosition * conversionFactor;
|
||||
decorationObj.transform.localPosition = convertedPosition;
|
||||
decorationObj.transform.localScale = placement.localScale;
|
||||
decorationObj.transform.localEulerAngles = new Vector3(0, 0, placement.rotation);
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Logging.Debug($"[StatueDecorationLoader] Spawned: {placement.decorationId} at {convertedPosition} (original: {placement.localPosition}, factor: {conversionFactor:F3})");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all existing decorations from decorationRoot
|
||||
/// </summary>
|
||||
public void ClearDecorations()
|
||||
{
|
||||
if (decorationRoot == null) return;
|
||||
|
||||
// Remove all children from decoration root only
|
||||
for (int i = decorationRoot.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Destroy(decorationRoot.GetChild(i).gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyImmediate(decorationRoot.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup - release Addressables handle
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Release DecorationData handle
|
||||
AddressablesUtility.ReleaseHandle(_decorationDataHandle);
|
||||
}
|
||||
|
||||
/// <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
|
||||
@@ -1,7 +1,6 @@
|
||||
using Core;
|
||||
using Minigames.StatueDressup.Controllers;
|
||||
using Minigames.StatueDressup.Data;
|
||||
using Minigames.StatueDressup.Utils;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
@@ -77,6 +76,40 @@ namespace Minigames.StatueDressup.DragDrop
|
||||
Logging.Debug($"[DecorationDraggableInstance] Initialized: {data?.DecorationName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize as already placed decoration (from saved state)
|
||||
/// Skips drag logic and sets up as if already placed on statue
|
||||
/// </summary>
|
||||
public void InitializeAsPlaced(DecorationData data, StatueDecorationController controller,
|
||||
AppleHills.Core.Settings.IStatueDressupSettings settings)
|
||||
{
|
||||
_decorationData = data;
|
||||
_controller = controller;
|
||||
_settings = settings;
|
||||
_isPlacedOnStatue = true;
|
||||
_isDragging = false;
|
||||
|
||||
// Set sprite
|
||||
if (decorationImage != null && data != null && data.DecorationSprite != null)
|
||||
{
|
||||
decorationImage.sprite = data.DecorationSprite;
|
||||
}
|
||||
|
||||
// Set authored size
|
||||
if (_rectTransform != null && data != null)
|
||||
{
|
||||
_rectTransform.sizeDelta = data.AuthoredSize;
|
||||
}
|
||||
|
||||
// Make non-interactive for placed state (can be made interactive later if needed)
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationDraggableInstance] Initialized as placed: {data?.DecorationName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start dragging from icon
|
||||
/// </summary>
|
||||
|
||||
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
|
||||
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
|
||||
@@ -16,7 +16,7 @@ MonoBehaviour:
|
||||
pauseTimeOnPauseGame: 0
|
||||
useSaveLoadSystem: 1
|
||||
autoClearSaves: 0
|
||||
dontSaveOnQuit: 1
|
||||
dontSaveOnQuit: 0
|
||||
bootstrapLogVerbosity: 0
|
||||
settingsLogVerbosity: 0
|
||||
gameManagerLogVerbosity: 0
|
||||
|
||||
Reference in New Issue
Block a user