First pass over MPV for the cement-statue-sticker minigame (#63)
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Reviewed-on: #63
This commit is contained in:
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,333 @@
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
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 : ManagedBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private DecorationGridIcon iconPrefab;
|
||||
[SerializeField] private DecorationDraggableInstance draggablePrefab;
|
||||
[SerializeField] private Transform itemsContainer;
|
||||
[SerializeField] private Transform draggableContainer; // Parent for spawned draggables
|
||||
[SerializeField] private Button nextPageButton;
|
||||
[SerializeField] private Button previousPageButton;
|
||||
[SerializeField] private StatueDecorationController statueController; // Controller for registration
|
||||
[SerializeField] private Image statueOutline; // Outline image shown during drag to indicate valid drop area
|
||||
|
||||
[Header("Layout")]
|
||||
[SerializeField] private GridLayoutGroup gridLayout;
|
||||
|
||||
private int _currentPage;
|
||||
private int _totalPages;
|
||||
private List<DecorationGridIcon> _spawnedIcons = new List<DecorationGridIcon>();
|
||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
||||
|
||||
// Properties
|
||||
public int CurrentPage => _currentPage;
|
||||
public int TotalPages => _totalPages;
|
||||
|
||||
/// <summary>
|
||||
/// Early initialization - get settings reference
|
||||
/// </summary>
|
||||
internal override void OnManagedAwake()
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
// Get settings early
|
||||
_settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
|
||||
|
||||
if (_settings == null)
|
||||
{
|
||||
Logging.Error("[DecorationMenuController] Failed to load StatueDressupSettings!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main initialization after all managers are ready
|
||||
/// </summary>
|
||||
internal override void OnManagedStart()
|
||||
{
|
||||
base.OnManagedStart();
|
||||
|
||||
if (_settings == null)
|
||||
{
|
||||
Logging.Error("[DecorationMenuController] Cannot initialize without settings!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure outline starts hidden
|
||||
if (statueOutline != null)
|
||||
{
|
||||
statueOutline.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
var allDecorations = _settings.AllDecorations;
|
||||
int itemsPerPage = _settings.ItemsPerPage;
|
||||
|
||||
Logging.Debug($"[DecorationMenuController] Initializing with {allDecorations?.Count ?? 0} decorations");
|
||||
|
||||
// Calculate total pages
|
||||
if (allDecorations != null && allDecorations.Count > 0)
|
||||
{
|
||||
_totalPages = Mathf.CeilToInt((float)allDecorations.Count / itemsPerPage);
|
||||
Logging.Debug($"[DecorationMenuController] Total pages: {_totalPages}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning("[DecorationMenuController] No decorations found in settings!");
|
||||
_totalPages = 0;
|
||||
}
|
||||
|
||||
// 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 icons
|
||||
/// </summary>
|
||||
private void PopulateCurrentPage()
|
||||
{
|
||||
if (_settings == null) return;
|
||||
|
||||
var allDecorations = _settings.AllDecorations;
|
||||
int itemsPerPage = _settings.ItemsPerPage;
|
||||
|
||||
if (allDecorations == null || allDecorations.Count == 0)
|
||||
{
|
||||
Logging.Warning("[DecorationMenuController] No decorations to populate");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationMenuController] Populating page {_currentPage + 1}/{_totalPages}");
|
||||
|
||||
// Clear existing icons
|
||||
ClearIcons();
|
||||
|
||||
// Calculate range for current page
|
||||
int startIndex = _currentPage * itemsPerPage;
|
||||
int endIndex = Mathf.Min(startIndex + itemsPerPage, allDecorations.Count);
|
||||
|
||||
Logging.Debug($"[DecorationMenuController] Spawning icons {startIndex} to {endIndex - 1}");
|
||||
|
||||
// Spawn icons for this page
|
||||
for (int i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
SpawnGridIcon(allDecorations[i]);
|
||||
}
|
||||
|
||||
// Update button states
|
||||
UpdateNavigationButtons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a grid icon in the menu
|
||||
/// </summary>
|
||||
private void SpawnGridIcon(DecorationData data)
|
||||
{
|
||||
if (iconPrefab == null || itemsContainer == null)
|
||||
{
|
||||
Logging.Warning("[DecorationMenuController] Missing icon prefab or container");
|
||||
return;
|
||||
}
|
||||
|
||||
DecorationGridIcon icon = Instantiate(iconPrefab, itemsContainer);
|
||||
icon.Initialize(data, this);
|
||||
|
||||
_spawnedIcons.Add(icon);
|
||||
|
||||
Logging.Debug($"[DecorationMenuController] Spawned icon: {data.DecorationName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory method: Spawn a draggable instance at cursor position
|
||||
/// Called by DecorationGridIcon when drag starts
|
||||
/// </summary>
|
||||
public DecorationDraggableInstance SpawnDraggableInstance(DecorationData data, Vector3 screenPosition)
|
||||
{
|
||||
if (draggablePrefab == null || statueController == null)
|
||||
{
|
||||
Logging.Warning("[DecorationMenuController] Missing draggable prefab or statue controller");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Show statue outline
|
||||
ShowStatueOutline();
|
||||
|
||||
// Determine parent - use draggableContainer if set, otherwise itemsContainer's parent
|
||||
Transform parent = draggableContainer != null ? draggableContainer : itemsContainer.parent;
|
||||
|
||||
// Spawn draggable instance
|
||||
DecorationDraggableInstance instance = Instantiate(draggablePrefab, parent);
|
||||
|
||||
// Get outline RectTransform for overlap detection
|
||||
RectTransform outlineRect = statueOutline != null ? statueOutline.rectTransform : null;
|
||||
|
||||
// Initialize with references
|
||||
instance.Initialize(
|
||||
data,
|
||||
outlineRect,
|
||||
statueController.StatueParent,
|
||||
statueController,
|
||||
_settings,
|
||||
OnDraggableFinished
|
||||
);
|
||||
|
||||
// Position at cursor (in local space)
|
||||
Canvas canvas = GetComponentInParent<Canvas>();
|
||||
if (canvas != null)
|
||||
{
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvas.transform as RectTransform,
|
||||
screenPosition,
|
||||
canvas.worldCamera,
|
||||
out Vector2 localPoint);
|
||||
|
||||
RectTransform instanceRect = instance.GetComponent<RectTransform>();
|
||||
if (instanceRect != null)
|
||||
{
|
||||
instanceRect.localPosition = localPoint;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationMenuController] Spawned draggable instance: {data.DecorationName}");
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the statue outline to indicate valid drop area
|
||||
/// </summary>
|
||||
private void ShowStatueOutline()
|
||||
{
|
||||
if (statueOutline != null)
|
||||
{
|
||||
statueOutline.gameObject.SetActive(true);
|
||||
Logging.Debug("[DecorationMenuController] Statue outline shown");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the statue outline after drag ends
|
||||
/// </summary>
|
||||
private void HideStatueOutline()
|
||||
{
|
||||
if (statueOutline != null)
|
||||
{
|
||||
statueOutline.gameObject.SetActive(false);
|
||||
Logging.Debug("[DecorationMenuController] Statue outline hidden");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback from DecorationDraggableInstance when drag finishes
|
||||
/// </summary>
|
||||
private void OnDraggableFinished()
|
||||
{
|
||||
HideStatueOutline();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all spawned icons
|
||||
/// </summary>
|
||||
private void ClearIcons()
|
||||
{
|
||||
foreach (var icon in _spawnedIcons)
|
||||
{
|
||||
if (icon != null)
|
||||
{
|
||||
Destroy(icon.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
_spawnedIcons.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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup when menu controller is destroyed
|
||||
/// </summary>
|
||||
internal override void OnManagedDestroy()
|
||||
{
|
||||
base.OnManagedDestroy();
|
||||
|
||||
// Cleanup button listeners
|
||||
if (nextPageButton != null)
|
||||
{
|
||||
nextPageButton.onClick.RemoveListener(OnNextPage);
|
||||
}
|
||||
|
||||
if (previousPageButton != null)
|
||||
{
|
||||
previousPageButton.onClick.RemoveListener(OnPreviousPage);
|
||||
}
|
||||
|
||||
// Cleanup icons
|
||||
ClearIcons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acbd542762b44e719326dff6c3a69e6e
|
||||
timeCreated: 1763745579
|
||||
@@ -0,0 +1,307 @@
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Minigames.StatueDressup.DragDrop;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Minigames.StatueDressup.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Main controller for the Mr. Cement statue decoration minigame
|
||||
/// Uses overlap-based placement instead of slots
|
||||
/// </summary>
|
||||
public class StatueDecorationController : ManagedBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private RectTransform statueArea; // Statue area for overlap detection
|
||||
[SerializeField] private Transform statueParent; // Parent for placed decorations
|
||||
[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 List<DecorationDraggableInstance> _placedDecorations = new List<DecorationDraggableInstance>();
|
||||
private bool _minigameCompleted;
|
||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
||||
|
||||
// Public property for menu controller
|
||||
public Transform StatueParent => statueParent;
|
||||
|
||||
/// <summary>
|
||||
/// Early initialization - get settings reference
|
||||
/// </summary>
|
||||
internal override void OnManagedAwake()
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
// Get settings early
|
||||
_settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main initialization after all managers are ready
|
||||
/// </summary>
|
||||
internal override void OnManagedStart()
|
||||
{
|
||||
base.OnManagedStart();
|
||||
|
||||
Logging.Debug("[StatueDecorationController] Initializing minigame");
|
||||
|
||||
// Setup photo button
|
||||
if (takePhotoButton != null)
|
||||
{
|
||||
takePhotoButton.onClick.AddListener(OnTakePhoto);
|
||||
}
|
||||
|
||||
// Subscribe to menu controller for tracking placed items
|
||||
// Items will manage their own placement via overlap detection
|
||||
if (menuController != null)
|
||||
{
|
||||
// Menu controller will handle spawning replacements
|
||||
Logging.Debug("[StatueDecorationController] Menu controller connected");
|
||||
}
|
||||
|
||||
// Load saved state if exists
|
||||
LoadStatueState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a decoration as placed on statue
|
||||
/// </summary>
|
||||
public void RegisterDecoration(DecorationDraggableInstance decoration)
|
||||
{
|
||||
if (decoration != null && !_placedDecorations.Contains(decoration))
|
||||
{
|
||||
_placedDecorations.Add(decoration);
|
||||
Logging.Debug($"[StatueDecorationController] Decoration placed: {decoration.Data?.DecorationName}");
|
||||
|
||||
// Auto-save state
|
||||
SaveStatueState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a decoration (when removed)
|
||||
/// </summary>
|
||||
public void UnregisterDecoration(DecorationDraggableInstance decoration)
|
||||
{
|
||||
if (decoration != null && _placedDecorations.Contains(decoration))
|
||||
{
|
||||
_placedDecorations.Remove(decoration);
|
||||
Logging.Debug($"[StatueDecorationController] Decoration removed: {decoration.Data?.DecorationName}");
|
||||
|
||||
// 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 areaScreenshot = new Texture2D(width, height, TextureFormat.RGB24, false);
|
||||
areaScreenshot.ReadPixels(new Rect(min.x, min.y, width, height), 0, 0);
|
||||
areaScreenshot.Apply();
|
||||
|
||||
return areaScreenshot;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
string saveKey = _settings?.PhotoSaveKey ?? photoSaveKey;
|
||||
PlayerPrefs.SetString(saveKey, 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()
|
||||
{
|
||||
// Check if persistence is enabled
|
||||
if (_settings == null || !_settings.EnableStatePersistence)
|
||||
{
|
||||
Logging.Debug("[StatueDecorationController] State persistence disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement save system
|
||||
// Save decoration ID + position + rotation for each placed item
|
||||
// Respect MaxSavedDecorations limit
|
||||
|
||||
Logging.Debug($"[StatueDecorationController] State saved to {_settings.StateSaveKey} (TODO: implement persistence)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load saved statue decoration state
|
||||
/// </summary>
|
||||
private void LoadStatueState()
|
||||
{
|
||||
// Check if persistence is enabled
|
||||
if (_settings == null || !_settings.EnableStatePersistence)
|
||||
{
|
||||
Logging.Debug("[StatueDecorationController] State persistence disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement load system
|
||||
// Restore decorations from saved state
|
||||
|
||||
Logging.Debug($"[StatueDecorationController] State loaded from {_settings.StateSaveKey} (TODO: implement persistence)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup when controller is destroyed
|
||||
/// </summary>
|
||||
internal override void OnManagedDestroy()
|
||||
{
|
||||
base.OnManagedDestroy();
|
||||
|
||||
// Cleanup button listener
|
||||
if (takePhotoButton != null)
|
||||
{
|
||||
takePhotoButton.onClick.RemoveListener(OnTakePhoto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,42 @@
|
||||
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);
|
||||
|
||||
[Header("Progression (Optional)")]
|
||||
[SerializeField] private bool isUnlocked = true;
|
||||
|
||||
// Properties
|
||||
public string DecorationId => decorationId;
|
||||
public string DecorationName => decorationName;
|
||||
public Sprite DecorationSprite => decorationSprite;
|
||||
public Vector2 AuthoredSize => authoredSize;
|
||||
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,242 @@
|
||||
using Core;
|
||||
using Minigames.StatueDressup.Controllers;
|
||||
using Minigames.StatueDressup.Data;
|
||||
using Minigames.StatueDressup.Utils;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Minigames.StatueDressup.DragDrop
|
||||
{
|
||||
/// <summary>
|
||||
/// Draggable instance of a decoration that can be placed on the statue.
|
||||
/// Created dynamically when dragging from menu or picking up from statue.
|
||||
/// Destroyed if dropped outside statue area.
|
||||
/// </summary>
|
||||
public class DecorationDraggableInstance : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private Image decorationImage;
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
|
||||
private DecorationData _decorationData;
|
||||
private RectTransform _rectTransform;
|
||||
private Canvas _canvas;
|
||||
private RectTransform _statueOutline;
|
||||
private Transform _statueParent;
|
||||
private StatueDecorationController _controller;
|
||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
||||
private System.Action _onFinishedCallback;
|
||||
|
||||
private bool _isDragging;
|
||||
private bool _isPlacedOnStatue;
|
||||
private Vector3 _dragOffset;
|
||||
|
||||
// Properties
|
||||
public DecorationData Data => _decorationData;
|
||||
public bool IsPlacedOnStatue => _isPlacedOnStatue;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_rectTransform = GetComponent<RectTransform>();
|
||||
_canvas = GetComponentInParent<Canvas>();
|
||||
|
||||
if (canvasGroup == null)
|
||||
{
|
||||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the draggable instance
|
||||
/// </summary>
|
||||
public void Initialize(DecorationData data, RectTransform statueOutline, Transform statueParent,
|
||||
StatueDecorationController controller, AppleHills.Core.Settings.IStatueDressupSettings settings,
|
||||
System.Action onFinishedCallback)
|
||||
{
|
||||
_decorationData = data;
|
||||
_statueOutline = statueOutline;
|
||||
_statueParent = statueParent;
|
||||
_controller = controller;
|
||||
_settings = settings;
|
||||
_onFinishedCallback = onFinishedCallback;
|
||||
|
||||
// Set sprite
|
||||
if (decorationImage != null && data != null && data.DecorationSprite != null)
|
||||
{
|
||||
decorationImage.sprite = data.DecorationSprite;
|
||||
}
|
||||
|
||||
// Set authored size
|
||||
if (_rectTransform != null && data != null)
|
||||
{
|
||||
_rectTransform.sizeDelta = data.AuthoredSize;
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationDraggableInstance] Initialized: {data?.DecorationName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start dragging from icon
|
||||
/// </summary>
|
||||
public void StartDragFromIcon(PointerEventData eventData)
|
||||
{
|
||||
_isDragging = true;
|
||||
|
||||
// Calculate offset from cursor to object center
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
_canvas.transform as RectTransform,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out Vector2 localPoint);
|
||||
|
||||
_dragOffset = _rectTransform.localPosition - (Vector3)localPoint;
|
||||
|
||||
Logging.Debug($"[DecorationDraggableInstance] Started drag from icon: {_decorationData?.DecorationName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continue dragging
|
||||
/// </summary>
|
||||
public void ContinueDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!_isDragging) return;
|
||||
|
||||
// Update position to follow cursor
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
_canvas.transform as RectTransform,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out Vector2 localPoint);
|
||||
|
||||
_rectTransform.localPosition = localPoint + (Vector2)_dragOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End drag - check placement
|
||||
/// </summary>
|
||||
public void EndDrag(PointerEventData eventData)
|
||||
{
|
||||
_isDragging = false;
|
||||
|
||||
Logging.Debug($"[DecorationDraggableInstance] Drag ended: {_decorationData?.DecorationName}");
|
||||
|
||||
// Check if overlapping with statue
|
||||
if (IsOverlappingStatue())
|
||||
{
|
||||
PlaceOnStatue();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayPopOutAndDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if item overlaps with statue outline
|
||||
/// </summary>
|
||||
private bool IsOverlappingStatue()
|
||||
{
|
||||
if (_statueOutline == null || _rectTransform == null)
|
||||
{
|
||||
Logging.Warning($"[DecorationDraggableInstance] Cannot check overlap - statueOutline or RectTransform is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get bounds of this item in world space
|
||||
Rect itemRect = GetWorldRect(_rectTransform);
|
||||
Rect outlineRect = GetWorldRect(_statueOutline);
|
||||
|
||||
// Check for any overlap
|
||||
bool overlaps = itemRect.Overlaps(outlineRect);
|
||||
|
||||
Logging.Debug($"[DecorationDraggableInstance] Overlap check: {_decorationData?.DecorationName}, overlaps={overlaps}");
|
||||
|
||||
return overlaps;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get world space rect for a RectTransform
|
||||
/// </summary>
|
||||
private Rect GetWorldRect(RectTransform rectTransform)
|
||||
{
|
||||
Vector3[] corners = new Vector3[4];
|
||||
rectTransform.GetWorldCorners(corners);
|
||||
|
||||
Vector3 bottomLeft = corners[0];
|
||||
Vector3 topRight = corners[2];
|
||||
|
||||
return new Rect(bottomLeft.x, bottomLeft.y, topRight.x - bottomLeft.x, topRight.y - bottomLeft.y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place item on statue at current position
|
||||
/// </summary>
|
||||
private void PlaceOnStatue()
|
||||
{
|
||||
Logging.Debug($"[DecorationDraggableInstance] Placing on statue: {_decorationData?.DecorationName}");
|
||||
|
||||
_isPlacedOnStatue = true;
|
||||
|
||||
// Move to statue parent if specified
|
||||
if (_statueParent != null && transform.parent != _statueParent)
|
||||
{
|
||||
transform.SetParent(_statueParent, true); // Keep world position
|
||||
}
|
||||
|
||||
// Register with controller
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.RegisterDecoration(this);
|
||||
}
|
||||
|
||||
// Notify menu controller to hide outline
|
||||
_onFinishedCallback?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play pop-out animation and destroy
|
||||
/// </summary>
|
||||
private void PlayPopOutAndDestroy()
|
||||
{
|
||||
Logging.Debug($"[DecorationDraggableInstance] Pop-out and destroy: {_decorationData?.DecorationName}");
|
||||
|
||||
// Notify menu controller to hide outline immediately
|
||||
_onFinishedCallback?.Invoke();
|
||||
|
||||
float duration = _settings?.PlacementAnimationDuration ?? 0.3f;
|
||||
|
||||
// Play pop-out with fade animation
|
||||
TweenAnimationUtility.PopOutWithFade(transform, canvasGroup, duration, () =>
|
||||
{
|
||||
Destroy(gameObject);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow picking up from statue for repositioning
|
||||
/// </summary>
|
||||
public void StartDragFromStatue(Vector3 pointerPosition)
|
||||
{
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.UnregisterDecoration(this);
|
||||
}
|
||||
|
||||
_isPlacedOnStatue = false;
|
||||
_isDragging = true;
|
||||
|
||||
// Calculate offset
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
_canvas.transform as RectTransform,
|
||||
pointerPosition,
|
||||
null,
|
||||
out Vector2 localPoint);
|
||||
|
||||
_dragOffset = _rectTransform.localPosition - (Vector3)localPoint;
|
||||
|
||||
Logging.Debug($"[DecorationDraggableInstance] Started drag from statue: {_decorationData?.DecorationName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4659fd035c74a79af0311de9e17f44a
|
||||
timeCreated: 1763991638
|
||||
@@ -0,0 +1,100 @@
|
||||
using Core;
|
||||
using Minigames.StatueDressup.Data;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Minigames.StatueDressup.DragDrop
|
||||
{
|
||||
/// <summary>
|
||||
/// Static grid icon for decorations in the menu.
|
||||
/// Handles tap and drag initiation, but doesn't move itself.
|
||||
/// Spawns a draggable instance when drag starts.
|
||||
/// </summary>
|
||||
public class DecorationGridIcon : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private Image iconImage;
|
||||
[SerializeField] private DecorationData decorationData;
|
||||
|
||||
private Controllers.DecorationMenuController _menuController;
|
||||
private DecorationDraggableInstance _activeDraggableInstance;
|
||||
|
||||
// Properties
|
||||
public DecorationData Data => decorationData;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the icon with decoration data
|
||||
/// </summary>
|
||||
public void Initialize(DecorationData data, Controllers.DecorationMenuController controller)
|
||||
{
|
||||
decorationData = data;
|
||||
_menuController = controller;
|
||||
|
||||
if (iconImage != null && data != null && data.DecorationSprite != null)
|
||||
{
|
||||
iconImage.sprite = data.DecorationSprite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle tap/click on icon
|
||||
/// </summary>
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
// Only process clicks if we're not dragging
|
||||
if (_activeDraggableInstance == null)
|
||||
{
|
||||
Logging.Debug($"[DecorationGridIcon] Item tapped: {decorationData?.DecorationName}");
|
||||
// Future: Open detail view, preview, etc.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle drag start - spawn draggable instance
|
||||
/// </summary>
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_menuController == null || decorationData == null)
|
||||
{
|
||||
Logging.Warning("[DecorationGridIcon] Cannot start drag - missing controller or data");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationGridIcon] Starting drag for: {decorationData.DecorationName}");
|
||||
|
||||
// Spawn draggable instance at cursor position
|
||||
_activeDraggableInstance = _menuController.SpawnDraggableInstance(decorationData, eventData.position);
|
||||
|
||||
// Start the drag on the spawned instance
|
||||
if (_activeDraggableInstance != null)
|
||||
{
|
||||
_activeDraggableInstance.StartDragFromIcon(eventData);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forward drag events to the active draggable instance
|
||||
/// </summary>
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_activeDraggableInstance != null)
|
||||
{
|
||||
_activeDraggableInstance.ContinueDrag(eventData);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forward drag end to the active draggable instance
|
||||
/// </summary>
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_activeDraggableInstance != null)
|
||||
{
|
||||
_activeDraggableInstance.EndDrag(eventData);
|
||||
_activeDraggableInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c806d80a321498c9f33f13d7a31065c
|
||||
timeCreated: 1763991611
|
||||
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,171 @@
|
||||
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.EaseIn, 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pop-out with fade - scale to 0 and fade out simultaneously
|
||||
/// </summary>
|
||||
public static void PopOutWithFade(Transform transform, CanvasGroup canvasGroup, float duration, Action onComplete = null)
|
||||
{
|
||||
// Scale to 0
|
||||
Tween.LocalScale(transform, Vector3.zero, duration, 0f, Tween.EaseInBack);
|
||||
|
||||
// Fade out simultaneously
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
Tween.CanvasGroupAlpha(canvasGroup, 0f, duration, 0f, Tween.EaseInOut, completeCallback: onComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no canvas group, just call complete after scale
|
||||
Tween.LocalScale(transform, Vector3.zero, duration, 0f, Tween.EaseInBack, completeCallback: onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abd48147eff149508890fe2fa87b8421
|
||||
timeCreated: 1763745490
|
||||
Reference in New Issue
Block a user