Compare commits
1 Commits
c47d4e5225
...
michal_dum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b20192a03a |
File diff suppressed because one or more lines are too long
3
Assets/Scripts/Minigames/StatueDressup.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5596931aef9448a3b369f7917af07797
|
||||||
|
timeCreated: 1763745490
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/Controllers.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/Controllers.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 34525368248b48e0b271537891123818
|
||||||
|
timeCreated: 1763745579
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Core;
|
||||||
|
using Minigames.StatueDressup.Data;
|
||||||
|
using Minigames.StatueDressup.DragDrop;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages the side menu with decoration items and pagination
|
||||||
|
/// </summary>
|
||||||
|
public class DecorationMenuController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("References")]
|
||||||
|
[SerializeField] private DecorationItem itemPrefab;
|
||||||
|
[SerializeField] private Transform itemsContainer;
|
||||||
|
[SerializeField] private Button nextPageButton;
|
||||||
|
[SerializeField] private Button previousPageButton;
|
||||||
|
|
||||||
|
[Header("Configuration")]
|
||||||
|
[SerializeField] private List<DecorationData> allDecorations = new List<DecorationData>();
|
||||||
|
[SerializeField] private int itemsPerPage = 10; // 2 columns x 5 rows
|
||||||
|
|
||||||
|
[Header("Layout")]
|
||||||
|
[SerializeField] private GridLayoutGroup gridLayout;
|
||||||
|
|
||||||
|
private int _currentPage = 0;
|
||||||
|
private int _totalPages = 0;
|
||||||
|
private List<DecorationItem> _spawnedItems = new List<DecorationItem>();
|
||||||
|
private Dictionary<DecorationItem, DecorationData> _itemDataMapping = new Dictionary<DecorationItem, DecorationData>();
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
public int CurrentPage => _currentPage;
|
||||||
|
public int TotalPages => _totalPages;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationMenuController] Initializing with {allDecorations.Count} decorations");
|
||||||
|
|
||||||
|
// Calculate total pages
|
||||||
|
_totalPages = Mathf.CeilToInt((float)allDecorations.Count / itemsPerPage);
|
||||||
|
Logging.Debug($"[DecorationMenuController] Total pages: {_totalPages}");
|
||||||
|
|
||||||
|
// Setup buttons
|
||||||
|
if (nextPageButton != null)
|
||||||
|
{
|
||||||
|
nextPageButton.onClick.AddListener(OnNextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousPageButton != null)
|
||||||
|
{
|
||||||
|
previousPageButton.onClick.AddListener(OnPreviousPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to drag events for all items
|
||||||
|
// (will be handled per-item when spawned)
|
||||||
|
|
||||||
|
// Populate first page
|
||||||
|
PopulateCurrentPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate the current page with decoration items
|
||||||
|
/// </summary>
|
||||||
|
private void PopulateCurrentPage()
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationMenuController] Populating page {_currentPage + 1}/{_totalPages}");
|
||||||
|
|
||||||
|
// Clear existing items
|
||||||
|
ClearItems();
|
||||||
|
|
||||||
|
// Calculate range for current page
|
||||||
|
int startIndex = _currentPage * itemsPerPage;
|
||||||
|
int endIndex = Mathf.Min(startIndex + itemsPerPage, allDecorations.Count);
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationMenuController] Spawning items {startIndex} to {endIndex - 1}");
|
||||||
|
|
||||||
|
// Spawn items for this page
|
||||||
|
for (int i = startIndex; i < endIndex; i++)
|
||||||
|
{
|
||||||
|
SpawnDecorationItem(allDecorations[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
UpdateNavigationButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawn a decoration item in the menu
|
||||||
|
/// </summary>
|
||||||
|
private void SpawnDecorationItem(DecorationData data)
|
||||||
|
{
|
||||||
|
if (itemPrefab == null || itemsContainer == null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[DecorationMenuController] Missing prefab or container");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DecorationItem item = Instantiate(itemPrefab, itemsContainer);
|
||||||
|
item.SetDecorationData(data);
|
||||||
|
|
||||||
|
// Store original position for return animation
|
||||||
|
if (item.RectTransform != null)
|
||||||
|
{
|
||||||
|
// Force layout update to get correct position
|
||||||
|
Canvas.ForceUpdateCanvases();
|
||||||
|
item.SetOriginalMenuPosition(item.RectTransform.anchoredPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to drag events
|
||||||
|
item.OnDragStarted += HandleItemPickedUp;
|
||||||
|
item.OnDragEnded += HandleItemDropped;
|
||||||
|
|
||||||
|
_spawnedItems.Add(item);
|
||||||
|
_itemDataMapping[item] = data;
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationMenuController] Spawned: {data.DecorationName} at position {item.RectTransform?.anchoredPosition}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle item picked up from menu
|
||||||
|
/// </summary>
|
||||||
|
private void HandleItemPickedUp(DraggableObject draggable)
|
||||||
|
{
|
||||||
|
if (draggable is DecorationItem item && _itemDataMapping.ContainsKey(item))
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationMenuController] Item picked up: {item.Data?.DecorationName}");
|
||||||
|
|
||||||
|
// Spawn replacement in menu slot
|
||||||
|
// This ensures menu always shows available items
|
||||||
|
DecorationData data = _itemDataMapping[item];
|
||||||
|
// We'll spawn replacement only if item is actually placed, not on pickup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle item dropped (either placed on statue or returned to menu)
|
||||||
|
/// </summary>
|
||||||
|
private void HandleItemDropped(DraggableObject draggable)
|
||||||
|
{
|
||||||
|
if (draggable is DecorationItem item && _itemDataMapping.ContainsKey(item))
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationMenuController] Item dropped: {item.Data?.DecorationName}, slot={item.CurrentSlot?.name}");
|
||||||
|
|
||||||
|
// If item was placed on statue, spawn replacement in menu
|
||||||
|
if (item.CurrentSlot != null && !item.IsInMenu)
|
||||||
|
{
|
||||||
|
DecorationData data = _itemDataMapping[item];
|
||||||
|
|
||||||
|
// Remove original from tracking
|
||||||
|
_spawnedItems.Remove(item);
|
||||||
|
_itemDataMapping.Remove(item);
|
||||||
|
|
||||||
|
// Spawn replacement
|
||||||
|
SpawnDecorationItem(data);
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationMenuController] Spawned replacement for: {data.DecorationName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all spawned items
|
||||||
|
/// </summary>
|
||||||
|
private void ClearItems()
|
||||||
|
{
|
||||||
|
foreach (var item in _spawnedItems)
|
||||||
|
{
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
item.OnDragStarted -= HandleItemPickedUp;
|
||||||
|
item.OnDragEnded -= HandleItemDropped;
|
||||||
|
Destroy(item.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_spawnedItems.Clear();
|
||||||
|
_itemDataMapping.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigate to next page
|
||||||
|
/// </summary>
|
||||||
|
private void OnNextPage()
|
||||||
|
{
|
||||||
|
if (_currentPage < _totalPages - 1)
|
||||||
|
{
|
||||||
|
_currentPage++;
|
||||||
|
PopulateCurrentPage();
|
||||||
|
Logging.Debug($"[DecorationMenuController] Next page: {_currentPage + 1}/{_totalPages}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigate to previous page
|
||||||
|
/// </summary>
|
||||||
|
private void OnPreviousPage()
|
||||||
|
{
|
||||||
|
if (_currentPage > 0)
|
||||||
|
{
|
||||||
|
_currentPage--;
|
||||||
|
PopulateCurrentPage();
|
||||||
|
Logging.Debug($"[DecorationMenuController] Previous page: {_currentPage + 1}/{_totalPages}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update navigation button interactability
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateNavigationButtons()
|
||||||
|
{
|
||||||
|
if (previousPageButton != null)
|
||||||
|
{
|
||||||
|
previousPageButton.interactable = _currentPage > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPageButton != null)
|
||||||
|
{
|
||||||
|
nextPageButton.interactable = _currentPage < _totalPages - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
// Cleanup button listeners
|
||||||
|
if (nextPageButton != null)
|
||||||
|
{
|
||||||
|
nextPageButton.onClick.RemoveListener(OnNextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousPageButton != null)
|
||||||
|
{
|
||||||
|
previousPageButton.onClick.RemoveListener(OnPreviousPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup item listeners
|
||||||
|
ClearItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: acbd542762b44e719326dff6c3a69e6e
|
||||||
|
timeCreated: 1763745579
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Core;
|
||||||
|
using Minigames.StatueDressup.DragDrop;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main controller for the Mr. Cement statue decoration minigame
|
||||||
|
/// </summary>
|
||||||
|
public class StatueDecorationController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("References")]
|
||||||
|
[SerializeField] private StatueDecorationSlot[] statueSlots;
|
||||||
|
[SerializeField] private DecorationMenuController menuController;
|
||||||
|
[SerializeField] private Button takePhotoButton;
|
||||||
|
[SerializeField] private GameObject statue;
|
||||||
|
|
||||||
|
[Header("UI Elements to Hide for Photo")]
|
||||||
|
[SerializeField] private GameObject[] uiElementsToHideForPhoto;
|
||||||
|
|
||||||
|
[Header("Photo Settings")]
|
||||||
|
[SerializeField] private RectTransform photoArea; // Area to capture
|
||||||
|
[SerializeField] private string photoSaveKey = "MrCementStatuePhoto";
|
||||||
|
|
||||||
|
private Dictionary<StatueDecorationSlot, DecorationItem> _placedDecorations = new Dictionary<StatueDecorationSlot, DecorationItem>();
|
||||||
|
private bool _minigameCompleted = false;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
Logging.Debug("[StatueDecorationController] Initializing minigame");
|
||||||
|
|
||||||
|
// Setup photo button
|
||||||
|
if (takePhotoButton != null)
|
||||||
|
{
|
||||||
|
takePhotoButton.onClick.AddListener(OnTakePhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to slot occupation events
|
||||||
|
foreach (var slot in statueSlots)
|
||||||
|
{
|
||||||
|
if (slot != null)
|
||||||
|
{
|
||||||
|
slot.OnOccupied += HandleDecorationPlaced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load saved state if exists
|
||||||
|
LoadStatueState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle decoration placed in slot
|
||||||
|
/// </summary>
|
||||||
|
private void HandleDecorationPlaced(DraggableObject draggable)
|
||||||
|
{
|
||||||
|
if (draggable is DecorationItem decoration)
|
||||||
|
{
|
||||||
|
var slot = decoration.CurrentSlot as StatueDecorationSlot;
|
||||||
|
if (slot != null)
|
||||||
|
{
|
||||||
|
_placedDecorations[slot] = decoration;
|
||||||
|
Logging.Debug($"[StatueDecorationController] Decoration placed: {decoration.Data?.DecorationName} in slot {slot.name}");
|
||||||
|
|
||||||
|
// Auto-save state
|
||||||
|
SaveStatueState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Take photo of decorated statue
|
||||||
|
/// </summary>
|
||||||
|
private void OnTakePhoto()
|
||||||
|
{
|
||||||
|
if (_minigameCompleted)
|
||||||
|
{
|
||||||
|
Logging.Debug("[StatueDecorationController] Minigame already completed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug("[StatueDecorationController] Taking photo of statue");
|
||||||
|
|
||||||
|
// Hide UI elements
|
||||||
|
HideUIForPhoto(true);
|
||||||
|
|
||||||
|
// Wait a frame for UI to hide, then capture
|
||||||
|
StartCoroutine(CapturePhotoCoroutine());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Capture photo after UI is hidden
|
||||||
|
/// </summary>
|
||||||
|
private System.Collections.IEnumerator CapturePhotoCoroutine()
|
||||||
|
{
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
|
||||||
|
// Capture the photo area
|
||||||
|
Texture2D photo = CaptureScreenshotArea();
|
||||||
|
|
||||||
|
if (photo != null)
|
||||||
|
{
|
||||||
|
// Save photo to album
|
||||||
|
SavePhotoToAlbum(photo);
|
||||||
|
|
||||||
|
// Award cards
|
||||||
|
AwardCards();
|
||||||
|
|
||||||
|
// Update town icon
|
||||||
|
UpdateTownIcon(photo);
|
||||||
|
|
||||||
|
// Show completion feedback
|
||||||
|
ShowCompletionFeedback();
|
||||||
|
|
||||||
|
_minigameCompleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore UI
|
||||||
|
HideUIForPhoto(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Capture screenshot of specific area
|
||||||
|
/// </summary>
|
||||||
|
private Texture2D CaptureScreenshotArea()
|
||||||
|
{
|
||||||
|
if (photoArea == null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[StatueDecorationController] No photo area specified, capturing full screen");
|
||||||
|
|
||||||
|
// Capture full screen
|
||||||
|
Texture2D screenshot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
|
||||||
|
screenshot.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
|
||||||
|
screenshot.Apply();
|
||||||
|
return screenshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get world corners of the rect
|
||||||
|
Vector3[] corners = new Vector3[4];
|
||||||
|
photoArea.GetWorldCorners(corners);
|
||||||
|
|
||||||
|
// Convert to screen space
|
||||||
|
Vector2 min = RectTransformUtility.WorldToScreenPoint(Camera.main, corners[0]);
|
||||||
|
Vector2 max = RectTransformUtility.WorldToScreenPoint(Camera.main, corners[2]);
|
||||||
|
|
||||||
|
int width = (int)(max.x - min.x);
|
||||||
|
int height = (int)(max.y - min.y);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatueDecorationController] Capturing area: {width}x{height} at ({min.x}, {min.y})");
|
||||||
|
|
||||||
|
// Capture the specified area
|
||||||
|
Texture2D screenshot = new Texture2D(width, height, TextureFormat.RGB24, false);
|
||||||
|
screenshot.ReadPixels(new Rect(min.x, min.y, width, height), 0, 0);
|
||||||
|
screenshot.Apply();
|
||||||
|
|
||||||
|
return screenshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save photo to card album
|
||||||
|
/// </summary>
|
||||||
|
private void SavePhotoToAlbum(Texture2D photo)
|
||||||
|
{
|
||||||
|
// TODO: Integrate with existing album save system
|
||||||
|
// For now, save to PlayerPrefs as base64
|
||||||
|
byte[] bytes = photo.EncodeToPNG();
|
||||||
|
string base64 = System.Convert.ToBase64String(bytes);
|
||||||
|
PlayerPrefs.SetString(photoSaveKey, base64);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
|
||||||
|
Logging.Debug("[StatueDecorationController] Photo saved to album");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Award Blokkemon cards to player
|
||||||
|
/// </summary>
|
||||||
|
private void AwardCards()
|
||||||
|
{
|
||||||
|
// TODO: Integrate with MinigameBoosterGiver
|
||||||
|
// MinigameBoosterGiver.GiveBooster();
|
||||||
|
|
||||||
|
Logging.Debug("[StatueDecorationController] Cards awarded (TODO: implement)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update town menu icon with decorated statue
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateTownIcon(Texture2D photo)
|
||||||
|
{
|
||||||
|
// TODO: Integrate with town system
|
||||||
|
// TownIconUpdater.SetStatueIcon(photo);
|
||||||
|
|
||||||
|
Logging.Debug("[StatueDecorationController] Town icon updated (TODO: implement)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show completion feedback to player
|
||||||
|
/// </summary>
|
||||||
|
private void ShowCompletionFeedback()
|
||||||
|
{
|
||||||
|
// TODO: Show success message/animation
|
||||||
|
DebugUIMessage.Show("Photo captured! Mr. Cement looks amazing!", Color.green);
|
||||||
|
Logging.Debug("[StatueDecorationController] Minigame completed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hide/show UI elements for photo
|
||||||
|
/// </summary>
|
||||||
|
private void HideUIForPhoto(bool hide)
|
||||||
|
{
|
||||||
|
foreach (var element in uiElementsToHideForPhoto)
|
||||||
|
{
|
||||||
|
if (element != null)
|
||||||
|
{
|
||||||
|
element.SetActive(!hide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save current statue decoration state
|
||||||
|
/// </summary>
|
||||||
|
private void SaveStatueState()
|
||||||
|
{
|
||||||
|
// TODO: Implement save system
|
||||||
|
// Save slot ID -> decoration ID mapping
|
||||||
|
|
||||||
|
Logging.Debug("[StatueDecorationController] State saved (TODO: implement persistence)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load saved statue decoration state
|
||||||
|
/// </summary>
|
||||||
|
private void LoadStatueState()
|
||||||
|
{
|
||||||
|
// TODO: Implement load system
|
||||||
|
// Restore decorations to slots
|
||||||
|
|
||||||
|
Logging.Debug("[StatueDecorationController] State loaded (TODO: implement persistence)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
// Cleanup button listener
|
||||||
|
if (takePhotoButton != null)
|
||||||
|
{
|
||||||
|
takePhotoButton.onClick.RemoveListener(OnTakePhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup slot listeners
|
||||||
|
foreach (var slot in statueSlots)
|
||||||
|
{
|
||||||
|
if (slot != null)
|
||||||
|
{
|
||||||
|
slot.OnOccupied -= HandleDecorationPlaced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 19e312ceaffa40ae90ac87b8209319cb
|
||||||
|
timeCreated: 1763745610
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/Data.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/Data.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a6e7dfb0a39c441fb8ac888a5e58a91e
|
||||||
|
timeCreated: 1763745500
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ScriptableObject data definition for statue decorations
|
||||||
|
/// </summary>
|
||||||
|
[CreateAssetMenu(fileName = "DecorationData", menuName = "AppleHills/Minigames/Decoration Data", order = 1)]
|
||||||
|
public class DecorationData : ScriptableObject
|
||||||
|
{
|
||||||
|
[Header("Identity")]
|
||||||
|
[SerializeField] private string decorationId;
|
||||||
|
[SerializeField] private string decorationName;
|
||||||
|
|
||||||
|
[Header("Visual")]
|
||||||
|
[SerializeField] private Sprite decorationSprite;
|
||||||
|
|
||||||
|
[Header("Size Configuration")]
|
||||||
|
[Tooltip("Full size when placed on statue (actual sprite size)")]
|
||||||
|
[SerializeField] private Vector2 authoredSize = new Vector2(128f, 128f);
|
||||||
|
|
||||||
|
[Tooltip("Small size in menu icon")]
|
||||||
|
[SerializeField] private Vector2 iconSize = new Vector2(64f, 64f);
|
||||||
|
|
||||||
|
[Header("Progression (Optional)")]
|
||||||
|
[SerializeField] private bool isUnlocked = true;
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
public string DecorationId => decorationId;
|
||||||
|
public string DecorationName => decorationName;
|
||||||
|
public Sprite DecorationSprite => decorationSprite;
|
||||||
|
public DecorationCategory Category => category;
|
||||||
|
public Vector2 AuthoredSize => authoredSize;
|
||||||
|
public Vector2 IconSize => iconSize;
|
||||||
|
public bool IsUnlocked => isUnlocked;
|
||||||
|
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
// Auto-generate ID from name if empty
|
||||||
|
if (string.IsNullOrEmpty(decorationId) && !string.IsNullOrEmpty(decorationName))
|
||||||
|
{
|
||||||
|
decorationId = decorationName.Replace(" ", "_").ToLower();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 74c6ae9aa803480c8fb918dd58cfb809
|
||||||
|
timeCreated: 1763745511
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/DragDrop.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/DragDrop.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4c3389a935534b7b86800516ffa42acb
|
||||||
|
timeCreated: 1763745531
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using Core;
|
||||||
|
using Minigames.StatueDressup.Data;
|
||||||
|
using Minigames.StatueDressup.Utils;
|
||||||
|
using UI.DragAndDrop.Core;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.DragDrop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Individual decoration item that can be dragged from menu to statue slots
|
||||||
|
/// </summary>
|
||||||
|
public class DecorationItem : DraggableObject
|
||||||
|
{
|
||||||
|
[Header("Decoration Data")]
|
||||||
|
[SerializeField] private DecorationData decorationData;
|
||||||
|
[SerializeField] private Image decorationImage;
|
||||||
|
|
||||||
|
private Vector2 _iconSize;
|
||||||
|
private Vector2 _authoredSize;
|
||||||
|
private Vector2 _originalMenuPosition;
|
||||||
|
private bool _isInMenu = true;
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
public DecorationData Data => decorationData;
|
||||||
|
public DecorationCategory Category => decorationData?.Category ?? DecorationCategory.Hats;
|
||||||
|
public bool IsInMenu => _isInMenu;
|
||||||
|
|
||||||
|
protected override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
if (decorationData != null)
|
||||||
|
{
|
||||||
|
_iconSize = decorationData.IconSize;
|
||||||
|
_authoredSize = decorationData.AuthoredSize;
|
||||||
|
|
||||||
|
// Set initial icon size
|
||||||
|
if (RectTransform != null)
|
||||||
|
{
|
||||||
|
RectTransform.sizeDelta = _iconSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sprite
|
||||||
|
if (decorationImage != null && decorationData.DecorationSprite != null)
|
||||||
|
{
|
||||||
|
decorationImage.sprite = decorationData.DecorationSprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store original menu position
|
||||||
|
if (RectTransform != null)
|
||||||
|
{
|
||||||
|
_originalMenuPosition = RectTransform.anchoredPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set decoration data (for spawned instances)
|
||||||
|
/// </summary>
|
||||||
|
public void SetDecorationData(DecorationData data)
|
||||||
|
{
|
||||||
|
decorationData = data;
|
||||||
|
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
_iconSize = data.IconSize;
|
||||||
|
_authoredSize = data.AuthoredSize;
|
||||||
|
|
||||||
|
// Update visual
|
||||||
|
if (decorationImage != null && data.DecorationSprite != null)
|
||||||
|
{
|
||||||
|
decorationImage.sprite = data.DecorationSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set icon size
|
||||||
|
if (RectTransform != null)
|
||||||
|
{
|
||||||
|
RectTransform.sizeDelta = _iconSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[DecorationItem] Set data: {data.DecorationName}, iconSize={_iconSize}, authoredSize={_authoredSize}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragStartedHook()
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationItem] OnDragStarted: {decorationData?.DecorationName}");
|
||||||
|
|
||||||
|
// Scale to authored size when dragging starts
|
||||||
|
if (RectTransform != null)
|
||||||
|
{
|
||||||
|
TweenAnimationUtility.AnimateScale(transform, Vector3.one, 0.2f);
|
||||||
|
|
||||||
|
// Animate size delta to authored size
|
||||||
|
RectTransform.sizeDelta = _authoredSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEndedHook()
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationItem] OnDragEnded: {decorationData?.DecorationName}, currentSlot={CurrentSlot?.name}");
|
||||||
|
|
||||||
|
// If not placed in a slot, return to menu
|
||||||
|
if (CurrentSlot == null)
|
||||||
|
{
|
||||||
|
ReturnToMenu();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_isInMenu = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return item to menu with animation
|
||||||
|
/// </summary>
|
||||||
|
private void ReturnToMenu()
|
||||||
|
{
|
||||||
|
Logging.Debug($"[DecorationItem] Returning to menu: {decorationData?.DecorationName}");
|
||||||
|
|
||||||
|
_isInMenu = true;
|
||||||
|
|
||||||
|
if (RectTransform != null)
|
||||||
|
{
|
||||||
|
// Animate back to icon size
|
||||||
|
RectTransform.sizeDelta = _iconSize;
|
||||||
|
TweenAnimationUtility.AnimateScale(transform, Vector3.one, 0.2f);
|
||||||
|
|
||||||
|
// Animate back to original position
|
||||||
|
TweenAnimationUtility.AnimateAnchoredPosition(RectTransform, _originalMenuPosition, 0.3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set original menu position (called by menu controller)
|
||||||
|
/// </summary>
|
||||||
|
public void SetOriginalMenuPosition(Vector2 position)
|
||||||
|
{
|
||||||
|
_originalMenuPosition = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 31a82dde0ffb439e86b79499b9daa92b
|
||||||
|
timeCreated: 1763745531
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using Core;
|
||||||
|
using Minigames.StatueDressup.Data;
|
||||||
|
using Minigames.StatueDressup.Utils;
|
||||||
|
using UI.DragAndDrop.Core;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.DragDrop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Slot on the statue where decorations can be placed
|
||||||
|
/// </summary>
|
||||||
|
public class StatueDecorationSlot : DraggableSlot, IPointerEnterHandler, IPointerExitHandler
|
||||||
|
{
|
||||||
|
[Header("Slot Configuration")]
|
||||||
|
[SerializeField] private DecorationCategory allowedCategory;
|
||||||
|
[SerializeField] private bool isPermanent = true; // Can't remove once placed
|
||||||
|
|
||||||
|
[Header("Glow Effect")]
|
||||||
|
[SerializeField] private GameObject glowEffect;
|
||||||
|
[SerializeField] private float glowPulseAmount = 1.1f;
|
||||||
|
[SerializeField] private float glowPulseDuration = 0.8f;
|
||||||
|
|
||||||
|
private bool _isGlowing;
|
||||||
|
private Pixelplacement.TweenSystem.TweenBase _glowTween;
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
public DecorationCategory AllowedCategory => allowedCategory;
|
||||||
|
public bool IsPermanent => isPermanent;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
// Hide glow effect initially
|
||||||
|
if (glowEffect != null)
|
||||||
|
{
|
||||||
|
glowEffect.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void OnPointerEnter(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
// Only glow when dragging a matching decoration
|
||||||
|
if (eventData.pointerDrag != null)
|
||||||
|
{
|
||||||
|
var decoration = eventData.pointerDrag.GetComponent<DecorationItem>();
|
||||||
|
if (decoration != null && decoration.Category == allowedCategory && !IsOccupied)
|
||||||
|
{
|
||||||
|
StartGlow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void OnPointerExit(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
StopGlow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start glow effect
|
||||||
|
/// </summary>
|
||||||
|
private void StartGlow()
|
||||||
|
{
|
||||||
|
if (_isGlowing || glowEffect == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isGlowing = true;
|
||||||
|
glowEffect.SetActive(true);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatueDecorationSlot] Starting glow on {name}");
|
||||||
|
|
||||||
|
// Pulse animation
|
||||||
|
_glowTween = TweenAnimationUtility.StartGlowPulse(glowEffect.transform, glowPulseAmount, glowPulseDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop glow effect
|
||||||
|
/// </summary>
|
||||||
|
private void StopGlow()
|
||||||
|
{
|
||||||
|
if (!_isGlowing || glowEffect == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isGlowing = false;
|
||||||
|
|
||||||
|
Logging.Debug($"[StatueDecorationSlot] Stopping glow on {name}");
|
||||||
|
|
||||||
|
// Stop pulse animation
|
||||||
|
if (_glowTween != null)
|
||||||
|
{
|
||||||
|
TweenAnimationUtility.StopTweens(glowEffect.transform);
|
||||||
|
_glowTween = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
glowEffect.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override to check category matching (uses base CanAccept)
|
||||||
|
/// </summary>
|
||||||
|
public new bool CanAccept(DraggableObject draggable)
|
||||||
|
{
|
||||||
|
// First check base conditions
|
||||||
|
if (!base.CanAccept(draggable))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Then check category matching
|
||||||
|
if (draggable is DecorationItem decoration)
|
||||||
|
{
|
||||||
|
bool matches = decoration.Category == allowedCategory;
|
||||||
|
Logging.Debug($"[StatueDecorationSlot] CanAccept: {decoration.Data?.DecorationName}, " +
|
||||||
|
$"category={decoration.Category}, allowed={allowedCategory}, matches={matches}");
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
// Clean up glow on disable
|
||||||
|
StopGlow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f68e3749518141b6bc818938dd8dc57d
|
||||||
|
timeCreated: 1763745550
|
||||||
3
Assets/Scripts/Minigames/StatueDressup/Utils.meta
Normal file
3
Assets/Scripts/Minigames/StatueDressup/Utils.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fe03648f638e4872abafaf49234a3f55
|
||||||
|
timeCreated: 1763745490
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
using Pixelplacement;
|
||||||
|
using Pixelplacement.TweenSystem;
|
||||||
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common animation utilities extracted from CardAnimator pattern.
|
||||||
|
/// Provides reusable tween animations for UI elements.
|
||||||
|
/// </summary>
|
||||||
|
public static class TweenAnimationUtility
|
||||||
|
{
|
||||||
|
#region Scale Animations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Animate scale to target value with ease in-out
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase AnimateScale(Transform transform, Vector3 targetScale, float duration, Action onComplete = null)
|
||||||
|
{
|
||||||
|
return Tween.LocalScale(transform, targetScale, duration, 0f, Tween.EaseInOut, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulse scale animation (scale up then back to original)
|
||||||
|
/// </summary>
|
||||||
|
public static void PulseScale(Transform transform, float pulseAmount = 1.1f, float duration = 0.2f, Action onComplete = null)
|
||||||
|
{
|
||||||
|
Vector3 originalScale = transform.localScale;
|
||||||
|
Vector3 pulseScale = originalScale * pulseAmount;
|
||||||
|
|
||||||
|
Tween.LocalScale(transform, pulseScale, duration, 0f, Tween.EaseOutBack,
|
||||||
|
completeCallback: () =>
|
||||||
|
{
|
||||||
|
Tween.LocalScale(transform, originalScale, duration, 0f, Tween.EaseInBack, completeCallback: onComplete);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pop-in animation (scale from 0 to target with overshoot)
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase PopIn(Transform transform, Vector3 targetScale, float duration = 0.5f, Action onComplete = null)
|
||||||
|
{
|
||||||
|
transform.localScale = Vector3.zero;
|
||||||
|
return Tween.LocalScale(transform, targetScale, duration, 0f, Tween.EaseOutBack, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pop-out animation (scale from current to 0)
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase PopOut(Transform transform, float duration = 0.3f, Action onComplete = null)
|
||||||
|
{
|
||||||
|
return Tween.LocalScale(transform, Vector3.zero, duration, 0f, Tween.EaseInBack, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Smooth scale transition with bounce
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase ScaleWithBounce(Transform transform, Vector3 targetScale, float duration, Action onComplete = null)
|
||||||
|
{
|
||||||
|
return Tween.LocalScale(transform, targetScale, duration, 0f, Tween.EaseOutBack, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Position Animations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Animate anchored position (for RectTransform UI elements)
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase AnimateAnchoredPosition(RectTransform rectTransform, Vector2 targetPosition, float duration, Action onComplete = null)
|
||||||
|
{
|
||||||
|
return Tween.AnchoredPosition(rectTransform, targetPosition, duration, 0f, Tween.EaseInOut, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Animate local position (for regular transforms)
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase AnimateLocalPosition(Transform transform, Vector3 targetPosition, float duration, Action onComplete = null)
|
||||||
|
{
|
||||||
|
return Tween.LocalPosition(transform, targetPosition, duration, 0f, Tween.EaseInOut, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move with bounce effect
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase MoveWithBounce(RectTransform rectTransform, Vector2 targetPosition, float duration, Action onComplete = null)
|
||||||
|
{
|
||||||
|
return Tween.AnchoredPosition(rectTransform, targetPosition, duration, 0f, Tween.EaseOutBack, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Combined Hover Animations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hover enter animation (lift and scale) for RectTransform
|
||||||
|
/// </summary>
|
||||||
|
public static void HoverEnter(RectTransform rectTransform, Vector2 originalPosition, float liftAmount = 20f,
|
||||||
|
float scaleMultiplier = 1.05f, float duration = 0.2f, Action onComplete = null)
|
||||||
|
{
|
||||||
|
Vector2 targetPos = originalPosition + Vector2.up * liftAmount;
|
||||||
|
|
||||||
|
Tween.AnchoredPosition(rectTransform, targetPos, duration, 0f, Tween.EaseOutBack);
|
||||||
|
Tween.LocalScale(rectTransform, Vector3.one * scaleMultiplier, duration, 0f, Tween.EaseOutBack, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hover exit animation (return to original position and scale) for RectTransform
|
||||||
|
/// </summary>
|
||||||
|
public static void HoverExit(RectTransform rectTransform, Vector2 originalPosition, float duration = 0.2f, Action onComplete = null)
|
||||||
|
{
|
||||||
|
Tween.AnchoredPosition(rectTransform, originalPosition, duration, 0f, Tween.EaseInBack);
|
||||||
|
Tween.LocalScale(rectTransform, Vector3.one, duration, 0f, Tween.EaseInBack, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Glow pulse effect (scale up/down repeatedly)
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase StartGlowPulse(Transform transform, float pulseAmount = 1.1f, float duration = 0.8f)
|
||||||
|
{
|
||||||
|
Vector3 originalScale = transform.localScale;
|
||||||
|
Vector3 pulseScale = originalScale * pulseAmount;
|
||||||
|
|
||||||
|
return Tween.LocalScale(transform, pulseScale, duration, 0f, Tween.EaseInOutSine, Tween.LoopType.PingPong);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop any active tweens on transform
|
||||||
|
/// </summary>
|
||||||
|
public static void StopTweens(Transform transform)
|
||||||
|
{
|
||||||
|
Tween.Cancel(transform.GetInstanceID());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fade Animations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fade CanvasGroup alpha
|
||||||
|
/// </summary>
|
||||||
|
public static TweenBase FadeCanvasGroup(CanvasGroup canvasGroup, float targetAlpha, float duration, Action onComplete = null)
|
||||||
|
{
|
||||||
|
return Tween.CanvasGroupAlpha(canvasGroup, targetAlpha, duration, 0f, Tween.EaseInOut, completeCallback: onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: abd48147eff149508890fe2fa87b8421
|
||||||
|
timeCreated: 1763745490
|
||||||
Reference in New Issue
Block a user