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
-