Implement MVP for the statue decoration minigame (#65)

MVP implemented with:
- placing, removing etc. decorations
- saving the state, displaying it on the map, restoring when game restarts
- saving screenshots to folder on device

Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com>
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: #65
This commit is contained in:
2025-11-27 13:21:22 +00:00
parent 5ad84ca3e8
commit 83aa3d5e6d
71 changed files with 6421 additions and 976 deletions

View File

@@ -0,0 +1,163 @@
using Core;
using UnityEngine;
using UnityEngine.EventSystems;
using Utils;
namespace Minigames.StatueDressup.Controllers
{
/// <summary>
/// Manages enlarged photo preview display.
/// Handles backdrop, preview spawning, and click-to-dismiss.
/// Similar to CardEnlargeController but simpler (no state machine).
/// </summary>
public class PhotoEnlargeController
{
private readonly GameObject backdrop;
private readonly Transform enlargedContainer;
private readonly float animationDuration;
private GameObject currentEnlargedPreview;
private PhotoGridItem currentSourceItem;
/// <summary>
/// Constructor
/// </summary>
public PhotoEnlargeController(GameObject backdrop, Transform enlargedContainer, float animationDuration = 0.3f)
{
this.backdrop = backdrop;
this.enlargedContainer = enlargedContainer;
this.animationDuration = animationDuration;
}
/// <summary>
/// Check if a photo is currently enlarged
/// </summary>
public bool IsPhotoEnlarged => currentEnlargedPreview != null;
/// <summary>
/// Enlarge a photo from a grid item
/// </summary>
public void EnlargePhoto(PhotoGridItem sourceItem, GameObject previewPrefab, Texture2D photoTexture, float enlargedScale)
{
if (sourceItem == null || previewPrefab == null || photoTexture == null)
{
Logging.Error("[PhotoEnlargeController] Invalid parameters for EnlargePhoto");
return;
}
// Don't allow multiple enlargements
if (currentEnlargedPreview != null)
{
Logging.Warning("[PhotoEnlargeController] Photo already enlarged");
return;
}
currentSourceItem = sourceItem;
// Show backdrop
if (backdrop != null)
{
backdrop.SetActive(true);
}
// Spawn preview clone
currentEnlargedPreview = Object.Instantiate(previewPrefab, enlargedContainer);
currentEnlargedPreview.transform.SetAsLastSibling();
// Position at source item's world position
currentEnlargedPreview.transform.position = sourceItem.transform.position;
currentEnlargedPreview.transform.localScale = sourceItem.transform.localScale;
// Set photo texture on preview
var previewImage = currentEnlargedPreview.GetComponent<UnityEngine.UI.Image>();
if (previewImage != null)
{
// Create sprite from texture
Sprite photoSprite = Sprite.Create(
photoTexture,
new Rect(0, 0, photoTexture.width, photoTexture.height),
new Vector2(0.5f, 0.5f)
);
previewImage.sprite = photoSprite;
}
// Add click handler to preview
var clickHandler = currentEnlargedPreview.GetComponent<EventTrigger>();
if (clickHandler == null)
{
clickHandler = currentEnlargedPreview.AddComponent<EventTrigger>();
}
var pointerClickEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerClick };
pointerClickEntry.callback.AddListener((_) => { ShrinkPhoto(); });
clickHandler.triggers.Add(pointerClickEntry);
// Animate to center and scale up
TweenAnimationUtility.AnimateLocalPosition(currentEnlargedPreview.transform, Vector3.zero, animationDuration);
TweenAnimationUtility.AnimateScale(currentEnlargedPreview.transform, Vector3.one * enlargedScale, animationDuration);
// Play audio feedback
AudioManager.Instance.LoadAndPlayUIAudio("card_albumdrop_deep", false);
Logging.Debug("[PhotoEnlargeController] Photo enlarged");
}
/// <summary>
/// Shrink the currently enlarged photo back to grid
/// </summary>
public void ShrinkPhoto()
{
if (currentEnlargedPreview == null || currentSourceItem == null)
{
Logging.Warning("[PhotoEnlargeController] No photo to shrink");
return;
}
// Hide backdrop
if (backdrop != null)
{
backdrop.SetActive(false);
}
// Get target position from source item
Vector3 targetWorldPos = currentSourceItem.transform.position;
Vector3 targetScale = currentSourceItem.transform.localScale;
GameObject previewToDestroy = currentEnlargedPreview;
// Animate back to source position
Pixelplacement.Tween.Position(previewToDestroy.transform, targetWorldPos, animationDuration, 0f, Pixelplacement.Tween.EaseInOut);
TweenAnimationUtility.AnimateScale(previewToDestroy.transform, targetScale, animationDuration, () =>
{
// Destroy preview after animation
Object.Destroy(previewToDestroy);
});
// Clear references
currentEnlargedPreview = null;
currentSourceItem = null;
Logging.Debug("[PhotoEnlargeController] Photo shrunk");
}
/// <summary>
/// Cleanup - call when gallery is closed
/// </summary>
public void Cleanup()
{
if (currentEnlargedPreview != null)
{
Object.Destroy(currentEnlargedPreview);
currentEnlargedPreview = null;
}
if (backdrop != null)
{
backdrop.SetActive(false);
}
currentSourceItem = null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8ba20462f9a647ee90389c7480147d2e
timeCreated: 1764151481

View File

@@ -0,0 +1,79 @@
using Core;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
namespace Minigames.StatueDressup.Controllers
{
/// <summary>
/// Individual photo thumbnail in the gallery grid.
/// Handles click to show enlarged view.
/// </summary>
public class PhotoGridItem : MonoBehaviour, IPointerClickHandler
{
[Header("References")]
[SerializeField] private Image thumbnailImage;
[SerializeField] private GameObject loadingIndicator;
private string photoId;
private StatuePhotoGalleryController galleryController;
/// <summary>
/// Initialize grid item with photo ID
/// </summary>
public void Initialize(string newPhotoId, StatuePhotoGalleryController controller)
{
this.photoId = newPhotoId;
galleryController = controller;
// Show loading state
if (loadingIndicator != null)
loadingIndicator.SetActive(true);
if (thumbnailImage != null)
thumbnailImage.enabled = false;
}
/// <summary>
/// Set the thumbnail texture
/// </summary>
public void SetThumbnail(Texture2D thumbnail)
{
if (thumbnail == null)
{
Logging.Warning($"[PhotoGridItem] Null thumbnail for photo: {photoId}");
return;
}
// Create sprite from thumbnail
Sprite thumbnailSprite = Sprite.Create(
thumbnail,
new Rect(0, 0, thumbnail.width, thumbnail.height),
new Vector2(0.5f, 0.5f)
);
if (thumbnailImage != null)
{
thumbnailImage.sprite = thumbnailSprite;
thumbnailImage.enabled = true;
}
// Hide loading indicator
if (loadingIndicator != null)
loadingIndicator.SetActive(false);
}
/// <summary>
/// Handle click to enlarge/shrink photo
/// </summary>
public void OnPointerClick(PointerEventData eventData)
{
if (galleryController != null && !string.IsNullOrEmpty(photoId))
{
Logging.Debug($"[PhotoGridItem] Clicked: {photoId}");
galleryController.OnGridItemClicked(this, photoId);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: acd8d97ee2f744d984a9507e75309be0
timeCreated: 1764065014