Add backbone for card creation and implement Camera minigame mechanics

This commit is contained in:
Michal Pikulski
2025-10-10 14:31:51 +02:00
parent d9039ce655
commit 0c5546efd2
107 changed files with 10490 additions and 280 deletions

View File

@@ -0,0 +1,343 @@
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
using Pixelplacement;
using UnityEngine;
using UnityEngine.UI;
namespace AppleHills.UI.CardSystem
{
/// <summary>
/// UI page for viewing and organizing the player's card collection in an album.
/// </summary>
public class AlbumViewPage : UIPage
{
[Header("Album UI Elements")]
[SerializeField] private GridLayoutGroup albumGrid;
[SerializeField] private RectTransform cardStackContainer;
[SerializeField] private GameObject emptyAlbumMessage;
[SerializeField] private GameObject cardSlotPrefab;
[SerializeField] private GameObject cardPrefab;
// Runtime references
private CardSystemManager _cardManager;
private List<CardUIElement> _displayedCards = new List<CardUIElement>();
private Dictionary<int, Transform> _albumSlots = new Dictionary<int, Transform>();
// Drag and drop handling
private CardUIElement _currentlyDraggedCard = null;
private Vector3 _cardOriginalPosition;
private void Awake()
{
_cardManager = CardSystemManager.Instance;
}
/// <summary>
/// Sets up the album when the page becomes active
/// </summary>
public override void TransitionIn()
{
base.TransitionIn();
// Initialize the album when the page becomes active
InitializeAlbum();
}
/// <summary>
/// Initializes the album with card slots and the player's collection
/// </summary>
private void InitializeAlbum()
{
// Clear any previous setup
ClearAlbum();
// Get all collected cards
List<CardData> collectedCards = _cardManager.GetAllCollectedCards();
// Show/hide empty message based on collection
if (emptyAlbumMessage != null)
{
emptyAlbumMessage.SetActive(collectedCards.Count == 0);
}
if (collectedCards.Count == 0)
{
return;
}
// Set up the album slots
SetupAlbumSlots();
// Create card UI elements for the stack
CreateCardStack(collectedCards);
}
/// <summary>
/// Sets up empty slots in the album grid
/// </summary>
private void SetupAlbumSlots()
{
if (albumGrid == null || cardSlotPrefab == null) return;
// Create predefined slots in the album
// For a simple implementation, we'll create a 5x5 grid of slots
int slotsPerZone = 5; // 5 slots per zone (one row)
int totalZones = System.Enum.GetValues(typeof(CardZone)).Length;
for (int zone = 0; zone < totalZones; zone++)
{
for (int i = 0; i < slotsPerZone; i++)
{
// Create a slot at this position
GameObject slotObj = Instantiate(cardSlotPrefab, albumGrid.transform);
// Calculate the collection index for this slot
int collectionIndex = zone * 100 + i; // Zone*100 + position to ensure unique indices
// Store the slot reference
_albumSlots[collectionIndex] = slotObj.transform;
// Set the slot label (optional)
Text slotLabel = slotObj.GetComponentInChildren<Text>();
if (slotLabel != null)
{
CardZone zoneEnum = (CardZone)zone;
slotLabel.text = $"{zoneEnum} #{i+1}";
}
}
}
}
/// <summary>
/// Creates UI elements for the player's collected cards
/// </summary>
private void CreateCardStack(List<CardData> cards)
{
if (cardStackContainer == null || cardPrefab == null) return;
// Stack offset for visual effect
Vector3 stackOffset = new Vector3(5f, -5f, 0f);
Vector3 basePosition = Vector3.zero;
// Sort cards by collection index
cards.Sort((a, b) => a.CollectionIndex.CompareTo(b.CollectionIndex));
// Create card UI elements
for (int i = 0; i < cards.Count; i++)
{
GameObject cardObj = Instantiate(cardPrefab, cardStackContainer);
CardUIElement cardUI = cardObj.GetComponent<CardUIElement>();
if (cardUI != null)
{
// Position in stack
cardObj.GetComponent<RectTransform>().anchoredPosition = basePosition + (stackOffset * i);
// Set up card data
cardUI.SetupCard(cards[i]);
// Add drag handlers
SetupCardDragHandlers(cardUI);
// Add to tracked cards
_displayedCards.Add(cardUI);
// Check if this card should be placed in a slot already
int collectionIndex = cards[i].CollectionIndex;
if (_albumSlots.TryGetValue(collectionIndex, out Transform slot))
{
// Card has a designated slot, place it there
PlaceCardInSlot(cardUI, slot);
}
}
}
}
/// <summary>
/// Sets up drag and drop handlers for a card
/// </summary>
private void SetupCardDragHandlers(CardUIElement cardUI)
{
// // Get drag handler component (you might need to implement this)
// DragHandler dragHandler = cardUI.GetComponent<DragHandler>();
// if (dragHandler == null)
// {
// // This is a stub for the drag handler
// // In a real implementation, you'd have a proper drag handler component
// // For now, we'll just add click listeners
//
// // Add click listener
// Button cardButton = cardUI.GetComponent<Button>();
// if (cardButton != null)
// {
// cardButton.onClick.AddListener(() => OnCardClicked(cardUI));
// }
// }
// else
// {
// // Set up the drag handler events
// dragHandler.OnBeginDrag.AddListener(() => OnBeginDragCard(cardUI));
// dragHandler.OnDrag.AddListener((position) => OnDragCard(cardUI, position));
// dragHandler.OnEndDrag.AddListener(() => OnEndDragCard(cardUI));
// }
}
/// <summary>
/// Handles when a card is clicked (simplistic alternative to drag and drop)
/// </summary>
private void OnCardClicked(CardUIElement cardUI)
{
CardData cardData = cardUI.GetCardData();
// Find the slot for this card based on collection index
if (_albumSlots.TryGetValue(cardData.CollectionIndex, out Transform slot))
{
// Place the card in its slot
PlaceCardInSlot(cardUI, slot);
}
}
/// <summary>
/// Handles the start of a card drag operation
/// </summary>
private void OnBeginDragCard(CardUIElement cardUI)
{
_currentlyDraggedCard = cardUI;
_cardOriginalPosition = cardUI.transform.position;
// Bring the card to the front
cardUI.transform.SetAsLastSibling();
}
/// <summary>
/// Handles the dragging of a card
/// </summary>
private void OnDragCard(CardUIElement cardUI, Vector3 position)
{
cardUI.transform.position = position;
}
/// <summary>
/// Handles the end of a card drag operation
/// </summary>
private void OnEndDragCard(CardUIElement cardUI)
{
// Check if the card is over a valid slot
Transform closestSlot = FindClosestSlot(cardUI.transform.position);
if (closestSlot != null)
{
// Place the card in the slot
PlaceCardInSlot(cardUI, closestSlot);
}
else
{
// Return the card to its original position
cardUI.transform.position = _cardOriginalPosition;
}
_currentlyDraggedCard = null;
}
/// <summary>
/// Places a card in an album slot
/// </summary>
private void PlaceCardInSlot(CardUIElement cardUI, Transform slot)
{
// Reparent the card to the slot
cardUI.transform.SetParent(slot);
// Animate card to center of slot using Pixelplacement.Tween
Tween.LocalPosition(cardUI.transform, Vector3.zero, 0.25f, 0f, Tween.EaseOutBack);
Debug.Log($"[AlbumViewPage] Placed card '{cardUI.GetCardData().Name}' in album slot");
}
/// <summary>
/// Finds the closest album slot to a given position
/// </summary>
private Transform FindClosestSlot(Vector3 position)
{
Transform closest = null;
float closestDistance = 100f; // Large initial value
foreach (var slot in _albumSlots.Values)
{
float distance = Vector3.Distance(position, slot.position);
if (distance < closestDistance && distance < 50f) // Only if within reasonable range
{
closestDistance = distance;
closest = slot;
}
}
return closest;
}
/// <summary>
/// Clears the album UI
/// </summary>
private void ClearAlbum()
{
// Clear displayed cards
foreach (var card in _displayedCards)
{
Destroy(card.gameObject);
}
_displayedCards.Clear();
// Clear album slots
foreach (Transform child in albumGrid.transform)
{
Destroy(child.gameObject);
}
_albumSlots.Clear();
}
/// <summary>
/// Override for transition in animation using Pixelplacement.Tween
/// </summary>
protected override void DoTransitionIn(System.Action onComplete)
{
// Simple slide in animation
RectTransform rt = GetComponent<RectTransform>();
if (rt != null)
{
// Store the end position
Vector2 endPosition = rt.anchoredPosition;
// Set initial position (off-screen to the right)
rt.anchoredPosition = new Vector2(Screen.width, endPosition.y);
// Animate to end position
Tween.AnchoredPosition(rt, endPosition, transitionDuration, 0f,
Tween.EaseOutStrong, Tween.LoopType.None, null, onComplete);
}
else
{
onComplete?.Invoke();
}
}
/// <summary>
/// Override for transition out animation using Pixelplacement.Tween
/// </summary>
protected override void DoTransitionOut(System.Action onComplete)
{
// Simple slide out animation
RectTransform rt = GetComponent<RectTransform>();
if (rt != null)
{
// Animate to off-screen position (to the left)
Vector2 offScreenPosition = new Vector2(-Screen.width, rt.anchoredPosition.y);
Tween.AnchoredPosition(rt, offScreenPosition, transitionDuration, 0f,
Tween.EaseInOutStrong, Tween.LoopType.None, null, onComplete);
}
else
{
onComplete?.Invoke();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 59ff936424a34ce3937299c66232bf7a
timeCreated: 1759923921

View File

@@ -0,0 +1,270 @@
using System.Collections;
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
using Pixelplacement;
using UnityEngine;
using UnityEngine.UI;
namespace AppleHills.UI.CardSystem
{
/// <summary>
/// UI page for opening booster packs and displaying the cards obtained.
/// </summary>
public class BoosterOpeningPage : UIPage
{
[Header("UI Elements")]
[SerializeField] private GameObject boosterPackObject;
[SerializeField] private Transform cardRevealTransform;
[SerializeField] private GameObject cardPrefab;
[SerializeField] private Button openBoosterButton;
[SerializeField] private Button continueButton;
[Header("Animation Settings")]
[SerializeField] private float cardRevealDelay = 0.5f;
[SerializeField] private float cardMoveToBackpackDelay = 1.0f;
// State tracking
private enum OpeningState
{
BoosterReady,
CardsRevealed,
Completed
}
private OpeningState _currentState = OpeningState.BoosterReady;
private List<CardUIElement> _revealedCards = new List<CardUIElement>();
private CardSystemManager _cardManager;
private void Awake()
{
_cardManager = CardSystemManager.Instance;
// Set up button listeners
if (openBoosterButton != null)
{
openBoosterButton.onClick.AddListener(OnOpenBoosterClicked);
}
if (continueButton != null)
{
continueButton.onClick.AddListener(OnContinueClicked);
continueButton.gameObject.SetActive(false);
}
}
private void OnDestroy()
{
// Clean up button listeners
if (openBoosterButton != null)
{
openBoosterButton.onClick.RemoveListener(OnOpenBoosterClicked);
}
if (continueButton != null)
{
continueButton.onClick.RemoveListener(OnContinueClicked);
}
}
/// <summary>
/// Resets the page to its initial state when it becomes active
/// </summary>
public override void TransitionIn()
{
base.TransitionIn();
ResetState();
}
/// <summary>
/// Resets the state of the booster opening process
/// </summary>
private void ResetState()
{
// Clear any previously revealed cards
foreach (var card in _revealedCards)
{
Destroy(card.gameObject);
}
_revealedCards.Clear();
// Reset state
_currentState = OpeningState.BoosterReady;
// Show booster pack, hide continue button
if (boosterPackObject != null)
{
boosterPackObject.SetActive(true);
}
if (openBoosterButton != null)
{
openBoosterButton.gameObject.SetActive(true);
}
if (continueButton != null)
{
continueButton.gameObject.SetActive(false);
}
}
/// <summary>
/// Handles click on the booster pack to open it
/// </summary>
private void OnOpenBoosterClicked()
{
if (_currentState != OpeningState.BoosterReady) return;
// Open the booster pack and get the cards
List<CardData> newCards = _cardManager.OpenBoosterPack();
if (newCards.Count > 0)
{
// Hide the booster pack and open button
if (boosterPackObject != null)
{
boosterPackObject.SetActive(false);
}
if (openBoosterButton != null)
{
openBoosterButton.gameObject.SetActive(false);
}
// Start revealing cards
StartCoroutine(RevealCards(newCards));
}
else
{
Debug.LogWarning("[BoosterOpeningPage] No cards were obtained from the booster pack.");
UIPageController.Instance.PopPage();
}
}
/// <summary>
/// Reveals cards one by one with animation
/// </summary>
private IEnumerator RevealCards(List<CardData> cards)
{
// Wait a short delay before revealing cards
yield return new WaitForSeconds(0.5f);
// Reveal each card
foreach (var cardData in cards)
{
// Instantiate card UI element
GameObject cardObj = Instantiate(cardPrefab, cardRevealTransform);
CardUIElement cardUI = cardObj.GetComponent<CardUIElement>();
if (cardUI != null)
{
// Set up the card data
cardUI.SetupCard(cardData);
_revealedCards.Add(cardUI);
// Set initial scale to zero for animation
cardObj.transform.localScale = Vector3.zero;
// Play reveal animation using Pixelplacement.Tween
Tween.LocalScale(cardObj.transform, Vector3.one, 0.5f, 0f, Tween.EaseOutBack);
// Call card's show animation
cardUI.OnShowAnimation();
// Wait for animation delay
yield return new WaitForSeconds(cardRevealDelay);
}
}
// Update state and show continue button
_currentState = OpeningState.CardsRevealed;
if (continueButton != null)
{
continueButton.gameObject.SetActive(true);
}
}
/// <summary>
/// Handles click on the continue button after cards are revealed
/// </summary>
private void OnContinueClicked()
{
if (_currentState != OpeningState.CardsRevealed) return;
// Start moving cards to backpack animation
StartCoroutine(MoveCardsToBackpack());
}
/// <summary>
/// Animates cards moving to the backpack
/// </summary>
private IEnumerator MoveCardsToBackpack()
{
// Hide continue button
if (continueButton != null)
{
continueButton.gameObject.SetActive(false);
}
// Get corner position for backpack (bottom left)
Vector3 cornerPosition = new Vector3(-Screen.width/2 + 50, -Screen.height/2 + 50, 0);
// Animate each card moving to the backpack
foreach (var card in _revealedCards)
{
// Play move to backpack animation using Pixelplacement.Tween
Tween.Position(card.transform, cornerPosition, 0.5f, 0f, Tween.EaseInBack);
Tween.LocalScale(card.transform, Vector3.zero, 0.5f, 0f, Tween.EaseInBack);
// Call card's move to backpack animation
card.OnMoveToBackpackAnimation();
// Wait for animation delay
yield return new WaitForSeconds(cardMoveToBackpackDelay);
}
// Wait a moment before completing
yield return new WaitForSeconds(0.3f);
// Complete the process and return to previous page
_currentState = OpeningState.Completed;
UIPageController.Instance.PopPage();
}
/// <summary>
/// Override for transition in animation using Pixelplacement.Tween
/// </summary>
protected override void DoTransitionIn(System.Action onComplete)
{
// Scale in animation for the booster pack
if (boosterPackObject != null)
{
boosterPackObject.transform.localScale = Vector3.zero;
Tween.LocalScale(boosterPackObject.transform, Vector3.one, transitionDuration, 0f,
Tween.EaseOutBack, Tween.LoopType.None, null, onComplete);
}
else
{
onComplete?.Invoke();
}
}
/// <summary>
/// Override for transition out animation using Pixelplacement.Tween
/// </summary>
protected override void DoTransitionOut(System.Action onComplete)
{
// Fade out animation using a CanvasGroup if available
CanvasGroup canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup != null)
{
Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value,
transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete);
}
else
{
onComplete?.Invoke();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f64698442ba344e0a466ee3c530797c7
timeCreated: 1759923862

View File

@@ -0,0 +1,186 @@
using System;
using AppleHills.Data.CardSystem;
using UnityEngine;
using UnityEngine.UI;
namespace AppleHills.UI.CardSystem
{
/// <summary>
/// Main UI controller for the card album system.
/// Manages the backpack icon and navigation between card system pages.
/// </summary>
public class CardAlbumUI : MonoBehaviour
{
[Header("UI References")]
[SerializeField] private GameObject backpackIcon;
[SerializeField] private UIPage mainMenuPage;
[SerializeField] private UIPage albumViewPage;
[SerializeField] private UIPage boosterOpeningPage;
[Header("UI Elements")]
[SerializeField] private Button backpackButton;
[SerializeField] private Text boosterCountText;
private UIPageController _pageController;
private CardSystemManager _cardManager;
private bool _isInitialized = false;
private void Awake()
{
// Get or create a UI page controller
_pageController = FindAnyObjectByType<UIPageController>();
if (_pageController == null)
{
GameObject controllerObj = new GameObject("UIPageController");
controllerObj.transform.SetParent(transform);
_pageController = controllerObj.AddComponent<UIPageController>();
}
// Initialize pages and hide them
InitializePages();
// Set up backpack button
if (backpackButton != null)
{
backpackButton.onClick.AddListener(OnBackpackButtonClicked);
}
// Initially show only the backpack icon
ShowOnlyBackpackIcon();
}
private void Start()
{
// Get card manager
_cardManager = CardSystemManager.Instance;
// Subscribe to events
if (_cardManager != null)
{
_cardManager.OnBoosterCountChanged += UpdateBoosterCount;
UpdateBoosterCount(_cardManager.GetBoosterPackCount());
}
_isInitialized = true;
}
private void OnDestroy()
{
// Unsubscribe from events
if (_cardManager != null)
{
_cardManager.OnBoosterCountChanged -= UpdateBoosterCount;
}
// Clean up button listeners
if (backpackButton != null)
{
backpackButton.onClick.RemoveListener(OnBackpackButtonClicked);
}
}
/// <summary>
/// Initializes all UI pages and sets them up with proper callbacks
/// </summary>
private void InitializePages()
{
// Ensure all pages are inactive at start
if (mainMenuPage != null)
{
mainMenuPage.gameObject.SetActive(false);
}
if (albumViewPage != null)
{
albumViewPage.gameObject.SetActive(false);
}
if (boosterOpeningPage != null)
{
boosterOpeningPage.gameObject.SetActive(false);
}
// Set up page changed callback
if (_pageController != null)
{
_pageController.OnPageChanged += OnPageChanged;
}
}
/// <summary>
/// Handles click on the backpack icon
/// </summary>
private void OnBackpackButtonClicked()
{
// If no pages are open, push the main menu
if (_pageController.CurrentPage == null)
{
_pageController.PushPage(mainMenuPage);
}
else if (_pageController.CurrentPage == mainMenuPage)
{
// If main menu is open, pop it
_pageController.PopPage();
}
}
/// <summary>
/// Handles changes to the current page
/// </summary>
private void OnPageChanged(UIPage newPage)
{
// Hide/show backpack icon based on current page
if (newPage == null)
{
ShowOnlyBackpackIcon();
}
else
{
backpackIcon.SetActive(false);
}
}
/// <summary>
/// Shows only the backpack icon, hiding all other UI elements
/// </summary>
private void ShowOnlyBackpackIcon()
{
backpackIcon.SetActive(true);
}
/// <summary>
/// Opens the album view page
/// </summary>
public void OpenAlbumView()
{
_pageController.PushPage(albumViewPage);
}
/// <summary>
/// Opens the booster opening page if boosters are available
/// </summary>
public void OpenBoosterPack()
{
if (_cardManager != null && _cardManager.GetBoosterPackCount() > 0)
{
_pageController.PushPage(boosterOpeningPage);
}
else
{
Debug.Log("[CardAlbumUI] No booster packs available");
// TODO: Show "no boosters available" message
}
}
/// <summary>
/// Updates the booster count display
/// </summary>
private void UpdateBoosterCount(int count)
{
if (boosterCountText != null)
{
boosterCountText.text = count.ToString();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fe5ff32f529d4f24a2064ee1dfa07758
timeCreated: 1759923763

View File

@@ -0,0 +1,177 @@
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
using Pixelplacement;
using UnityEngine;
using UnityEngine.UI;
namespace AppleHills.UI.CardSystem
{
/// <summary>
/// UI page for the main menu of the card system.
/// Shows options to open boosters, view album, etc.
/// </summary>
public class CardMenuPage : UIPage
{
[Header("Menu Options")]
[SerializeField] private Button openBoosterButton;
[SerializeField] private Button viewAlbumButton;
[SerializeField] private Button changeClothesButton;
[Header("UI Elements")]
[SerializeField] private Text boosterCountText;
[SerializeField] private GameObject noBoostersMessage;
[SerializeField] private CanvasGroup canvasGroup;
private CardAlbumUI _cardAlbumUI;
private CardSystemManager _cardManager;
private void Awake()
{
// Get references
_cardAlbumUI = FindAnyObjectByType<CardAlbumUI>();
_cardManager = CardSystemManager.Instance;
// Make sure we have a CanvasGroup
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Set up button listeners
if (openBoosterButton != null)
{
openBoosterButton.onClick.AddListener(OnOpenBoosterClicked);
}
if (viewAlbumButton != null)
{
viewAlbumButton.onClick.AddListener(OnViewAlbumClicked);
}
if (changeClothesButton != null)
{
changeClothesButton.onClick.AddListener(OnChangeClothesClicked);
// Disable "Coming Soon" feature
changeClothesButton.interactable = false;
}
}
private void OnEnable()
{
UpdateUI();
}
private void OnDestroy()
{
// Clean up button listeners
if (openBoosterButton != null)
{
openBoosterButton.onClick.RemoveListener(OnOpenBoosterClicked);
}
if (viewAlbumButton != null)
{
viewAlbumButton.onClick.RemoveListener(OnViewAlbumClicked);
}
if (changeClothesButton != null)
{
changeClothesButton.onClick.RemoveListener(OnChangeClothesClicked);
}
}
/// <summary>
/// Updates the UI elements based on current state
/// </summary>
private void UpdateUI()
{
if (_cardManager == null) return;
int boosterCount = _cardManager.GetBoosterPackCount();
// Update booster count text
if (boosterCountText != null)
{
boosterCountText.text = $"Boosters: {boosterCount}";
}
// Enable/disable open booster button based on availability
if (openBoosterButton != null)
{
openBoosterButton.interactable = boosterCount > 0;
}
// Show/hide no boosters message
if (noBoostersMessage != null)
{
noBoostersMessage.SetActive(boosterCount <= 0);
}
}
/// <summary>
/// Handles click on the Open Booster button
/// </summary>
private void OnOpenBoosterClicked()
{
if (_cardAlbumUI != null)
{
_cardAlbumUI.OpenBoosterPack();
}
}
/// <summary>
/// Handles click on the View Album button
/// </summary>
private void OnViewAlbumClicked()
{
if (_cardAlbumUI != null)
{
_cardAlbumUI.OpenAlbumView();
}
}
/// <summary>
/// Handles click on the Change Clothes button (coming soon)
/// </summary>
private void OnChangeClothesClicked()
{
Debug.Log("[CardMenuPage] Change Clothes feature coming soon!");
// No implementation yet - "Coming soon" feature
}
/// <summary>
/// Override for transition in animation using Pixelplacement.Tween
/// </summary>
protected override void DoTransitionIn(System.Action onComplete)
{
// Simple fade in animation
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete);
}
else
{
// Fallback if no CanvasGroup
onComplete?.Invoke();
}
}
/// <summary>
/// Override for transition out animation using Pixelplacement.Tween
/// </summary>
protected override void DoTransitionOut(System.Action onComplete)
{
// Simple fade out animation
if (canvasGroup != null)
{
Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete);
}
else
{
// Fallback if no CanvasGroup
onComplete?.Invoke();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a81af156ae284925a1f004d08f0bb364
timeCreated: 1759923823

View File

@@ -0,0 +1,255 @@
using System;
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace AppleHills.UI.CardSystem
{
/// <summary>
/// Handles displaying and interacting with a single card in the UI.
/// </summary>
public class CardUIElement : MonoBehaviour
{
[Header("UI Elements")]
[SerializeField] private TextMeshProUGUI cardNameText;
[SerializeField] private Image cardImage;
[SerializeField] private Image frameImage;
[SerializeField] private Image backgroundImage;
[SerializeField] private Image backgroundShape;
[Header("Card Data")]
[SerializeField] private CardDefinition cardDefinition;
[SerializeField] private CardData cardData;
[Header("Visual Settings")]
[SerializeField] private CardVisualConfig visualConfig;
// Events
public event Action<CardUIElement> OnCardClicked;
/// <summary>
/// Sets up the card UI with the given card data
/// </summary>
public void SetupCard(CardData cardData)
{
Debug.Log($"[CardUIElement] Setting up card with data: {cardData}");
this.cardData = cardData;
if (cardData == null)
{
Debug.LogError("[CardUIElement] Attempted to setup card with null data");
return;
}
UpdateCardVisuals();
}
/// <summary>
/// Updates the card visuals based on the current card data
/// </summary>
private void UpdateCardVisuals()
{
if (cardData == null) return;
// Set basic text information
if (cardNameText != null) cardNameText.text = cardData.Name;
// Set the card image
if (cardImage != null)
{
if (cardData.CardImage != null)
{
cardImage.sprite = cardData.CardImage;
cardImage.enabled = true;
}
else
{
cardImage.sprite = null;
cardImage.enabled = false;
// Don't log warnings for missing images - this is expected for new cards
// and causes console spam in the editor
}
}
// Update frame color based on rarity
UpdateFrameByRarity(cardData.Rarity);
// Update background color and shape based on zone
UpdateBackgroundByZone(cardData.Zone);
}
/// <summary>
/// Updates the frame color based on card rarity
/// </summary>
private void UpdateFrameByRarity(CardRarity rarity)
{
if (frameImage == null) return;
// Get color from config if available, otherwise use default colors
Color frameColor = visualConfig != null
? visualConfig.GetRarityColor(rarity)
: GetDefaultRarityColor(rarity);
frameImage.color = frameColor;
}
/// <summary>
/// Updates the background color and shape based on card zone
/// </summary>
private void UpdateBackgroundByZone(CardZone zone)
{
// Update background color
if (backgroundImage != null)
{
Color bgColor = visualConfig != null
? visualConfig.GetZoneColor(zone)
: GetDefaultZoneColor(zone);
backgroundImage.color = bgColor;
}
// Update background shape
if (backgroundShape != null && visualConfig != null)
{
Sprite shapeSprite = visualConfig.GetZoneShape(zone);
if (shapeSprite != null)
{
backgroundShape.sprite = shapeSprite;
backgroundShape.gameObject.SetActive(true);
}
else
{
backgroundShape.gameObject.SetActive(false);
}
}
}
/// <summary>
/// Returns default color for rarity if no config is available
/// </summary>
private Color GetDefaultRarityColor(CardRarity rarity)
{
switch (rarity)
{
case CardRarity.Common:
return Color.gray;
case CardRarity.Uncommon:
return Color.green;
case CardRarity.Rare:
return Color.blue;
case CardRarity.Epic:
return new Color(0.5f, 0, 0.5f); // Purple
case CardRarity.Legendary:
return Color.yellow;
default:
return Color.white;
}
}
/// <summary>
/// Returns default color for zone if no config is available
/// </summary>
private Color GetDefaultZoneColor(CardZone zone)
{
// Default zone colors from CardDefinition
switch (zone)
{
case CardZone.AppleHills:
return new Color(0.8f, 0.9f, 0.8f); // Light green
case CardZone.Quarry:
return new Color(0.85f, 0.8f, 0.7f); // Sandy brown
case CardZone.Forest:
return new Color(0.6f, 0.8f, 0.6f); // Forest green
case CardZone.Mountain:
return new Color(0.7f, 0.7f, 0.9f); // Bluish
case CardZone.Beach:
return new Color(0.9f, 0.85f, 0.7f); // Sandy yellow
default:
return Color.white;
}
}
/// <summary>
/// Called when the card is clicked
/// </summary>
public void OnClick()
{
OnCardClicked?.Invoke(this);
}
/// <summary>
/// Returns the card data associated with this UI element
/// </summary>
public CardData GetCardData()
{
return cardData;
}
// Animation methods that can be called from UI events
/// <summary>
/// Called when the card is revealed from a booster pack
/// </summary>
public void OnShowAnimation()
{
// Stub for card reveal animation
Debug.Log($"[CardUIElement] Showing card: {cardData?.Name}");
// Could add animation code or call Animation Trigger here
}
/// <summary>
/// Called when the card moves to the backpack after being revealed
/// </summary>
public void OnMoveToBackpackAnimation()
{
// Stub for animation when card moves to backpack
Debug.Log($"[CardUIElement] Moving card to backpack: {cardData?.Name}");
// Could add animation code or call Animation Trigger here
}
#if UNITY_EDITOR
/// <summary>
/// Create card data from definition and update the UI
/// </summary>
public void CreateFromDefinition()
{
if (cardDefinition == null)
{
Debug.LogWarning("[CardUIElement] Cannot create card data: No card definition assigned");
return;
}
cardData = cardDefinition.CreateCardData();
UpdateCardVisuals();
Debug.Log($"[CardUIElement] Created card from definition: {cardData.Name}");
}
#endif
}
#if UNITY_EDITOR
[CustomEditor(typeof(CardUIElement))]
public class CardUIElementEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
CardUIElement cardUI = (CardUIElement)target;
EditorGUILayout.Space();
if (GUILayout.Button("Create From Definition"))
{
cardUI.CreateFromDefinition();
EditorUtility.SetDirty(cardUI);
}
}
}
#endif
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aed7c581bdb84200a05dd8a7df409ab0
timeCreated: 1759923795

View File

@@ -0,0 +1,92 @@
using System;
using UnityEngine;
namespace AppleHills.UI.CardSystem
{
/// <summary>
/// Base class for UI pages that can transition in and out.
/// Extended by specific UI page implementations for the card system.
/// </summary>
public abstract class UIPage : MonoBehaviour
{
[Header("Page Settings")]
public string PageName;
// Events using System.Action instead of UnityEvents
public event Action OnTransitionInStarted;
public event Action OnTransitionInCompleted;
public event Action OnTransitionOutStarted;
public event Action OnTransitionOutCompleted;
// Default duration for transitions
[SerializeField] protected float transitionDuration = 0.3f;
// State flags
protected bool _isTransitioning = false;
protected bool _isVisible = false;
/// <summary>
/// Called when the page is shown. Handles the transition in animation.
/// </summary>
public virtual void TransitionIn()
{
gameObject.SetActive(true);
_isTransitioning = true;
OnTransitionInStarted?.Invoke();
// Override in child classes for specific transition animations
DoTransitionIn(() => {
_isTransitioning = false;
_isVisible = true;
OnTransitionInCompleted?.Invoke();
});
}
/// <summary>
/// Called when the page is hidden. Handles the transition out animation.
/// </summary>
public virtual void TransitionOut()
{
_isTransitioning = true;
OnTransitionOutStarted?.Invoke();
// Override in child classes for specific transition animations
DoTransitionOut(() => {
_isTransitioning = false;
_isVisible = false;
OnTransitionOutCompleted?.Invoke();
gameObject.SetActive(false);
});
}
/// <summary>
/// Called when the back button is pressed while this page is active.
/// Default implementation pops the page from the stack.
/// </summary>
public virtual void OnBackPressed()
{
if (!_isTransitioning)
{
UIPageController.Instance.PopPage();
}
}
/// <summary>
/// Implement transition in animation in child classes.
/// </summary>
protected virtual void DoTransitionIn(Action onComplete)
{
// Default implementation - instant transition
onComplete?.Invoke();
}
/// <summary>
/// Implement transition out animation in child classes.
/// </summary>
protected virtual void DoTransitionOut(Action onComplete)
{
// Default implementation - instant transition
onComplete?.Invoke();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57fa26be941c43dc9331534ab59416b1
timeCreated: 1759923734

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AppleHills.UI.CardSystem
{
/// <summary>
/// Manages UI page transitions and maintains a stack of active pages.
/// Pages are pushed onto a stack for navigation and popped when going back.
/// </summary>
public class UIPageController : MonoBehaviour
{
private static UIPageController _instance;
public static UIPageController Instance => _instance;
private Stack<UIPage> _pageStack = new Stack<UIPage>();
public UIPage CurrentPage => _pageStack.Count > 0 ? _pageStack.Peek() : null;
// Event fired when the page stack changes
public event Action<UIPage> OnPageChanged;
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
}
/// <summary>
/// Pushes a new page onto the stack, hiding the current page and showing the new one.
/// </summary>
public void PushPage(UIPage page)
{
if (page == null) return;
// Hide current page if there is one
if (_pageStack.Count > 0)
{
UIPage currentPage = _pageStack.Peek();
currentPage.TransitionOut();
}
// Push and show new page
_pageStack.Push(page);
page.TransitionIn();
OnPageChanged?.Invoke(page);
Debug.Log($"[UIPageController] Pushed page: {page.PageName}");
}
/// <summary>
/// Pops the current page from the stack and shows the previous page.
/// </summary>
public void PopPage()
{
if (_pageStack.Count <= 0) return;
// Hide and pop current page
UIPage currentPage = _pageStack.Pop();
currentPage.TransitionOut();
// Show previous page if there is one
if (_pageStack.Count > 0)
{
UIPage previousPage = _pageStack.Peek();
previousPage.TransitionIn();
OnPageChanged?.Invoke(previousPage);
Debug.Log($"[UIPageController] Popped to previous page: {previousPage.PageName}");
}
else
{
OnPageChanged?.Invoke(null);
Debug.Log("[UIPageController] Popped last page, no pages left in stack");
}
}
/// <summary>
/// Clears all pages from the stack.
/// </summary>
public void ClearStack()
{
if (_pageStack.Count <= 0) return;
// Hide current page
UIPage currentPage = _pageStack.Peek();
currentPage.TransitionOut();
// Clear stack
_pageStack.Clear();
OnPageChanged?.Invoke(null);
Debug.Log("[UIPageController] Cleared page stack");
}
/// <summary>
/// Handles back button input and navigates to the previous page if possible.
/// </summary>
private void Update()
{
if (UnityEngine.Input.GetKeyDown(KeyCode.Escape) && _pageStack.Count > 0)
{
_pageStack.Peek().OnBackPressed();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b1ae6c1745e44e22a0fa9209ebe45ee3
timeCreated: 1759923720