Rework how prefabs for cards are pre-defined (#88)

Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: #88
This commit is contained in:
2025-12-18 15:51:21 +00:00
parent 61f6da7a7d
commit 6c5a396540
36 changed files with 2749 additions and 1326 deletions

View File

@@ -1,5 +1,4 @@
using AppleHills.Core.Settings;
using Minigames.CardSorting.Data;
using UnityEngine;
namespace Core.Settings
@@ -29,21 +28,21 @@ namespace Core.Settings
[SerializeField] private AnimationCurve speedCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
[Header("Item Pools")]
[Tooltip("Garbage items that can spawn (banana peels, cans, receipts, etc.)")]
[SerializeField] private GarbageItemDefinition[] garbageItems = new GarbageItemDefinition[0];
[Tooltip("Normal rarity card prefabs that can spawn")]
[SerializeField] private GameObject[] normalCardPrefabs = new GameObject[0];
[Tooltip("Rare rarity card prefabs that can spawn")]
[SerializeField] private GameObject[] rareCardPrefabs = new GameObject[0];
[Tooltip("Legendary rarity card prefabs that can spawn")]
[SerializeField] private GameObject[] legendaryCardPrefabs = new GameObject[0];
[Tooltip("Garbage prefabs that can spawn")]
[SerializeField] private GameObject[] garbagePrefabs = new GameObject[0];
[Header("Spawn Weights")]
[Tooltip("Weight for spawning normal rarity cards")]
[Range(0, 100)] [SerializeField] private float normalCardWeight = 40f;
[Tooltip("Weight for spawning rare rarity cards")]
[Range(0, 100)] [SerializeField] private float rareCardWeight = 30f;
[Tooltip("Weight for spawning legendary rarity cards")]
[Range(0, 100)] [SerializeField] private float legendCardWeight = 20f;
[Tooltip("Weight for spawning garbage items")]
[Range(0, 100)] [SerializeField] private float garbageWeight = 10f;
[Tooltip("Ratio of cards to garbage (0 = all garbage, 0.5 = 50/50 split, 1 = all cards)")]
[Range(0, 1)] [SerializeField] private float cardToGarbageRatio = 0.5f;
[Header("Scoring")]
[Tooltip("Points awarded for correct sort")]
@@ -81,11 +80,11 @@ namespace Core.Settings
public float InitialBeltSpeed => initialBeltSpeed;
public float MaxBeltSpeed => maxBeltSpeed;
public AnimationCurve SpeedCurve => speedCurve;
public GarbageItemDefinition[] GarbageItems => garbageItems;
public float NormalCardWeight => normalCardWeight;
public float RareCardWeight => rareCardWeight;
public float LegendCardWeight => legendCardWeight;
public float GarbageWeight => garbageWeight;
public GameObject[] NormalCardPrefabs => normalCardPrefabs;
public GameObject[] RareCardPrefabs => rareCardPrefabs;
public GameObject[] LegendaryCardPrefabs => legendaryCardPrefabs;
public GameObject[] GarbagePrefabs => garbagePrefabs;
public float CardToGarbageRatio => cardToGarbageRatio;
public int CorrectSortPoints => correctSortPoints;
public int IncorrectSortPenalty => incorrectSortPenalty;
public int MissedItemPenalty => missedItemPenalty;

View File

@@ -18,14 +18,14 @@ namespace Core.Settings
float MaxBeltSpeed { get; }
AnimationCurve SpeedCurve { get; }
// Item Pools
GarbageItemDefinition[] GarbageItems { get; }
// Item Pools - Arrays of prefabs
GameObject[] NormalCardPrefabs { get; }
GameObject[] RareCardPrefabs { get; }
GameObject[] LegendaryCardPrefabs { get; }
GameObject[] GarbagePrefabs { get; }
// Spawn Weights
float NormalCardWeight { get; }
float RareCardWeight { get; }
float LegendCardWeight { get; }
float GarbageWeight { get; }
// Spawn Ratio (0 = all garbage, 1 = all cards)
float CardToGarbageRatio { get; }
// Scoring
int CorrectSortPoints { get; }

View File

@@ -1,8 +1,6 @@
using AppleHills.Data.CardSystem;
using Core.Settings;
using Data.CardSystem;
using Minigames.CardSorting.Core;
using Minigames.CardSorting.Data;
using System.Collections.Generic;
using UI.DragAndDrop.Core;
using UnityEngine;
@@ -20,8 +18,6 @@ namespace Minigames.CardSorting.Controllers
private readonly Transform spawnPoint;
private readonly Transform endPoint; // Visual end - scoring happens here
private readonly Transform despawnPoint; // Off-screen - destruction happens here
private readonly GameObject cardPrefab;
private readonly GameObject garbagePrefab;
private readonly ICardSortingSettings settings;
private readonly Transform spawnContainer; // Container for all spawned items
@@ -30,7 +26,7 @@ namespace Minigames.CardSorting.Controllers
private float currentSpeed;
private SortableItem lastSpawnedItem; // Track last spawned item for distance-based spawning
private float cachedSpawnOffsetX; // Cached random offset for next spawn
private bool isGameOver = false; // Flag to stop conveyor when game ends
private bool isGameOver; // Flag to stop conveyor when game ends
// Events - conveyor owns item lifecycle
public event System.Action<SortableItem> OnItemSpawned; // Fired when new item spawns
@@ -47,16 +43,12 @@ namespace Minigames.CardSorting.Controllers
Transform spawnPoint,
Transform endPoint,
Transform despawnPoint,
GameObject cardPrefab,
GameObject garbagePrefab,
ICardSortingSettings settings,
Transform spawnContainer)
{
this.spawnPoint = spawnPoint;
this.endPoint = endPoint;
this.despawnPoint = despawnPoint;
this.cardPrefab = cardPrefab;
this.garbagePrefab = garbagePrefab;
this.settings = settings;
this.spawnContainer = spawnContainer;
@@ -114,33 +106,60 @@ namespace Minigames.CardSorting.Controllers
/// <summary>
/// Spawn a new item at the spawn point.
/// Uses cardToGarbageRatio (0-1) to determine card vs garbage spawn chance.
/// If cards spawn, rarity is determined by array lengths.
/// </summary>
private SortableItem SpawnNewItem(float gameProgress)
private void SpawnNewItem(float gameProgress)
{
// Weighted random: card or garbage?
float totalWeight = settings.NormalCardWeight + settings.RareCardWeight +
settings.LegendCardWeight + settings.GarbageWeight;
if (totalWeight <= 0f)
{
Debug.LogWarning("[ConveyorBeltController] Total spawn weight is 0, cannot spawn items!");
return null;
}
float roll = Random.Range(0f, totalWeight);
// Use ratio to decide: card or garbage?
float cardChance = settings.CardToGarbageRatio; // 0 = all garbage, 1 = all cards
float roll = Random.Range(0f, 1f);
SortableItem item;
if (roll < settings.GarbageWeight)
if (roll < cardChance)
{
// Spawn garbage
item = SpawnGarbageItem();
// Spawn card - determine rarity based on array lengths
int normalWeight = settings.NormalCardPrefabs?.Length ?? 0;
int rareWeight = settings.RareCardPrefabs?.Length ?? 0;
int legendWeight = settings.LegendaryCardPrefabs?.Length ?? 0;
int totalCardWeight = normalWeight + rareWeight + legendWeight;
if (totalCardWeight <= 0)
{
Debug.LogWarning("[ConveyorBeltController] No card prefabs configured, spawning garbage instead");
item = SpawnRandomPrefabFromArray(settings.GarbagePrefabs, true, CardRarity.Normal);
}
else
{
float rarityRoll = Random.Range(0f, totalCardWeight);
CardRarity rarity;
GameObject[] targetArray;
if (rarityRoll < normalWeight)
{
rarity = CardRarity.Normal;
targetArray = settings.NormalCardPrefabs;
}
else if (rarityRoll < normalWeight + rareWeight)
{
rarity = CardRarity.Rare;
targetArray = settings.RareCardPrefabs;
}
else
{
rarity = CardRarity.Legendary;
targetArray = settings.LegendaryCardPrefabs;
}
item = SpawnRandomPrefabFromArray(targetArray, false, rarity);
}
}
else
{
// Spawn card - determine rarity, get random card from CardSystemManager
CardRarity rarity = DetermineRarity(roll);
item = SpawnCardItem(rarity);
// Spawn garbage
item = SpawnRandomPrefabFromArray(settings.GarbagePrefabs, true, CardRarity.Normal);
}
if (item != null)
@@ -154,114 +173,61 @@ namespace Minigames.CardSorting.Controllers
// Emit spawn event
OnItemSpawned?.Invoke(item);
}
return item;
}
private SortableItem SpawnGarbageItem()
{
if (settings.GarbageItems == null || settings.GarbageItems.Length == 0)
{
Debug.LogWarning("[ConveyorBeltController] No garbage items configured!");
return null;
}
GarbageItemDefinition garbage = SelectRandomGarbage();
// Apply random Y offset to spawn position
float randomOffsetY = Random.Range(settings.SpawnOffsetY.x, settings.SpawnOffsetY.y);
Vector3 spawnPos = spawnPoint.position + new Vector3(0f, randomOffsetY, 0f);
GameObject obj = Object.Instantiate(garbagePrefab, spawnPos, Quaternion.identity, spawnContainer);
SortableItem item = obj.GetComponent<SortableItem>();
if (item != null)
{
item.SetupAsGarbage(garbage);
// Apply card size (garbage items use same size as cards)
ApplyCardSize(item);
// Subscribe to item events
item.OnItemDroppedInBox += HandleItemDroppedInBox;
item.OnItemDroppedOnFloor += HandleItemDroppedOnFloor;
item.OnItemReturnedToConveyor += HandleItemReturnedToConveyor;
// Subscribe to drag events to remove from tracking
item.OnDragStarted += HandleItemDragStarted;
}
else
{
Debug.LogError("[ConveyorBeltController] Garbage prefab missing SortableItem component!");
Object.Destroy(obj);
return null;
}
return item;
}
private SortableItem SpawnCardItem(CardRarity rarity)
{
// Get a random card of the specified rarity
CardData cardData = GetRandomCardDataByRarity(rarity);
if (cardData == null)
{
Debug.LogWarning($"[ConveyorBeltController] No card data found for rarity {rarity}");
return null;
}
// Apply random Y offset to spawn position
float randomOffsetY = Random.Range(settings.SpawnOffsetY.x, settings.SpawnOffsetY.y);
Vector3 spawnPos = spawnPoint.position + new Vector3(0f, randomOffsetY, 0f);
GameObject obj = Object.Instantiate(cardPrefab, spawnPos, Quaternion.identity, spawnContainer);
SortableItem item = obj.GetComponent<SortableItem>();
if (item != null)
{
item.SetupAsCard(cardData);
// Apply card size
ApplyCardSize(item);
// Subscribe to item events
item.OnItemDroppedInBox += HandleItemDroppedInBox;
item.OnItemDroppedOnFloor += HandleItemDroppedOnFloor;
item.OnItemReturnedToConveyor += HandleItemReturnedToConveyor;
// Subscribe to drag events to remove from tracking
item.OnDragStarted += HandleItemDragStarted;
}
else
{
Debug.LogError("[ConveyorBeltController] Card prefab missing SortableItem component!");
Object.Destroy(obj);
return null;
}
return item;
}
/// <summary>
/// Helper method to get a random card of a specific rarity.
/// Gets a CardDefinition from CardSystemManager and converts to CardData.
/// Does NOT affect player's collection or open boosters.
/// Spawn a random prefab from an array.
/// Prefab is assumed to have SortableItem component and be visually pre-configured.
/// </summary>
private CardData GetRandomCardDataByRarity(CardRarity targetRarity)
private SortableItem SpawnRandomPrefabFromArray(GameObject[] prefabArray, bool isGarbage, CardRarity rarity)
{
// Get random card definition from manager
var definition = CardSystemManager.Instance.GetRandomCardDefinitionByRarity(targetRarity);
if (definition == null)
if (prefabArray == null || prefabArray.Length == 0)
{
Debug.LogWarning($"[ConveyorBeltController] No card definition found for rarity {targetRarity}");
Debug.LogWarning($"[ConveyorBeltController] No prefabs configured for {(isGarbage ? "garbage" : rarity.ToString())}!");
return null;
}
// Create CardData from definition using constructor
// This properly links the definition and sets all properties
return new CardData(definition);
// Pick random prefab
GameObject prefab = prefabArray[Random.Range(0, prefabArray.Length)];
if (prefab == null)
{
Debug.LogWarning($"[ConveyorBeltController] Null prefab in array for {(isGarbage ? "garbage" : rarity.ToString())}!");
return null;
}
// Apply random Y offset to spawn position
float randomOffsetY = Random.Range(settings.SpawnOffsetY.x, settings.SpawnOffsetY.y);
Vector3 spawnPos = spawnPoint.position + new Vector3(0f, randomOffsetY, 0f);
// Instantiate prefab
GameObject obj = Object.Instantiate(prefab, spawnPos, Quaternion.identity, spawnContainer);
SortableItem item = obj.GetComponent<SortableItem>();
if (item == null)
{
Debug.LogError($"[ConveyorBeltController] Prefab missing SortableItem component: {prefab.name}");
Object.Destroy(obj);
return null;
}
// Initialize item based on type (just sets flags and state machine)
if (isGarbage)
{
item.SetupAsGarbage();
}
else
{
item.SetupAsCard(rarity);
}
// Subscribe to item events
item.OnItemDroppedInBox += HandleItemDroppedInBox;
item.OnItemDroppedOnFloor += HandleItemDroppedOnFloor;
item.OnItemReturnedToConveyor += HandleItemReturnedToConveyor;
item.OnDragStarted += HandleItemDragStarted;
return item;
}
private void UpdateBeltSpeed(float gameProgress)
@@ -391,7 +357,7 @@ namespace Minigames.CardSorting.Controllers
if (!activeItems.Contains(item))
{
activeItems.Add(item);
Debug.Log($"[ConveyorBeltController] Item returned to conveyor: {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
Debug.Log($"[ConveyorBeltController] Item returned to conveyor: {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
}
}
@@ -420,42 +386,7 @@ namespace Minigames.CardSorting.Controllers
// Emit event for scoring
OnItemDroppedOnFloor?.Invoke(item);
Debug.Log($"[ConveyorBeltController] Item dropped on floor: {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
}
private CardRarity DetermineRarity(float roll)
{
// Adjust roll to be relative to card weights only (subtract garbage weight)
float adjusted = roll - settings.GarbageWeight;
if (adjusted < settings.NormalCardWeight)
return CardRarity.Normal;
if (adjusted < settings.NormalCardWeight + settings.RareCardWeight)
return CardRarity.Rare;
return CardRarity.Legendary;
}
private GarbageItemDefinition SelectRandomGarbage()
{
return settings.GarbageItems[Random.Range(0, settings.GarbageItems.Length)];
}
/// <summary>
/// Apply configured card size to spawned item.
/// </summary>
private void ApplyCardSize(SortableItem item)
{
if (item == null || item.Context == null || item.Context.RootTransform == null)
return;
// Get the RectTransform to resize (root object)
var rectTransform = item.Context.RootTransform.GetComponent<RectTransform>();
if (rectTransform != null)
{
rectTransform.sizeDelta = settings.CardSize;
}
Debug.Log($"[ConveyorBeltController] Item dropped on floor: {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
}
/// <summary>
@@ -477,7 +408,7 @@ namespace Minigames.CardSorting.Controllers
if (wasTracked)
{
Debug.Log($"[ConveyorBeltController] Item removed from tracking (picked up): {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
Debug.Log($"[ConveyorBeltController] Item removed from tracking (picked up): {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
}
}

View File

@@ -28,7 +28,7 @@ namespace Minigames.CardSorting.Core
{
if (item == null) return;
Debug.Log($"[ConveyorBeltSlot] Item dropped back on conveyor: {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
Debug.Log($"[ConveyorBeltSlot] Item dropped back on conveyor: {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
}
}
}

View File

@@ -1,36 +0,0 @@
using UnityEngine;
using UnityEngine.UI;
namespace Minigames.CardSorting.Core
{
/// <summary>
/// Simple sprite renderer for garbage items.
/// Parallel to CardDisplay for cards.
/// </summary>
public class GarbageVisual : MonoBehaviour
{
[Header("Visual Components")]
[SerializeField] private Image spriteRenderer;
/// <summary>
/// Update the displayed sprite.
/// </summary>
public void UpdateDisplay(Sprite sprite)
{
if (spriteRenderer != null)
{
spriteRenderer.sprite = sprite;
}
}
private void Awake()
{
// Auto-find Image component if not assigned
if (spriteRenderer == null)
{
spriteRenderer = GetComponent<Image>();
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: b707770fc3a6448ea0dcd1b2fbf41e00
timeCreated: 1763461824

View File

@@ -1,4 +1,4 @@
using AppleHills.Data.CardSystem;
using AppleHills.Data.CardSystem;
using Core;
using Core.SaveLoad;
using Minigames.CardSorting.Data;
@@ -21,10 +21,12 @@ namespace Minigames.CardSorting.Core
[Header("Configuration")]
[SerializeField] private string initialState = "OnConveyorState";
// Data tracking
private bool isGarbage;
private CardData cardData;
private GarbageItemDefinition garbageItem;
// Data tracking - set during spawn, no complex data initialization
private bool _isGarbage;
private CardRarity _rarity; // Only relevant for cards
// Track last hovered box for hover indicator feedback
private SortingBox _lastHoveredBox;
// Events - item emits notifications, conveyor subscribes
public event System.Action<SortableItem, SortingBox, bool> OnItemDroppedInBox;
@@ -34,9 +36,8 @@ namespace Minigames.CardSorting.Core
// Public accessors
public SortableItemContext Context => context;
public AppleMachine StateMachine => stateMachine;
public bool IsGarbage => isGarbage;
public CardData CardData => cardData;
public GarbageItemDefinition GarbageItem => garbageItem;
public bool IsGarbage => _isGarbage;
public CardRarity Rarity => _rarity;
/// <summary>
/// Get the correct box type for this item.
@@ -45,10 +46,10 @@ namespace Minigames.CardSorting.Core
{
get
{
if (isGarbage)
if (_isGarbage)
return BoxType.Trash;
return cardData.Rarity switch
return _rarity switch
{
CardRarity.Normal => BoxType.Normal,
CardRarity.Rare => BoxType.Rare,
@@ -71,43 +72,23 @@ namespace Minigames.CardSorting.Core
}
/// <summary>
/// Setup item as a card.
/// Setup item as a card. Prefab is already visually configured.
/// State machine auto-starts via Initialization component (calls StartMachine in Start).
/// </summary>
public void SetupAsCard(CardData data)
public void SetupAsCard(CardRarity rarity)
{
isGarbage = false;
cardData = data;
garbageItem = null;
if (context != null)
{
context.SetupAsCard(data);
}
if (stateMachine != null && !string.IsNullOrEmpty(initialState))
{
stateMachine.ChangeState(initialState);
}
_isGarbage = false;
_rarity = rarity;
}
/// <summary>
/// Setup item as garbage.
/// Setup item as garbage. Prefab is already visually configured.
/// State machine auto-starts via Initialization component (calls StartMachine in Start).
/// </summary>
public void SetupAsGarbage(GarbageItemDefinition garbage)
public void SetupAsGarbage()
{
isGarbage = true;
cardData = default;
garbageItem = garbage;
if (context != null)
{
context.SetupAsGarbage(garbage.Sprite);
}
if (stateMachine != null && !string.IsNullOrEmpty(initialState))
{
stateMachine.ChangeState(initialState);
}
_isGarbage = true;
_rarity = CardRarity.Normal; // Default, not used for garbage
}
protected override void OnDragStartedHook()
@@ -125,7 +106,7 @@ namespace Minigames.CardSorting.Core
}
// Default behavior if state doesn't handle
Logging.Debug($"[SortableItem] Drag started on {(isGarbage ? garbageItem.DisplayName : cardData.Name)}");
Logging.Debug($"[SortableItem] Drag started on {(_isGarbage ? "Garbage" : $"{_rarity} Card")}");
}
// TODO: Fixed when base slot/draggable reworked
@@ -143,6 +124,13 @@ namespace Minigames.CardSorting.Core
{
base.OnDragEndedHook();
// Hide hover indicator on any previously hovered box
if (_lastHoveredBox != null)
{
_lastHoveredBox.HideHoverIndicator();
_lastHoveredBox = null;
}
// Check what type of slot we're over
if (CurrentSlot is SortingBox box)
{
@@ -203,6 +191,7 @@ namespace Minigames.CardSorting.Core
UnityEngine.EventSystems.EventSystem.current.RaycastAll(eventData, raycastResults);
DraggableSlot hoveredSlot = null;
SortingBox hoveredBox = null;
// Find first slot (SortingBox or ConveyorBeltSlot) in raycast results
foreach (var result in raycastResults)
@@ -212,6 +201,7 @@ namespace Minigames.CardSorting.Core
if (box != null)
{
hoveredSlot = box;
hoveredBox = box;
break;
}
@@ -224,6 +214,26 @@ namespace Minigames.CardSorting.Core
}
}
// Update hover indicator on boxes
if (hoveredBox != _lastHoveredBox)
{
// Hide indicator on previously hovered box
if (_lastHoveredBox != null)
{
_lastHoveredBox.HideHoverIndicator();
}
// Show indicator on newly hovered box
if (hoveredBox != null)
{
// Check if this is the correct box for visual feedback
bool isCorrectBox = hoveredBox.ValidateItem(this);
hoveredBox.ShowHoverIndicator(isCorrectBox);
}
_lastHoveredBox = hoveredBox;
}
// Update current slot (used in OnDragEndedHook)
if (hoveredSlot != null && hoveredSlot != CurrentSlot)
{

View File

@@ -1,6 +1,5 @@
using AppleHills.Data.CardSystem;
using Core.SaveLoad;
using UI.CardSystem;
using Core.SaveLoad;
using Minigames.CardSorting.Data;
using UI.CardSystem.StateMachine;
using UnityEngine;
@@ -9,13 +8,13 @@ namespace Minigames.CardSorting.Core
/// <summary>
/// Shared context for sortable item states.
/// Provides access to common components and data that states need.
/// Routes data to appropriate visual component (CardDisplay for cards, GarbageVisual for garbage).
/// Prefabs handle their own visual setup - no runtime initialization needed.
/// </summary>
public class SortableItemContext : MonoBehaviour
{
[Header("Visual Components (one or the other)")]
[SerializeField] private CardDisplay cardDisplay; // For cards
[SerializeField] private GarbageVisual garbageVisual; // For garbage
[Header("Effect Components")]
[Tooltip("Top-level overlay image for visual effects (blink red, etc). Should be hidden by default.")]
[SerializeField] private UnityEngine.UI.Image blinkOverlayImage;
[Header("Shared Components")]
[SerializeField] private CardAnimator animator;
@@ -24,8 +23,7 @@ namespace Minigames.CardSorting.Core
private AppleMachine stateMachine;
// Public accessors
public CardDisplay CardDisplay => cardDisplay;
public GarbageVisual GarbageVisual => garbageVisual;
public UnityEngine.UI.Image BlinkOverlayImage => blinkOverlayImage;
public CardAnimator Animator => animator;
public Transform VisualTransform => visualTransform;
public AppleMachine StateMachine => stateMachine;
@@ -42,6 +40,20 @@ namespace Minigames.CardSorting.Core
private void Awake()
{
// Capture original transform for drag animations
// This preserves the prefab's configured scale (e.g., 0.05 for world-space Canvas)
var currentScale = transform.localScale;
if (currentScale.x < 0.01f && currentScale.y < 0.01f && currentScale.z < 0.01f)
{
OriginalScale = Vector3.one; // Fallback if scale is ~0
}
else
{
OriginalScale = currentScale;
}
OriginalPosition = transform.localPosition;
OriginalRotation = transform.localRotation;
// Auto-find components if not assigned
if (visualTransform == null)
{
@@ -64,76 +76,16 @@ namespace Minigames.CardSorting.Core
}
}
if (cardDisplay == null && visualTransform != null)
{
cardDisplay = visualTransform.GetComponentInChildren<CardDisplay>();
}
if (garbageVisual == null && visualTransform != null)
{
garbageVisual = visualTransform.GetComponentInChildren<GarbageVisual>();
}
stateMachine = GetComponentInChildren<AppleMachine>();
}
/// <summary>
/// Setup as card item - CardDisplay handles all rendering.
/// </summary>
public void SetupAsCard(CardData cardData)
{
// Capture original root transform for drag animations
// This preserves the tiny world-space Canvas scale (e.g., 0.05)
var currentScale = transform.localScale;
if (currentScale.x < 0.01f && currentScale.y < 0.01f && currentScale.z < 0.01f)
{
OriginalScale = Vector3.one; // Fallback if scale is ~0
}
else
{
OriginalScale = currentScale;
}
OriginalPosition = transform.localPosition;
OriginalRotation = transform.localRotation;
if (cardDisplay != null)
// Hide blink overlay initially
if (blinkOverlayImage != null)
{
cardDisplay.SetupCard(cardData);
}
else
{
Debug.LogError($"[SortableItemContext] CardDisplay not found on {name}");
blinkOverlayImage.enabled = false;
}
}
/// <summary>
/// Setup as garbage item - simple sprite display.
/// </summary>
public void SetupAsGarbage(Sprite sprite)
{
// Capture original root transform for drag animations
// This preserves the tiny world-space Canvas scale (e.g., 0.05)
var currentScale = transform.localScale;
if (currentScale.x < 0.01f && currentScale.y < 0.01f && currentScale.z < 0.01f)
{
OriginalScale = Vector3.one; // Fallback if scale is ~0
}
else
{
OriginalScale = currentScale;
}
OriginalPosition = transform.localPosition;
OriginalRotation = transform.localRotation;
if (garbageVisual != null)
{
garbageVisual.UpdateDisplay(sprite);
}
else
{
Debug.LogError($"[SortableItemContext] GarbageVisual not found on {name}");
}
}
}
}

View File

@@ -0,0 +1 @@


View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0ebfb8d9e40f4532b3a3919ced988330
timeCreated: 1766069349

View File

@@ -13,8 +13,47 @@ namespace Minigames.CardSorting.Core
[Header("Box Configuration")]
[SerializeField] private BoxType boxType;
[Header("Visual Feedback")]
[Tooltip("Sprite renderer to show when an item is hovering over this box")]
[SerializeField] private SpriteRenderer hoverIndicator;
public BoxType BoxType => boxType;
private void Start()
{
// Hide hover indicator on start
if (hoverIndicator != null)
{
hoverIndicator.enabled = false;
}
}
/// <summary>
/// Show the hover indicator when an item is hovering over this box.
/// </summary>
/// <param name="isCorrect">If true, tints the indicator green. Otherwise uses default color.</param>
public void ShowHoverIndicator(bool isCorrect = false)
{
if (hoverIndicator != null)
{
hoverIndicator.enabled = true;
// Tint green if correct box, white otherwise
hoverIndicator.color = isCorrect ? Color.green : Color.white;
}
}
/// <summary>
/// Hide the hover indicator when an item stops hovering.
/// </summary>
public void HideHoverIndicator()
{
if (hoverIndicator != null)
{
hoverIndicator.enabled = false;
}
}
/// <summary>
/// Check if item belongs in this box.
/// </summary>

View File

@@ -21,8 +21,6 @@ namespace Minigames.CardSorting.Core
[SerializeField] private Transform conveyorSpawnPoint;
[SerializeField] private Transform conveyorEndPoint; // Visual end - items scored as missed here
[SerializeField] private Transform conveyorDespawnPoint; // Off-screen - items destroyed here
[SerializeField] private GameObject sortableCardPrefab;
[SerializeField] private GameObject sortableGarbagePrefab;
[SerializeField] private SortingBox[] sortingBoxes;
[SerializeField] private Transform spawnedItemsContainer; // Container for all spawned items (optional, will auto-create if null)
@@ -44,8 +42,6 @@ namespace Minigames.CardSorting.Core
conveyorSpawnPoint,
conveyorEndPoint,
conveyorDespawnPoint,
sortableCardPrefab,
sortableGarbagePrefab,
_settings,
GetOrCreateSpawnContainer()
);
@@ -213,23 +209,24 @@ namespace Minigames.CardSorting.Core
}
// Blink the item red (if it still exists)
if (item != null && item.Context != null && item.Context.Animator != null)
if (item != null && item.Context != null && item.Context.Animator != null && item.Context.BlinkOverlayImage != null)
{
UnityEngine.UI.Image imageToBlink = null;
// Show overlay before blinking
item.Context.BlinkOverlayImage.enabled = true;
if (item.Context.CardDisplay != null)
{
imageToBlink = item.Context.CardDisplay.GetComponent<UnityEngine.UI.Image>();
}
else if (item.Context.GarbageVisual != null)
{
imageToBlink = item.Context.GarbageVisual.GetComponent<UnityEngine.UI.Image>();
}
item.Context.Animator.BlinkRed(item.Context.BlinkOverlayImage, 0.15f);
if (imageToBlink != null)
{
item.Context.Animator.BlinkRed(imageToBlink, 0.15f);
}
// Hide overlay after blink duration (assuming blink duration + buffer)
StartCoroutine(HideOverlayAfterDelay(item.Context.BlinkOverlayImage, 0.5f));
}
}
private System.Collections.IEnumerator HideOverlayAfterDelay(UnityEngine.UI.Image overlay, float delay)
{
yield return new WaitForSeconds(delay);
if (overlay != null)
{
overlay.enabled = false;
}
}
@@ -310,7 +307,7 @@ namespace Minigames.CardSorting.Core
{
if (item != null && item.gameObject != null)
{
Logging.Debug($"[SortingGameManager] Destroying orphaned item: {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
Logging.Debug($"[SortingGameManager] Destroying orphaned item: {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
Destroy(item.gameObject);
}
}
@@ -364,7 +361,7 @@ namespace Minigames.CardSorting.Core
// Forward to public event for UI/other systems
OnItemSpawned?.Invoke(item);
Logging.Debug($"[SortingGameManager] Item spawned: {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
Logging.Debug($"[SortingGameManager] Item spawned: {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
}
/// <summary>
@@ -383,11 +380,11 @@ namespace Minigames.CardSorting.Core
Score.RecordMissedItem();
PlayWrongStateFeedback(item);
LoseLife();
Logging.Debug($"[SortingGameManager] Trash fell off belt! {item.GarbageItem?.DisplayName} - PENALTY");
Logging.Debug($"[SortingGameManager] Trash fell off belt! Garbage - PENALTY");
}
else
{
Logging.Debug($"[SortingGameManager] Card fell off belt: {item.CardData?.Name} - no penalty");
Logging.Debug($"[SortingGameManager] Card fell off belt: {item.Rarity} Card - no penalty");
}
// Fire global fell off belt event for effects
@@ -412,11 +409,11 @@ namespace Minigames.CardSorting.Core
Score.RecordIncorrectSort();
PlayWrongStateFeedback(item);
LoseLife();
Logging.Debug($"[SortingGameManager] Trash dropped on floor! {item.GarbageItem?.DisplayName} - PENALTY");
Logging.Debug($"[SortingGameManager] Trash dropped on floor! Garbage - PENALTY");
}
else
{
Logging.Debug($"[SortingGameManager] Card dropped on floor: {item.CardData?.Name} - no penalty");
Logging.Debug($"[SortingGameManager] Card dropped on floor: {item.Rarity} Card - no penalty");
}
}
@@ -426,7 +423,8 @@ namespace Minigames.CardSorting.Core
/// </summary>
private void OnConveyorItemDespawned(SortableItem item)
{
Logging.Debug($"[SortingGameManager] Item despawned: {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
Logging.Debug($"[SortingGameManager] Item despawned: {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
// Destroy the item
if (item != null)
@@ -446,7 +444,7 @@ namespace Minigames.CardSorting.Core
if (correct)
{
Score.RecordCorrectSort();
Logging.Debug($"[SortingGameManager] Correct sort! {item.CardData?.Name ?? item.GarbageItem?.DisplayName}");
Logging.Debug($"[SortingGameManager] Correct sort! {(item.IsGarbage ? "Garbage" : $"{item.Rarity} Card")}");
// Fire global correct sort event for effects
OnItemSortedCorrectly?.Invoke(item);
@@ -460,14 +458,14 @@ namespace Minigames.CardSorting.Core
Score.RecordIncorrectSort();
PlayWrongStateFeedback(item);
LoseLife();
Logging.Debug($"[SortingGameManager] Incorrect trash sort! {item.GarbageItem?.DisplayName} - PENALTY");
Logging.Debug($"[SortingGameManager] Incorrect trash sort! Garbage - PENALTY");
// Fire global incorrect sort event for effects
OnItemSortedIncorrectly?.Invoke(item);
}
else
{
Logging.Debug($"[SortingGameManager] Card sorted incorrectly: {item.CardData?.Name} - no penalty");
Logging.Debug($"[SortingGameManager] Card sorted incorrectly: {item.Rarity} Card - no penalty");
}
}

View File

@@ -0,0 +1 @@


View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d88deb3df9e54bdb83b9a7ed1c7e3e27
timeCreated: 1766069228

View File

@@ -1,38 +0,0 @@
using UnityEngine;
namespace Minigames.CardSorting.Data
{
/// <summary>
/// Definition for garbage items (banana peels, cans, receipts, etc.).
/// Cards use existing CardDefinition from CardSystemManager.
/// </summary>
[CreateAssetMenu(fileName = "GarbageItem", menuName = "Minigames/CardSorting/GarbageItem", order = 0)]
public class GarbageItemDefinition : ScriptableObject
{
[Tooltip("Unique identifier for this garbage item")]
[SerializeField] private string itemId;
[Tooltip("Display name for debugging")]
[SerializeField] private string displayName;
[Tooltip("Sprite to display for this garbage item")]
[SerializeField] private Sprite sprite;
// Public accessors
public string ItemId => itemId;
public string DisplayName => displayName;
public Sprite Sprite => sprite;
#if UNITY_EDITOR
private void OnValidate()
{
// Auto-generate itemId from asset name if empty
if (string.IsNullOrEmpty(itemId))
{
itemId = name;
}
}
#endif
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 2e69a2167710437798b1980126d5a4f6
timeCreated: 1763461765

View File

@@ -57,28 +57,12 @@ namespace Minigames.CardSorting.StateMachine.States
private void StartBlinkingRed()
{
if (_context.Animator == null) return;
if (_context.Animator == null || _context.BlinkOverlayImage == null) return;
// Get the image to tint (CardDisplay or GarbageVisual)
UnityEngine.UI.Image imageToBlink = null;
// Show overlay before blinking
_context.BlinkOverlayImage.enabled = true;
if (_context.CardDisplay != null)
{
imageToBlink =
_context.CardDisplay.GetComponent<UnityEngine.UI.Image>()
?? _context.CardDisplay.GetComponentInChildren<UnityEngine.UI.Image>();
}
else if (_context.GarbageVisual != null)
{
imageToBlink =
_context.GarbageVisual.GetComponent<UnityEngine.UI.Image>()
?? _context.GarbageVisual.GetComponentInChildren<UnityEngine.UI.Image>();
}
if (imageToBlink != null)
{
_context.Animator.BlinkRed(imageToBlink);
}
_context.Animator.BlinkRed(_context.BlinkOverlayImage);
}
private void OnDisable()