Fix up card flows, align with the old 1:1
This commit is contained in:
committed by
Michal Pikulski
parent
a6471ede45
commit
39d5890db4
206
Assets/Scripts/UI/CardSystem/ProgressBarController.cs
Normal file
206
Assets/Scripts/UI/CardSystem/ProgressBarController.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using System.Collections;
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace UI.CardSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls a vertical progress bar made of individual Image elements.
|
||||
/// Fills from bottom to top and animates the newest element with a blink effect.
|
||||
///
|
||||
/// Setup: Place on GameObject with GridLayoutGroup (1 column, N rows).
|
||||
/// Grid should have "Lower Right" corner start so first child = bottom element.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(GridLayoutGroup))]
|
||||
public class ProgressBarController : MonoBehaviour
|
||||
{
|
||||
[Header("Progress Elements")]
|
||||
[Tooltip("The individual Image components representing progress segments (auto-detected from children)")]
|
||||
private Image[] _progressElements;
|
||||
|
||||
private ICardSystemSettings _settings;
|
||||
private Coroutine _currentBlinkCoroutine;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_settings = GameManager.GetSettingsObject<ICardSystemSettings>();
|
||||
|
||||
// Auto-detect all child Image components
|
||||
_progressElements = GetComponentsInChildren<Image>(true);
|
||||
|
||||
if (_progressElements.Length == 0)
|
||||
{
|
||||
Logging.Warning("[ProgressBarController] No child Image components found! Add Image children to this GridLayout.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show progress and animate the newest element with a blink effect.
|
||||
/// </summary>
|
||||
/// <param name="currentCount">Current progress (1 to maxCount)</param>
|
||||
/// <param name="maxCount">Maximum progress value (typically 5)</param>
|
||||
/// <param name="onComplete">Callback invoked after blink animation completes</param>
|
||||
public void ShowProgress(int currentCount, int maxCount, System.Action onComplete)
|
||||
{
|
||||
// Validate input
|
||||
if (currentCount < 0 || currentCount > maxCount)
|
||||
{
|
||||
Logging.Warning($"[ProgressBarController] Invalid progress: {currentCount}/{maxCount}");
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate element count
|
||||
if (_progressElements.Length < maxCount)
|
||||
{
|
||||
Logging.Warning($"[ProgressBarController] Not enough progress elements! Expected {maxCount}, found {_progressElements.Length}");
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any existing blink animation
|
||||
if (_currentBlinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_currentBlinkCoroutine);
|
||||
_currentBlinkCoroutine = null;
|
||||
}
|
||||
|
||||
// Disable all elements first
|
||||
foreach (var element in _progressElements)
|
||||
{
|
||||
element.enabled = false;
|
||||
}
|
||||
|
||||
// Enable elements from BOTTOM to TOP (first N elements)
|
||||
// Since GridLayout starts at "Lower Right", element[0] = bottom, element[4] = top
|
||||
for (int i = 0; i < currentCount && i < _progressElements.Length; i++)
|
||||
{
|
||||
_progressElements[i].enabled = true;
|
||||
}
|
||||
|
||||
Logging.Debug($"[ProgressBarController] Showing progress {currentCount}/{maxCount}");
|
||||
|
||||
// Blink the NEWEST element (the one we just added)
|
||||
// Newest element is at index (currentCount - 1)
|
||||
int newestIndex = currentCount - 1;
|
||||
if (newestIndex >= 0 && newestIndex < _progressElements.Length && currentCount > 0)
|
||||
{
|
||||
_currentBlinkCoroutine = StartCoroutine(BlinkElement(newestIndex, onComplete));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No element to blink (e.g., currentCount = 0)
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show progress without blink animation (instant display).
|
||||
/// </summary>
|
||||
/// <param name="currentCount">Current progress (1 to maxCount)</param>
|
||||
/// <param name="maxCount">Maximum progress value</param>
|
||||
public void ShowProgressInstant(int currentCount, int maxCount)
|
||||
{
|
||||
// Validate
|
||||
if (currentCount < 0 || currentCount > maxCount || _progressElements.Length < maxCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable all
|
||||
foreach (var element in _progressElements)
|
||||
{
|
||||
element.enabled = false;
|
||||
}
|
||||
|
||||
// Enable first N elements
|
||||
for (int i = 0; i < currentCount && i < _progressElements.Length; i++)
|
||||
{
|
||||
_progressElements[i].enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide all progress elements.
|
||||
/// </summary>
|
||||
public void HideProgress()
|
||||
{
|
||||
foreach (var element in _progressElements)
|
||||
{
|
||||
element.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blink a specific element by toggling enabled/disabled.
|
||||
/// </summary>
|
||||
private IEnumerator BlinkElement(int index, System.Action onComplete)
|
||||
{
|
||||
if (index < 0 || index >= _progressElements.Length)
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
yield break;
|
||||
}
|
||||
|
||||
Image element = _progressElements[index];
|
||||
|
||||
// Get blink settings (or use defaults if not available)
|
||||
float blinkDuration = 0.15f; // Duration for each on/off toggle
|
||||
int blinkCount = 3; // Number of full blinks (on->off->on = 1 blink)
|
||||
|
||||
// Try to get settings if available
|
||||
if (_settings != null)
|
||||
{
|
||||
// Settings could expose these if needed:
|
||||
// blinkDuration = _settings.ProgressBlinkDuration;
|
||||
// blinkCount = _settings.ProgressBlinkCount;
|
||||
}
|
||||
|
||||
Logging.Debug($"[ProgressBarController] Blinking element {index} ({blinkCount} times)");
|
||||
|
||||
// Wait a brief moment before starting blink
|
||||
yield return new WaitForSeconds(0.3f);
|
||||
|
||||
// Perform blinks (on->off->on = 1 full blink)
|
||||
for (int i = 0; i < blinkCount; i++)
|
||||
{
|
||||
// Off
|
||||
element.enabled = false;
|
||||
yield return new WaitForSeconds(blinkDuration);
|
||||
|
||||
// On
|
||||
element.enabled = true;
|
||||
yield return new WaitForSeconds(blinkDuration);
|
||||
}
|
||||
|
||||
// Ensure element is enabled at the end
|
||||
element.enabled = true;
|
||||
|
||||
Logging.Debug($"[ProgressBarController] Blink complete for element {index}");
|
||||
|
||||
_currentBlinkCoroutine = null;
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total number of progress elements available.
|
||||
/// </summary>
|
||||
public int GetElementCount()
|
||||
{
|
||||
return _progressElements?.Length ?? 0;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Clean up any running coroutines
|
||||
if (_currentBlinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_currentBlinkCoroutine);
|
||||
_currentBlinkCoroutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e91de41001c14101b8fa4216d6c7888b
|
||||
timeCreated: 1762939781
|
||||
@@ -1,4 +1,5 @@
|
||||
using AppleHills.Data.CardSystem;
|
||||
using System;
|
||||
using AppleHills.Data.CardSystem;
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -29,6 +30,18 @@ namespace UI.CardSystem.StateMachine
|
||||
public bool IsNewCard { get; set; }
|
||||
public int RepeatCardCount { get; set; }
|
||||
|
||||
// Events for external coordination (BoosterOpeningPage, etc.)
|
||||
public event Action<CardContext> OnFlipComplete;
|
||||
public event Action<CardContext> OnCardDismissed;
|
||||
public event Action<CardContext> OnCardInteractionComplete;
|
||||
public event Action<CardContext> OnUpgradeTriggered;
|
||||
|
||||
// Helper methods for states
|
||||
public void FireFlipComplete() => OnFlipComplete?.Invoke(this);
|
||||
public void FireCardDismissed() => OnCardDismissed?.Invoke(this);
|
||||
public void FireCardInteractionComplete() => OnCardInteractionComplete?.Invoke(this);
|
||||
public void FireUpgradeTriggered() => OnUpgradeTriggered?.Invoke(this);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Auto-find components if not assigned
|
||||
|
||||
@@ -45,6 +45,9 @@ namespace UI.CardSystem.StateMachine.States
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
// Fire dismissed event
|
||||
_context.FireCardDismissed();
|
||||
|
||||
// Tap to dismiss - shrink back and transition to revealed state
|
||||
if (_context.Animator != null)
|
||||
{
|
||||
@@ -53,6 +56,11 @@ namespace UI.CardSystem.StateMachine.States
|
||||
_context.StateMachine.ChangeState("RevealedState");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback if no animator
|
||||
_context.StateMachine.ChangeState("RevealedState");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
using Core.SaveLoad;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using AppleHills.Core.Settings;
|
||||
using Core;
|
||||
using AppleHills.Data.CardSystem;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Enlarged state for REPEAT cards - shows progress bar toward next rarity upgrade.
|
||||
/// Owns the ProgressBarUI as a child GameObject.
|
||||
/// Uses ProgressBarController to animate progress filling.
|
||||
/// Auto-upgrades card when threshold is reached.
|
||||
/// </summary>
|
||||
public class CardEnlargedRepeatState : AppleState, IPointerClickHandler
|
||||
{
|
||||
[Header("State-Owned Visuals")]
|
||||
[SerializeField] private GameObject progressBarContainer;
|
||||
[SerializeField] private Image progressBarFill;
|
||||
[SerializeField] private TextMeshProUGUI progressText;
|
||||
[SerializeField] private ProgressBarController progressBar;
|
||||
|
||||
private CardContext _context;
|
||||
private ICardSystemSettings _settings;
|
||||
private Vector3 _originalScale;
|
||||
private bool _waitingForTap = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -33,12 +33,12 @@ namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
// Store original scale
|
||||
_originalScale = _context.RootTransform.localScale;
|
||||
_waitingForTap = false;
|
||||
|
||||
// Show progress bar
|
||||
// Show progress bar container
|
||||
if (progressBarContainer != null)
|
||||
{
|
||||
progressBarContainer.SetActive(true);
|
||||
UpdateProgressBar();
|
||||
}
|
||||
|
||||
// Enlarge the card
|
||||
@@ -46,27 +46,148 @@ namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
_context.Animator.PlayEnlarge(_settings.NewCardEnlargedScale);
|
||||
}
|
||||
|
||||
// Show progress with blink animation
|
||||
if (progressBar != null)
|
||||
{
|
||||
int currentCount = _context.RepeatCardCount + 1; // +1 because we just got this card
|
||||
int maxCount = _settings.CardsToUpgrade;
|
||||
|
||||
progressBar.ShowProgress(currentCount, maxCount, OnProgressComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning("[CardEnlargedRepeatState] ProgressBar component not assigned!");
|
||||
OnProgressComplete();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProgressBar()
|
||||
private void OnProgressComplete()
|
||||
{
|
||||
int currentCount = _context.RepeatCardCount;
|
||||
int cardsToUpgrade = _settings.CardsToUpgrade;
|
||||
float progress = (float)currentCount / cardsToUpgrade;
|
||||
|
||||
if (progressBarFill != null)
|
||||
// Check if this card triggers an upgrade
|
||||
if (ShouldTriggerUpgrade())
|
||||
{
|
||||
progressBarFill.fillAmount = progress;
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Card will trigger upgrade! ({_context.RepeatCardCount + 1}/{_settings.CardsToUpgrade})");
|
||||
TriggerUpgrade();
|
||||
}
|
||||
else
|
||||
{
|
||||
// No upgrade - just wait for tap to dismiss
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Progress shown, waiting for tap to dismiss");
|
||||
_waitingForTap = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldTriggerUpgrade()
|
||||
{
|
||||
int currentCount = _context.RepeatCardCount + 1; // +1 for the card we just got
|
||||
CardRarity currentRarity = _context.CardData.Rarity;
|
||||
|
||||
// Can't upgrade if already Legendary
|
||||
if (currentRarity == CardRarity.Legendary)
|
||||
return false;
|
||||
|
||||
// Check if we've hit the threshold
|
||||
return currentCount >= _settings.CardsToUpgrade;
|
||||
}
|
||||
|
||||
private void TriggerUpgrade()
|
||||
{
|
||||
_context.FireUpgradeTriggered();
|
||||
|
||||
CardData cardData = _context.CardData;
|
||||
CardRarity oldRarity = cardData.Rarity;
|
||||
CardRarity newRarity = oldRarity + 1;
|
||||
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Upgrading card from {oldRarity} to {newRarity}");
|
||||
|
||||
// Get the existing card at lower rarity from inventory
|
||||
CardData existingLowerRarity = Data.CardSystem.CardSystemManager.Instance.GetCardInventory().GetCard(cardData.Name, oldRarity);
|
||||
|
||||
if (existingLowerRarity != null)
|
||||
{
|
||||
// Reset lower rarity count to 0
|
||||
existingLowerRarity.CopiesOwned = 0;
|
||||
}
|
||||
|
||||
if (progressText != null)
|
||||
// Create upgraded card data
|
||||
CardData upgradedCard = new CardData(cardData);
|
||||
upgradedCard.Rarity = newRarity;
|
||||
upgradedCard.CopiesOwned = 1;
|
||||
|
||||
// Check if this card is new at the higher rarity
|
||||
bool isNewAtHigherRarity = Data.CardSystem.CardSystemManager.Instance.IsCardNew(upgradedCard, out CardData existingHigherRarity);
|
||||
|
||||
// Add to inventory
|
||||
Data.CardSystem.CardSystemManager.Instance.GetCardInventory().AddCard(upgradedCard);
|
||||
|
||||
// Update our card data to show upgraded rarity
|
||||
cardData.Rarity = newRarity;
|
||||
|
||||
// Update the CardDisplay to show new rarity
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
progressText.text = $"{currentCount}/{cardsToUpgrade}";
|
||||
_context.CardDisplay.SetupCard(cardData);
|
||||
}
|
||||
|
||||
// Determine next transition
|
||||
if (isNewAtHigherRarity || newRarity == CardRarity.Legendary)
|
||||
{
|
||||
// Show as NEW at higher rarity
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Card is NEW at {newRarity}, transitioning to EnlargedNewState");
|
||||
TransitionToNewCardView();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already have copies at higher rarity - show progress there too
|
||||
int ownedAtHigherRarity = existingHigherRarity.CopiesOwned;
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Card is REPEAT at {newRarity} ({ownedAtHigherRarity}/{_settings.CardsToUpgrade}), showing progress");
|
||||
|
||||
// Update context for higher rarity
|
||||
_context.IsNewCard = false;
|
||||
_context.RepeatCardCount = ownedAtHigherRarity;
|
||||
|
||||
// Re-enter this state with updated data (will show progress for higher rarity)
|
||||
// First hide current progress, then show new progress
|
||||
if (progressBar != null)
|
||||
{
|
||||
progressBar.ShowProgress(ownedAtHigherRarity + 1, _settings.CardsToUpgrade, () =>
|
||||
{
|
||||
// After showing progress at higher rarity, transition to NEW
|
||||
TransitionToNewCardView();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
TransitionToNewCardView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TransitionToNewCardView()
|
||||
{
|
||||
// Update context to show as new card
|
||||
_context.IsNewCard = true;
|
||||
|
||||
// Hide progress bar before transitioning
|
||||
if (progressBarContainer != null)
|
||||
{
|
||||
progressBarContainer.SetActive(false);
|
||||
}
|
||||
|
||||
// Transition to EnlargedNewState (card is already enlarged, will show NEW badge)
|
||||
_context.StateMachine.ChangeState("EnlargedNewState");
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
// Only respond to clicks if waiting for tap
|
||||
if (!_waitingForTap)
|
||||
return;
|
||||
|
||||
// Fire dismissed event
|
||||
_context.FireCardDismissed();
|
||||
|
||||
// Tap to dismiss - shrink back and transition to revealed state
|
||||
if (_context.Animator != null)
|
||||
{
|
||||
@@ -75,6 +196,10 @@ namespace UI.CardSystem.StateMachine.States
|
||||
_context.StateMachine.ChangeState("RevealedState");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.StateMachine.ChangeState("RevealedState");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
|
||||
@@ -47,11 +47,21 @@ namespace UI.CardSystem.StateMachine.States
|
||||
|
||||
private void OnFlipComplete()
|
||||
{
|
||||
// Fire flip complete event
|
||||
_context.FireFlipComplete();
|
||||
|
||||
// Transition based on whether this is a new card or repeat
|
||||
if (_context.IsNewCard)
|
||||
{
|
||||
_context.StateMachine.ChangeState("EnlargedNewState");
|
||||
}
|
||||
else if (_context.CardData.Rarity == AppleHills.Data.CardSystem.CardRarity.Legendary)
|
||||
{
|
||||
// Legendary repeat - skip enlarge, they can't upgrade
|
||||
// Add to inventory and move to revealed state
|
||||
Data.CardSystem.CardSystemManager.Instance.AddCardToInventoryDelayed(_context.CardData);
|
||||
_context.StateMachine.ChangeState("RevealedState");
|
||||
}
|
||||
else if (_context.RepeatCardCount > 0)
|
||||
{
|
||||
_context.StateMachine.ChangeState("EnlargedRepeatState");
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Revealed state - card is flipped and visible, waiting for interaction.
|
||||
/// Can be clicked to enlarge or dragged to place in album.
|
||||
/// Revealed state - card is flipped and visible at normal size.
|
||||
/// This is the "waiting" state:
|
||||
/// - In booster flow: waiting for all cards to finish before animating to album
|
||||
/// - In album placement flow: waiting to be dragged to a slot
|
||||
/// </summary>
|
||||
public class CardRevealedState : AppleState, IPointerClickHandler
|
||||
public class CardRevealedState : AppleState
|
||||
{
|
||||
private CardContext _context;
|
||||
|
||||
@@ -19,14 +19,9 @@ namespace UI.CardSystem.StateMachine.States
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Card is simply visible and interactable
|
||||
// No special animations needed
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
// Click to enlarge
|
||||
_context.StateMachine.ChangeState("EnlargedNewState");
|
||||
// Card is at normal size, fully revealed
|
||||
// Fire interaction complete event (for BoosterOpeningPage tracking)
|
||||
_context.FireCardInteractionComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user