diff --git a/Assets/Scenes/Levels/AppleHillsOverworld.unity b/Assets/Scenes/Levels/AppleHillsOverworld.unity index 3e383fa7..d1a57764 100644 --- a/Assets/Scenes/Levels/AppleHillsOverworld.unity +++ b/Assets/Scenes/Levels/AppleHillsOverworld.unity @@ -3189,9 +3189,6 @@ PrefabInstance: - targetCorrespondingSourceObject: {fileID: 7404622075362872657, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3} insertIndex: -1 addedObject: {fileID: 1460027150} - - targetCorrespondingSourceObject: {fileID: 7404622075362872657, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3} - insertIndex: -1 - addedObject: {fileID: 1460027149} m_SourcePrefab: {fileID: 100100000, guid: f44866deaba5f5c4a90f0330dd9957f0, type: 3} --- !u!4 &1460027142 stripped Transform: @@ -3214,18 +3211,6 @@ MonoBehaviour: specificPhotoId: applyPivotOffset: 1 showDebugInfo: 1 ---- !u!114 &1460027149 -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: acf5624b19664ce5900f1a7c1328edbc, type: 3} - m_Name: - m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Controllers.StatueDressupSettings --- !u!114 &1460027150 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Scenes/MiniGames/StatueDecoration.unity b/Assets/Scenes/MiniGames/StatueDecoration.unity index 4288846d..645acb64 100644 --- a/Assets/Scenes/MiniGames/StatueDecoration.unity +++ b/Assets/Scenes/MiniGames/StatueDecoration.unity @@ -2086,8 +2086,9 @@ GameObject: - component: {fileID: 1181815686} - component: {fileID: 1181815685} - component: {fileID: 1181815684} + - component: {fileID: 1181815687} m_Layer: 0 - m_Name: SFXManager + m_Name: DecorationEventManager m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -2221,6 +2222,18 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1181815687 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1181815683} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5c9796e0044a4fcd95b02a19925a6b2b, type: 3} + m_Name: + m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Events.DecorationEventsManager --- !u!1 &1217454514 GameObject: m_ObjectHideFlags: 0 @@ -2864,7 +2877,6 @@ GameObject: m_Component: - component: {fileID: 1647993459} - component: {fileID: 1647993458} - - component: {fileID: 1647993461} - component: {fileID: 1647993460} m_Layer: 0 m_Name: StatueDecorationController @@ -2922,18 +2934,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: afa56ec5b1f84b32ba014a91d9d9a0a0, type: 3} m_Name: m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Controllers.DecorationDataManager ---- !u!114 &1647993461 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1647993457} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: acf5624b19664ce5900f1a7c1328edbc, type: 3} - m_Name: - m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Controllers.StatueDressupSettings --- !u!1 &1685271989 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationDataManager.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationDataManager.cs index a8c95925..40f3f083 100644 --- a/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationDataManager.cs +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationDataManager.cs @@ -2,27 +2,73 @@ using Core; using Core.Lifecycle; using Minigames.StatueDressup.Data; +using UnityEngine; using UnityEngine.ResourceManagement.AsyncOperations; using Utils; - namespace Minigames.StatueDressup.Controllers { /// - /// Singleton manager for decoration data loading and caching. - /// Loads all DecorationData assets once via Addressables and provides shared access. + /// Singleton manager for statue dressup data loading and caching. + /// Loads settings first, then decoration data via Addressables and provides shared access. /// Used by both minigame and town map to avoid duplicate loading. /// - public class DecorationDataManager : ManagedBehaviour + public class DecorationDataManager : ManagedBehaviour, IReadyNotifier { public static DecorationDataManager Instance { get; private set; } + // Static callback queue for when instance doesn't exist yet + private static readonly List PendingCallbacks = new List(); + private AppleHills.Core.Settings.IStatueDressupSettings settings; private Dictionary decorationDataDict; private AsyncOperationHandle> decorationDataHandle; private bool isLoaded; /// - /// Check if data is loaded and ready + /// Get the settings instance /// + public AppleHills.Core.Settings.IStatueDressupSettings Settings => settings; + + /// + /// Check if data is loaded and ready (implements IReadyNotifier) + /// + public bool IsReady => isLoaded; + + /// + /// Event invoked when data is loaded and ready (implements IReadyNotifier) + /// + public event System.Action OnReady; + + /// + /// Static method to register callbacks that will execute when manager is ready. + /// Can be called before instance exists - callbacks will be queued and executed when ready. + /// Handles null instance gracefully by queuing callbacks until instance is created and ready. + /// + public static void WhenReady(System.Action callback) + { + if (callback == null) return; + + // If instance exists and is ready, execute immediately + if (Instance != null && Instance.IsReady) + { + callback.Invoke(); + return; + } + + // If instance exists but not ready, use instance method + if (Instance != null) + { + (Instance as IReadyNotifier).WhenReady(callback); + return; + } + + // Instance doesn't exist yet - queue callback + PendingCallbacks.Add(callback); + } + + /// + /// Legacy property - use IsReady instead + /// + [System.Obsolete("Use IsReady instead")] public bool IsLoaded => isLoaded; /// @@ -32,8 +78,6 @@ namespace Minigames.StatueDressup.Controllers internal override void OnManagedAwake() { - base.OnManagedAwake(); - // Singleton pattern if (Instance != null && Instance != this) { @@ -44,12 +88,32 @@ namespace Minigames.StatueDressup.Controllers Instance = this; } - - internal override async void OnManagedStart() + + internal override void OnManagedStart() { - base.OnManagedStart(); + StartCoroutine(LoadSettingsAndData()); + } + + /// + /// Load settings first, then decoration data + /// + private System.Collections.IEnumerator LoadSettingsAndData() + { + Logging.Debug("[DecorationDataManager] Waiting for GameManager to be accessible..."); - await LoadAllDecorationData(); + // Wait until GameManager is accessible and settings can be retrieved + settings = GameManager.GetSettingsObject(); + + Logging.Debug("[DecorationDataManager] Settings loaded successfully"); + + // Now load decoration data + var loadTask = LoadAllDecorationData(); + yield return new WaitUntil(() => loadTask.IsCompleted); + + if (loadTask.IsFaulted) + { + Logging.Error($"[DecorationDataManager] Failed to load decoration data: {loadTask.Exception}"); + } } /// @@ -63,7 +127,6 @@ namespace Minigames.StatueDressup.Controllers return; } - var settings = StatueDressupSettings.Instance?.Settings; string label = settings?.DecorationDataLabel; if (string.IsNullOrEmpty(label)) @@ -85,6 +148,20 @@ namespace Minigames.StatueDressup.Controllers isLoaded = true; Logging.Debug($"[DecorationDataManager] Loaded {decorationDataDict.Count} DecorationData assets"); + + // Subscribe all pending callbacks to OnReady event before invoking + if (PendingCallbacks.Count > 0) + { + Logging.Debug($"[DecorationDataManager] Subscribing {PendingCallbacks.Count} pending callbacks to OnReady"); + foreach (var callback in PendingCallbacks) + { + OnReady += callback; + } + PendingCallbacks.Clear(); + } + + // Mark as ready and notify listeners (including pending callbacks) + OnReady?.Invoke(); } /// @@ -122,10 +199,8 @@ namespace Minigames.StatueDressup.Controllers return decorationDataDict.TryGetValue(decorationId, out data); } - internal override void OnManagedDestroy() + private void OnDestroy() { - base.OnManagedDestroy(); - // Release Addressables handle AddressablesUtility.ReleaseHandle(decorationDataHandle); diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationMenuController.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationMenuController.cs index d0d14ed0..cca44a65 100644 --- a/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationMenuController.cs +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/DecorationMenuController.cs @@ -35,9 +35,6 @@ namespace Minigames.StatueDressup.Controllers public int CurrentPage => currentPage; public int TotalPages => totalPages; - /// - /// Early initialization - singleton setup - /// internal override void OnManagedAwake() { base.OnManagedAwake(); @@ -66,7 +63,19 @@ namespace Minigames.StatueDressup.Controllers { base.OnManagedStart(); - var settings = StatueDressupSettings.Instance?.Settings; + // Wait for data manager to be ready before initializing + DecorationDataManager.WhenReady(() => + { + InitializeMenu(); + }); + } + + /// + /// Initialize menu once data manager is ready + /// + private void InitializeMenu() + { + var settings = DecorationDataManager.Instance?.Settings; if (settings == null) { @@ -75,7 +84,7 @@ namespace Minigames.StatueDressup.Controllers } var allDecorations = settings.AllDecorations; - int itemsPerPage = settings.ItemsPerPage; + int itemsPerPage = settings?.ItemsPerPage ?? StatueDressupConstants.DefaultMenuItemsPerPage; Logging.Debug($"[DecorationMenuController] Initializing with {allDecorations?.Count ?? 0} decorations"); @@ -114,7 +123,7 @@ namespace Minigames.StatueDressup.Controllers /// private void PopulateCurrentPage() { - var settings = StatueDressupSettings.Instance?.Settings; + var settings = DecorationDataManager.Instance?.Settings; if (settings == null) return; var allDecorations = settings.AllDecorations; @@ -196,7 +205,7 @@ namespace Minigames.StatueDressup.Controllers outlineRect, StatueDecorationController.Instance.StatueParent, StatueDecorationController.Instance, - StatueDressupSettings.Instance.Settings, + DecorationDataManager.Instance.Settings, OnDraggableFinished, ShowStatueOutline, HideStatueOutline diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs index 6b0feae3..1070f2e9 100644 --- a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Core; using Core.Lifecycle; @@ -44,9 +44,6 @@ namespace Minigames.StatueDressup.Controllers public Transform StatueParent => statueParent; public RectTransform StatueArea => statueArea; - /// - /// Early initialization - singleton setup - /// internal override void OnManagedAwake() { base.OnManagedAwake(); @@ -71,30 +68,12 @@ namespace Minigames.StatueDressup.Controllers Logging.Debug("[StatueDecorationController] Initializing minigame"); - // DecorationDataManager exists (initialized in OnManagedAwake) but data loads async in OnManagedStart - // Wait for data to finish loading before initializing - if (!DecorationDataManager.Instance.IsLoaded) + // Wait for decoration data to be ready before initializing + DecorationDataManager.WhenReady(() => { - Logging.Debug("[StatueDecorationController] Waiting for DecorationData to load..."); - StartCoroutine(WaitForDataAndInitialize()); - return; - } - - InitializeMinigame(); - } - - /// - /// Wait for data manager to finish loading data before initializing - /// - private System.Collections.IEnumerator WaitForDataAndInitialize() - { - while (!DecorationDataManager.Instance.IsLoaded) - { - yield return null; - } - - Logging.Debug("[StatueDecorationController] DecorationData loaded, initializing minigame"); - InitializeMinigame(); + Logging.Debug("[StatueDecorationController] DecorationData ready, initializing minigame"); + InitializeMinigame(); + }); } /// @@ -342,7 +321,7 @@ namespace Minigames.StatueDressup.Controllers /// private void SaveStatueState() { - var settings = StatueDressupSettings.Instance?.Settings; + var settings = DecorationDataManager.Instance?.Settings; // Check if persistence is enabled if (settings == null || !settings.EnableStatePersistence) @@ -363,7 +342,7 @@ namespace Minigames.StatueDressup.Controllers /// private void LoadStatueState() { - var settings = StatueDressupSettings.Instance?.Settings; + var settings = DecorationDataManager.Instance?.Settings; // Check if persistence is enabled if (settings == null || !settings.EnableStatePersistence) @@ -435,7 +414,7 @@ namespace Minigames.StatueDressup.Controllers var context = DecorationDragContext.CreateForPlaced( decorationData, this, - StatueDressupSettings.Instance.Settings, + DecorationDataManager.Instance.Settings, statueArea, canvasParent, showOutlineCallback, @@ -512,3 +491,4 @@ namespace Minigames.StatueDressup.Controllers } } + diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDressupSettings.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDressupSettings.cs deleted file mode 100644 index c6f81c28..00000000 --- a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDressupSettings.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Core; -using Core.Lifecycle; - -namespace Minigames.StatueDressup.Controllers -{ - /// - /// Singleton manager for StatueDressup settings access. - /// Loads settings once and provides global access point. - /// - public class StatueDressupSettings : ManagedBehaviour - { - public static StatueDressupSettings Instance { get; private set; } - - private AppleHills.Core.Settings.IStatueDressupSettings settings; - - /// - /// Get the settings instance - /// - public AppleHills.Core.Settings.IStatueDressupSettings Settings => settings; - - internal override void OnManagedAwake() - { - base.OnManagedAwake(); - - // Singleton pattern - if (Instance != null && Instance != this) - { - Logging.Warning("[StatueDressupSettings] Duplicate instance detected. Destroying duplicate."); - Destroy(gameObject); - return; - } - - Instance = this; - - // Load settings once - settings = GameManager.GetSettingsObject(); - - if (settings == null) - { - Logging.Error("[StatueDressupSettings] Failed to load StatueDressupSettings!"); - } - else - { - Logging.Debug("[StatueDressupSettings] Settings loaded successfully"); - } - } - - internal override void OnManagedDestroy() - { - base.OnManagedDestroy(); - - if (Instance == this) - { - Instance = null; - } - } - } -} - diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDressupSettings.cs.meta b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDressupSettings.cs.meta deleted file mode 100644 index 5aff1b89..00000000 --- a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDressupSettings.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: acf5624b19664ce5900f1a7c1328edbc -timeCreated: 1764240158 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs index 02b5a4d1..7b357f62 100644 --- a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using Core; using Core.Lifecycle; @@ -40,9 +40,9 @@ namespace Minigames.StatueDressup.Controllers private bool isLoadingPage; private PhotoEnlargeController enlargeController; - internal override void OnManagedStart() + internal override void OnManagedAwake() { - base.OnManagedStart(); + base.OnManagedAwake(); // Singleton pattern if (Instance != null && Instance != this) @@ -53,11 +53,29 @@ namespace Minigames.StatueDressup.Controllers } Instance = this; + } + + internal override void OnManagedStart() + { + base.OnManagedStart(); - var settings = StatueDressupSettings.Instance?.Settings; + // Wait for data manager to be ready before initializing + DecorationDataManager.WhenReady(() => + { + InitializeGallery(); + }); + } + + /// + /// Initialize gallery once data manager is ready + /// + private void InitializeGallery() + { + var settings = DecorationDataManager.Instance?.Settings; // Initialize enlarge controller - enlargeController = new PhotoEnlargeController(backdrop, enlargedContainer, settings?.GalleryAnimationDuration ?? 0.3f); + enlargeController = new PhotoEnlargeController(backdrop, enlargedContainer, + settings?.GalleryAnimationDuration ?? StatueDressupConstants.DefaultAnimationDuration); // Setup page navigation buttons if (previousPageButton != null) @@ -108,7 +126,7 @@ namespace Minigames.StatueDressup.Controllers ClearGrid(); // Get photos for current page - int itemsPerPage = StatueDressupSettings.Instance?.Settings?.GalleryItemsPerPage ?? 20; + int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage; List pagePhotoIds = PhotoManager.GetPhotoIdsPage(CaptureType.StatueMinigame, currentPage, itemsPerPage); Logging.Debug($"[StatuePhotoGalleryController] Displaying page {currentPage + 1}: {pagePhotoIds.Count} items"); @@ -134,7 +152,7 @@ namespace Minigames.StatueDressup.Controllers /// private void UpdatePageButtons() { - int itemsPerPage = StatueDressupSettings.Instance?.Settings?.GalleryItemsPerPage ?? 20; + int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage; int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage); // Enable/disable previous button @@ -168,7 +186,7 @@ namespace Minigames.StatueDressup.Controllers /// private void OnNextPageClicked() { - int itemsPerPage = StatueDressupSettings.Instance?.Settings?.GalleryItemsPerPage ?? 20; + int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage; int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage); if (currentPage < totalPages - 1) @@ -224,7 +242,7 @@ namespace Minigames.StatueDressup.Controllers } // Create thumbnail - int thumbSize = StatueDressupSettings.Instance?.Settings?.GalleryThumbnailSize ?? 256; + int thumbSize = DecorationDataManager.Instance?.Settings?.GalleryThumbnailSize ?? StatueDressupConstants.DefaultThumbnailSize; Texture2D thumbnail = PhotoManager.CreateThumbnail(fullPhoto, thumbSize); // Destroy full photo immediately (we only need thumbnail) @@ -250,7 +268,7 @@ namespace Minigames.StatueDressup.Controllers thumbnailCacheOrder.Enqueue(photoId); // Evict oldest if over limit - int maxCached = StatueDressupSettings.Instance?.Settings?.GalleryMaxCachedThumbnails ?? 50; + int maxCached = DecorationDataManager.Instance?.Settings?.GalleryMaxCachedThumbnails ?? StatueDressupConstants.DefaultMaxCachedThumbnails; while (thumbnailCache.Count > maxCached && thumbnailCacheOrder.Count > 0) { string oldestId = thumbnailCacheOrder.Dequeue(); @@ -284,7 +302,7 @@ namespace Minigames.StatueDressup.Controllers Logging.Debug($"[StatuePhotoGalleryController] Enlarging photo: {photoId}"); - float enlargedScale = StatueDressupSettings.Instance?.Settings?.GalleryEnlargedScale ?? 2.5f; + float enlargedScale = DecorationDataManager.Instance?.Settings?.GalleryEnlargedScale ?? StatueDressupConstants.DefaultEnlargedScale; // Check cache first if (fullPhotoCache.TryGetValue(photoId, out Texture2D fullPhoto)) @@ -404,3 +422,4 @@ namespace Minigames.StatueDressup.Controllers } } + diff --git a/Assets/Scripts/Minigames/StatueDressup/Data/DecorationEventData.cs b/Assets/Scripts/Minigames/StatueDressup/Data/DecorationEventData.cs new file mode 100644 index 00000000..f23d5d92 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Data/DecorationEventData.cs @@ -0,0 +1,39 @@ +using UnityEngine; + +namespace Minigames.StatueDressup.Data +{ + /// + /// Event data passed with decoration events for VFX/SFX responses + /// + public class DecorationEventData + { + /// + /// The decoration data (sprite, id, etc.) + /// + public DecorationData DecorationData { get; set; } + + /// + /// The GameObject instance of the decoration (if applicable) + /// + public GameObject Instance { get; set; } + + /// + /// Position where the event occurred (world space) + /// + public Vector3 Position { get; set; } + + /// + /// Whether this decoration was dragged from the menu or from the statue + /// + public bool FromStatue { get; set; } + + public DecorationEventData(DecorationData data, GameObject instance, Vector3 position, bool fromStatue = false) + { + DecorationData = data; + Instance = instance; + Position = position; + FromStatue = fromStatue; + } + } +} + diff --git a/Assets/Scripts/Minigames/StatueDressup/Data/DecorationEventData.cs.meta b/Assets/Scripts/Minigames/StatueDressup/Data/DecorationEventData.cs.meta new file mode 100644 index 00000000..bdd4fc23 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Data/DecorationEventData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5d9b3e7728c0420c8290986c31d5b738 +timeCreated: 1764248890 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/Display/StatueDecorationLoader.cs b/Assets/Scripts/Minigames/StatueDressup/Display/StatueDecorationLoader.cs index b3022276..0876eae6 100644 --- a/Assets/Scripts/Minigames/StatueDressup/Display/StatueDecorationLoader.cs +++ b/Assets/Scripts/Minigames/StatueDressup/Display/StatueDecorationLoader.cs @@ -39,29 +39,15 @@ namespace Minigames.StatueDressup.Display return; } - // DecorationDataManager exists (initialized in OnManagedAwake) but data loads async - // Wait for data to finish loading before displaying decorations - StartCoroutine(WaitForDataAndDisplay()); - } - - /// - /// Wait for DecorationDataManager to finish loading data before displaying decorations - /// - private System.Collections.IEnumerator WaitForDataAndDisplay() - { - // Wait for data to load (manager is guaranteed to exist) - while (!DecorationDataManager.Instance.IsLoaded) + // Wait for decoration data manager to be ready (static method handles null instance) + DecorationDataManager.WhenReady(() => { - yield return null; - } - - if (showDebugInfo) - { - Logging.Debug("[StatueDecorationLoader] DecorationData loaded, displaying decorations"); - } - - // Load and display decorations - LoadAndDisplayDecorations(); + if (showDebugInfo) + { + Logging.Debug("[StatueDecorationLoader] DecorationData ready, displaying decorations"); + } + LoadAndDisplayDecorations(); + }); } /// @@ -70,7 +56,7 @@ namespace Minigames.StatueDressup.Display public void LoadAndDisplayDecorations() { // Check if DecorationData is loaded via manager - if (DecorationDataManager.Instance == null || !DecorationDataManager.Instance.IsLoaded) + if (DecorationDataManager.Instance == null || !DecorationDataManager.Instance.IsReady) { Logging.Warning("[StatueDecorationLoader] DecorationDataManager not ready. Cannot display decorations."); return; diff --git a/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationDraggableInstance.cs b/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationDraggableInstance.cs index da904075..3f1f0ba4 100644 --- a/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationDraggableInstance.cs +++ b/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationDraggableInstance.cs @@ -1,6 +1,7 @@ using Core; using Minigames.StatueDressup.Controllers; using Minigames.StatueDressup.Data; +using Minigames.StatueDressup.Events; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; @@ -155,6 +156,10 @@ namespace Minigames.StatueDressup.DragDrop { isDragging = true; + // Broadcast started dragging event (from grid) + var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false); + DecorationEventsManager.BroadcastDecorationStartedDragging(eventDataObj); + // Calculate offset from cursor to object center RectTransformUtility.ScreenPointToLocalPointInRectangle( canvas.transform as RectTransform, @@ -193,6 +198,10 @@ namespace Minigames.StatueDressup.DragDrop Logging.Debug($"[DecorationDraggableInstance] Drag ended: {decorationData?.DecorationName}"); + // Broadcast finished dragging event + var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: isPlacedOnStatue); + DecorationEventsManager.BroadcastDecorationFinishedDragging(eventDataObj); + // Check if overlapping with statue if (IsOverlappingStatue()) { @@ -250,6 +259,10 @@ namespace Minigames.StatueDressup.DragDrop isPlacedOnStatue = true; + // Broadcast dropped on statue event + var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false); + DecorationEventsManager.BroadcastDecorationDroppedOnStatue(eventDataObj); + // Move to statue parent if specified if (statueParent != null && transform.parent != statueParent) { @@ -273,14 +286,22 @@ namespace Minigames.StatueDressup.DragDrop { Logging.Debug($"[DecorationDraggableInstance] Pop-out and destroy: {decorationData?.DecorationName}"); + // Broadcast dropped out event (animation starting) + var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false); + DecorationEventsManager.BroadcastDecorationDroppedOut(eventDataObj); + // Notify menu controller to hide outline immediately onFinishedCallback?.Invoke(); - float duration = settings?.PlacementAnimationDuration ?? 0.3f; + float duration = settings?.PlacementAnimationDuration ?? StatueDressupConstants.DefaultAnimationDuration; // Play pop-out with fade animation TweenAnimationUtility.PopOutWithFade(transform, canvasGroup, duration, () => { + // Broadcast finished dropping out event (animation complete) + var finalEventData = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false); + DecorationEventsManager.BroadcastDecorationFinishedDroppingOut(finalEventData); + Destroy(gameObject); }); } @@ -301,6 +322,10 @@ namespace Minigames.StatueDressup.DragDrop isPlacedOnStatue = false; isDragging = true; + // Broadcast started dragging event (from statue) + var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: true); + DecorationEventsManager.BroadcastDecorationStartedDragging(eventDataObj); + // Show statue outline when picking up from statue if (onShowOutlineCallback != null) { @@ -345,6 +370,10 @@ namespace Minigames.StatueDressup.DragDrop Logging.Debug($"[DecorationDraggableInstance] Decoration tapped: {decorationData?.DecorationName}"); + // Broadcast tap event + var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: true); + DecorationEventsManager.BroadcastDecorationTappedOnStatue(eventDataObj); + // Future: Open detail view, play sound effect, show info popup, etc. } diff --git a/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationGridIcon.cs b/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationGridIcon.cs index d26874f4..2e6691fa 100644 --- a/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationGridIcon.cs +++ b/Assets/Scripts/Minigames/StatueDressup/DragDrop/DecorationGridIcon.cs @@ -1,5 +1,7 @@ using Core; +using Minigames.StatueDressup.Controllers; using Minigames.StatueDressup.Data; +using Minigames.StatueDressup.Events; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; @@ -17,7 +19,7 @@ namespace Minigames.StatueDressup.DragDrop [SerializeField] private Image iconImage; [SerializeField] private DecorationData decorationData; - private Controllers.DecorationMenuController _menuController; + private DecorationMenuController _menuController; private DecorationDraggableInstance _activeDraggableInstance; // Properties @@ -26,7 +28,7 @@ namespace Minigames.StatueDressup.DragDrop /// /// Initialize the icon with decoration data /// - public void Initialize(DecorationData data, Controllers.DecorationMenuController controller) + public void Initialize(DecorationData data, DecorationMenuController controller) { decorationData = data; _menuController = controller; @@ -46,6 +48,11 @@ namespace Minigames.StatueDressup.DragDrop if (_activeDraggableInstance == null) { Logging.Debug($"[DecorationGridIcon] Item tapped: {decorationData?.DecorationName}"); + + // Broadcast tapped in grid event + var eventDataObj = new DecorationEventData(decorationData, gameObject, transform.position, fromStatue: false); + DecorationEventsManager.BroadcastDecorationTappedInGrid(eventDataObj); + // Future: Open detail view, preview, etc. } } diff --git a/Assets/Scripts/Minigames/StatueDressup/Events.meta b/Assets/Scripts/Minigames/StatueDressup/Events.meta new file mode 100644 index 00000000..b48c4744 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Events.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fd293a0b6d2e4b28bd74bc8aff5fea01 +timeCreated: 1764248911 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/Events/DecorationEventsManager.cs b/Assets/Scripts/Minigames/StatueDressup/Events/DecorationEventsManager.cs new file mode 100644 index 00000000..05f5b318 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Events/DecorationEventsManager.cs @@ -0,0 +1,172 @@ +using Core; +using Core.Lifecycle; +using Minigames.StatueDressup.Data; + +namespace Minigames.StatueDressup.Events +{ + /// + /// Manager for decoration VFX/SFX events. + /// Listens to decoration state changes and triggers audio/visual feedback. + /// + public class DecorationEventsManager : ManagedBehaviour + { + public static DecorationEventsManager Instance { get; private set; } + + // Static events for decoration state changes + public static event System.Action OnDecorationTappedInGrid; + public static event System.Action OnDecorationTappedOnStatue; + public static event System.Action OnDecorationStartedDragging; + public static event System.Action OnDecorationFinishedDragging; + public static event System.Action OnDecorationDroppedOnStatue; + public static event System.Action OnDecorationDroppedOut; + public static event System.Action OnDecorationFinishedDroppingOut; + + internal override void OnManagedAwake() + { + base.OnManagedAwake(); + + // Singleton pattern + if (Instance != null && Instance != this) + { + Logging.Warning("[DecorationEventsManager] Duplicate instance detected. Destroying duplicate."); + Destroy(gameObject); + return; + } + + Instance = this; + + // Subscribe to all events + OnDecorationTappedInGrid += HandleDecorationTappedInGrid; + OnDecorationTappedOnStatue += HandleDecorationTappedOnStatue; + OnDecorationStartedDragging += HandleDecorationStartedDragging; + OnDecorationFinishedDragging += HandleDecorationFinishedDragging; + OnDecorationDroppedOnStatue += HandleDecorationDroppedOnStatue; + OnDecorationDroppedOut += HandleDecorationDroppedOut; + OnDecorationFinishedDroppingOut += HandleDecorationFinishedDroppingOut; + } + + private void OnDestroy() + { + // Unsubscribe from all events + if (Instance == this) + { + OnDecorationTappedInGrid -= HandleDecorationTappedInGrid; + OnDecorationTappedOnStatue -= HandleDecorationTappedOnStatue; + OnDecorationStartedDragging -= HandleDecorationStartedDragging; + OnDecorationFinishedDragging -= HandleDecorationFinishedDragging; + OnDecorationDroppedOnStatue -= HandleDecorationDroppedOnStatue; + OnDecorationDroppedOut -= HandleDecorationDroppedOut; + OnDecorationFinishedDroppingOut -= HandleDecorationFinishedDroppingOut; + + Instance = null; + } + } + + #region Static Broadcasting Methods + + /// + /// Broadcast that a decoration was tapped in the grid menu + /// + public static void BroadcastDecorationTappedInGrid(DecorationEventData eventData) + { + OnDecorationTappedInGrid?.Invoke(eventData); + } + + /// + /// Broadcast that a decoration already on the statue was tapped + /// + public static void BroadcastDecorationTappedOnStatue(DecorationEventData eventData) + { + OnDecorationTappedOnStatue?.Invoke(eventData); + } + + /// + /// Broadcast that a decoration started being dragged + /// + public static void BroadcastDecorationStartedDragging(DecorationEventData eventData) + { + OnDecorationStartedDragging?.Invoke(eventData); + } + + /// + /// Broadcast that a decoration finished being dragged (released) + /// + public static void BroadcastDecorationFinishedDragging(DecorationEventData eventData) + { + OnDecorationFinishedDragging?.Invoke(eventData); + } + + /// + /// Broadcast that a decoration was successfully dropped on the statue + /// + public static void BroadcastDecorationDroppedOnStatue(DecorationEventData eventData) + { + OnDecorationDroppedOnStatue?.Invoke(eventData); + } + + /// + /// Broadcast that a decoration was dropped outside the statue (animation starts) + /// + public static void BroadcastDecorationDroppedOut(DecorationEventData eventData) + { + OnDecorationDroppedOut?.Invoke(eventData); + } + + /// + /// Broadcast that a decoration finished its drop-out animation + /// + public static void BroadcastDecorationFinishedDroppingOut(DecorationEventData eventData) + { + OnDecorationFinishedDroppingOut?.Invoke(eventData); + } + + #endregion + + #region Event Handlers (Stubbed with Logs) + + private void HandleDecorationTappedInGrid(DecorationEventData eventData) + { + Logging.Debug($"[DecorationEventsManager] Decoration tapped in grid: {eventData.DecorationData?.DecorationId}"); + // TODO: Play tap SFX/VFX + } + + private void HandleDecorationTappedOnStatue(DecorationEventData eventData) + { + Logging.Debug($"[DecorationEventsManager] Decoration tapped on statue: {eventData.DecorationData?.DecorationId}"); + // TODO: Play tap SFX/VFX (different from grid tap?) + } + + private void HandleDecorationStartedDragging(DecorationEventData eventData) + { + Logging.Debug($"[DecorationEventsManager] Decoration started dragging: {eventData.DecorationData?.DecorationId} (FromStatue: {eventData.FromStatue})"); + // TODO: Play drag start SFX, maybe show drag VFX + } + + private void HandleDecorationFinishedDragging(DecorationEventData eventData) + { + Logging.Debug($"[DecorationEventsManager] Decoration finished dragging: {eventData.DecorationData?.DecorationId}"); + // TODO: Play drag release SFX + } + + private void HandleDecorationDroppedOnStatue(DecorationEventData eventData) + { + Logging.Debug($"[DecorationEventsManager] Decoration dropped on statue: {eventData.DecorationData?.DecorationId}"); + // TODO: Play success SFX, maybe show placement VFX + } + + private void HandleDecorationDroppedOut(DecorationEventData eventData) + { + Logging.Debug($"[DecorationEventsManager] Decoration dropped out (animation starting): {eventData.DecorationData?.DecorationId}"); + // TODO: Play drop-out SFX (whoosh/disappear sound?) + } + + private void HandleDecorationFinishedDroppingOut(DecorationEventData eventData) + { + Logging.Debug($"[DecorationEventsManager] Decoration finished dropping out: {eventData.DecorationData?.DecorationId}"); + // TODO: Play finish SFX (poof sound?), maybe show VFX + } + + #endregion + } +} + diff --git a/Assets/Scripts/Minigames/StatueDressup/Events/DecorationEventsManager.cs.meta b/Assets/Scripts/Minigames/StatueDressup/Events/DecorationEventsManager.cs.meta new file mode 100644 index 00000000..8d46879e --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Events/DecorationEventsManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5c9796e0044a4fcd95b02a19925a6b2b +timeCreated: 1764248911 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs b/Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs new file mode 100644 index 00000000..36205c0a --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs @@ -0,0 +1,63 @@ +using System; + +namespace Minigames.StatueDressup +{ + /// + /// Interface for managers that load asynchronously and notify when ready. + /// Allows dependent components to safely access the manager via WhenReady callbacks. + /// + public interface IReadyNotifier + { + /// + /// True when the manager has finished initialization and is ready to use + /// + bool IsReady { get; } + + /// + /// Event invoked when the manager becomes ready + /// + event Action OnReady; + } + + /// + /// Extension methods for IReadyNotifier to provide common callback behavior + /// + public static class ReadyNotifierExtensions + { + /// + /// Execute callback when ready. If already ready, executes immediately. + /// If not ready yet, subscribes to OnReady event and executes when fired. + /// + public static void WhenReady(this IReadyNotifier notifier, Action callback) + { + if (notifier == null) + { + Core.Logging.Warning("[ReadyNotifierExtensions] Notifier is null, cannot execute callback"); + return; + } + + if (callback == null) + { + return; + } + + if (notifier.IsReady) + { + // Already ready - execute immediately + callback.Invoke(); + } + else + { + // Not ready yet - subscribe to event and auto-unsubscribe after invocation + Action handler = null; + handler = () => + { + callback.Invoke(); + notifier.OnReady -= handler; // Unsubscribe to prevent memory leaks + }; + notifier.OnReady += handler; + } + } + } +} + diff --git a/Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs.meta b/Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs.meta new file mode 100644 index 00000000..b94b3d79 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/IReadyNotifier.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: da5633d4a0f84ceeaca54bb2c542dca4 +timeCreated: 1764244569 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/StatueDressupConstants.cs b/Assets/Scripts/Minigames/StatueDressup/StatueDressupConstants.cs new file mode 100644 index 00000000..e3a4f66a --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/StatueDressupConstants.cs @@ -0,0 +1,24 @@ +namespace Minigames.StatueDressup +{ + /// + /// Default constants for StatueDressup minigame. + /// These serve as fallbacks when settings fail to load. + /// Prefer using settings configuration over these constants. + /// + public static class StatueDressupConstants + { + // Pagination + public const int DefaultMenuItemsPerPage = 20; + public const int DefaultGalleryItemsPerPage = 20; + + // Animations + public const float DefaultAnimationDuration = 0.3f; + + // Gallery/Photos - Performance Critical + // Note: These affect memory usage and should ideally come from settings + public const int DefaultThumbnailSize = 256; + public const int DefaultMaxCachedThumbnails = 50; + public const float DefaultEnlargedScale = 2.5f; + } +} + diff --git a/Assets/Scripts/Minigames/StatueDressup/StatueDressupConstants.cs.meta b/Assets/Scripts/Minigames/StatueDressup/StatueDressupConstants.cs.meta new file mode 100644 index 00000000..999144a0 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/StatueDressupConstants.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 86c9225c5cc54b8b99b1964c393de5a3 +timeCreated: 1764242098 \ No newline at end of file diff --git a/docs/statue_minigame_primer.md b/docs/statue_minigame_primer.md new file mode 100644 index 00000000..1cc85e46 --- /dev/null +++ b/docs/statue_minigame_primer.md @@ -0,0 +1,154 @@ +# Statue Decoration Minigame - Primer + +A quick-start guide for designers and programmers working on the statue decoration minigame. + +## Table of Contents +- [Overview](#overview) +- [Quick References](#quick-references) + - [Adding Audio/Visual Feedback](#adding-audiovisual-feedback) + - [Loading Decoration Data & Settings](#loading-decoration-data--settings) + - [Adding New Decorations](#adding-new-decorations) + - [Changing UI Layout](#changing-ui-layout) + - [Tweaking Behavior](#tweaking-behavior) + - [Photo Saving/Loading](#photo-savingloading) +- [Class Reference](#class-reference) + - [Controllers](#controllers) + - [Drag & Drop](#drag--drop) + - [Data & Events](#data--events) + - [Display](#display) + - [Settings](#settings) +- [Common Tasks](#common-tasks) +- [Known TODOs](#known-todos) + +## Overview +Players drag sticker decorations from a menu onto a statue, decorate it however they like, and take photos. Photos are saved to a gallery with decoration metadata so the statue can be restored in the town map miniature. + +## Quick References + +### Adding Audio/Visual Feedback +**Location:** `DecorationEventsManager.cs` in `Events/` +All decoration interactions broadcast events (tap, drag start, drag end, place, etc.). Subscribe to events or edit the stub handler methods to add your SFX/VFX. Just add a `DecorationEventsManager` component to the scene. + +### Loading Decoration Data & Settings +**Location:** `DecorationDataManager.cs` in `Controllers/` +This singleton loads settings from GameManager, then loads all decoration sprites via Addressables. Everything happens automatically on scene start. Use `DecorationDataManager.WhenReady(() => { ... })` to wait for data before accessing. + +### Adding New Decorations +**Location:** Create a `DecorationData` ScriptableObject in Unity +Set the sprite, ID, and name. Add it to the Addressables group specified in `StatueDressupSettings`. It'll appear in the menu automatically. + +### Changing UI Layout +**Location:** `StatueDressupMinigame` scene +- Menu grid: `DecorationMenuController` → `ItemsContainer` +- Statue area: `StatueDecorationController` → `StatueImage` and `DecorationsParent` +- Photo gallery: `StatuePhotoGalleryController` → Grid and pagination UI + +### Tweaking Behavior +**Location:** `StatueDressupSettings` ScriptableObject +Contains all tunable values: animation durations, menu items per page, gallery settings, drag thresholds, etc. + +### Photo Saving/Loading +**Location:** `StatueDecorationController.cs` - `TakePhoto()` method +Photos are captured with metadata (decoration positions, scales, IDs). Use `PhotoManager` for save/load operations. Metadata is read by `StatueDecorationLoader` to restore decorations on the town map. + +--- + +## Class Reference + +### Controllers + +**`DecorationDataManager`** +- **What it does:** Loads settings and all decoration data from Addressables. Single source of truth for both. +- **When you might need it:** Accessing decoration sprites/data, checking if a decoration ID exists, waiting for data to be ready. + +**`StatueDecorationController`** +- **What it does:** Main minigame controller - manages decoration placement, photo capture, and save/load of decoration state. +- **When you might need it:** Taking photos, getting/setting decoration placements, loading saved decorations, accessing statue UI references. + +**`DecorationMenuController`** +- **What it does:** Manages the scrollable decoration picker menu with pagination and spawns draggable instances. +- **When you might need it:** Changing menu behavior, adding categories/filters, modifying how decorations are displayed. + +**`StatuePhotoGalleryController`** +- **What it does:** Displays saved photos in a paginated grid with thumbnail caching and enlarged preview. +- **When you might need it:** Modifying gallery UI, changing thumbnail behavior, adding photo delete/share functionality. + +--- + +### Drag & Drop + +**`DecorationDraggableInstance`** +- **What it does:** The actual draggable decoration instance that follows the cursor and can be placed on the statue. +- **When you might need it:** Changing drag behavior, adding visual effects during drag, modifying placement validation. + +**`DecorationGridIcon`** +- **What it does:** Static menu icon that spawns a draggable instance when clicked/dragged. +- **When you might need it:** Adding icon animations, tooltips, or preview functionality to menu items. + +**`DecorationDragContext`** +- **What it does:** Data container passed to draggable instances with all references and callbacks needed for dragging. +- **When you might need it:** Adding new data that draggables need during their lifecycle. + +--- + +### Data & Events + +**`DecorationData`** (ScriptableObject) +- **What it does:** Defines a single decoration (sprite, ID, name, authored size). +- **When you might need it:** Creating new decorations or querying decoration properties. + +**`DecorationPlacement`** +- **What it does:** Serializable data for a placed decoration (position, scale, rotation, sorting order). +- **When you might need it:** Saving/loading decoration state, modifying what gets persisted. + +**`DecorationMetadata`** +- **What it does:** Complete metadata for all decorations on a photo (list of placements + coordinate system type). +- **When you might need it:** Working with photo save/load system, adding new metadata fields. + +**`DecorationEventData`** +- **What it does:** Event payload containing decoration, instance GameObject, position, and context flags. +- **When you might need it:** Handling decoration events for SFX/VFX, adding new event data fields. + +**`DecorationEventsManager`** +- **What it does:** Broadcasts and handles all decoration interaction events (tap, drag, place, etc.). +- **When you might need it:** Playing sounds/effects in response to decoration interactions, adding telemetry. + +--- + +### Display + +**`StatueDecorationLoader`** +- **What it does:** Loads saved decoration metadata and spawns decorations on a statue (used in town map). +- **When you might need it:** Displaying decorations outside the minigame, fixing coordinate conversion bugs. + +--- + +### Settings + +**`StatueDressupSettings`** (ScriptableObject in `Core/Settings`) +- **What it does:** All configuration values for the minigame (durations, counts, labels, thresholds). +- **When you might need it:** Tuning any minigame behavior without touching code. + +**`StatueDressupConstants`** +- **What it does:** Fallback constant values if settings are missing. +- **When you might need it:** Setting default values or adding new configurable parameters. + +--- + +## Common Tasks + +**Add a new decoration:** +Create `DecorationData` ScriptableObject → Set sprite/ID → Add to Addressables group + +**Change drag feel:** +Edit `StatueDressupSettings` → Adjust `DragSnapThreshold` or animation durations + +**Add sound to decoration tap:** +Edit `DecorationEventsManager.HandleDecorationTappedInGrid()` → Play your audio clip + +**Change menu layout:** +Edit `StatueDressupSettings` → Adjust `ItemsPerPage` or edit menu prefab + +**Debug save/load:** +Check `PhotoManager.SavePhoto()` and `StatueDecorationLoader.LoadAndDisplayDecorations()` + diff --git a/docs/statue_photo_gallery_setup.md b/docs/statue_photo_gallery_setup.md deleted file mode 100644 index 95791837..00000000 --- a/docs/statue_photo_gallery_setup.md +++ /dev/null @@ -1,164 +0,0 @@ -# Photo Gallery Scene Setup Guide - -## Current Scene Status -✅ MainCanvas exists -✅ StatueDecorationController exists -✅ PhotoCaptureTestController exists (testing only) - ---- - -## Required Scene Setup - -### 1. **Create UIPage Wrappers** - -#### PlayAreaPage -1. Create empty GameObject under `MainCanvas` → name: `PlayAreaPage` -2. Add component: `PlayAreaPage.cs` -3. Move **existing decoration UI** as children under PlayAreaPage: - - Decoration menu - - Take photo button - - Any other play area UI - -#### PhotoGalleryPage -1. Create empty GameObject under `MainCanvas` → name: `PhotoGalleryPage` -2. Add component: `PhotoGalleryPage.cs` -3. Set initially inactive (will be shown when opened) - ---- - -### 2. **Setup Photo Gallery UI** (under PhotoGalleryPage) - -``` -PhotoGalleryPage -├── GalleryController (GameObject) -│ └── StatuePhotoGalleryController.cs -├── GridContainer (GameObject with GridLayoutGroup) -├── EnlargedContainer (GameObject - top layer) -├── Backdrop (UI Image - dark/black, alpha 0.8) -├── PageStatusText (UI Text - shows "Page X/Y") -└── Navigation - ├── PreviousPageButton (UI Button - "< Prev") - └── NextPageButton (UI Button - "Next >") -``` - -**Components to assign on StatuePhotoGalleryController:** -- `gridContainer` → GridContainer transform -- `gridItemPrefab` → Create prefab with PhotoGridItem.cs + UI Image -- `enlargedContainer` → EnlargedContainer transform -- `backdrop` → Backdrop GameObject -- `enlargedPreviewPrefab` → Same as gridItemPrefab (or leave null to clone item) -- `previousPageButton` → Previous page button -- `nextPageButton` → Next page button -- `pageStatusText` → Optional status text showing page info - -**GridLayoutGroup Settings (on GridContainer):** -- Cell Size: e.g., 200x200 -- Spacing: e.g., 10x10 -- Constraint: Fixed Column Count (3-4 columns recommended) -- Child Alignment: Upper Left (or as preferred) - ---- - -### 3. **Create PhotoGridItem Prefab** - -1. Create UI → Image in scene -2. Add `PhotoGridItem.cs` component -3. Assign `thumbnailImage` → the Image component itself -4. Optional: Add loading indicator child (simple spinner/text) -5. Drag to Prefabs folder → name: `PhotoGridItem` -6. Delete from scene - ---- - -### 4. **Update StatueDecorationController** - -**Assign in Inspector:** -- `playAreaPage` → PlayAreaPage GameObject -- `photoGalleryPage` → PhotoGalleryPage GameObject -- `openGalleryButton` → Create button in PlayAreaPage UI, assign here -- `photoArea` → RectTransform defining capture area (statue + decorations) -- `takePhotoButton` → Existing button (uncomment line 97 in code when ready) - ---- - -### 5. **Create Gallery Open Button** - -1. Add Button in PlayAreaPage UI → name: "OpenGalleryButton" -2. Button Text: "📷 Gallery" or "View Photos" -3. Assign to StatueDecorationController's `openGalleryButton` field - ---- - -### 6. **Page Navigation** (PhotoGalleryPage) - -1. Create two buttons for Previous/Next page -2. Buttons will be auto-wired by StatuePhotoGalleryController -3. Controller auto-disables buttons when at first/last page -4. Grid clears and reloads on each page change - ---- - -## Key Features - -### ✅ Button-Based Pagination -- Previous/Next buttons navigate pages -- Grid **clears completely** and shows only current page -- Buttons auto-disable at boundaries (first/last page) -- Status text shows "Page X/Y (total photos)" - -### ✅ Grid Clearing -- Grid is cleared on initialization -- Grid clears when switching pages -- Prevents leftover items from scene setup - -### ✅ Enlarge System -- Click photo → spawns preview clone, animates to center -- Click again → shrinks back, destroys preview -- Original grid items never move -- Backdrop blocks grid interaction - ---- - -## Testing Checklist - -### Photo Capture (Already Working) -- ✅ PhotoCaptureTestController captures and saves photos -- ✅ Photos saved to `Application.persistentDataPath/StatuePhotos/` - -### Gallery Integration (New Setup) -1. Play scene -2. Click "Open Gallery" button → Gallery page shows -3. First page of photos loads in grid -4. Click "Next" → Grid clears, next page loads -5. Click "Previous" → Grid clears, previous page loads -6. Click photo → Enlarges to center with backdrop -7. Click enlarged photo → Shrinks back to grid -8. Back button → Returns to play area - ---- - -## Quick Setup Time -- **PlayAreaPage wrapper:** 2 minutes -- **PhotoGalleryPage structure:** 5 minutes -- **PhotoGridItem prefab:** 3 minutes -- **Wire references:** 5 minutes - -**Total: ~15 minutes** - ---- - -## Configuration Variables -Set these on `StatuePhotoGalleryController`: -- `itemsPerPage`: 20 (photos per page) -- `thumbnailSize`: 256 (pixels) -- `maxCachedThumbnails`: 50 (LRU cache) -- `enlargedScale`: 2.5 (zoom amount) -- `animationDuration`: 0.3s - ---- - -## Cleanup (Optional) -Once gallery works, you can: -- Remove `PhotoCaptureTestController` GameObject -- Keep for quick testing if preferred -