chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
214
Assets/Scripts/SpecialMoveCardSystem/CardBehaviour.cs
Normal file
214
Assets/Scripts/SpecialMoveCardSystem/CardBehaviour.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles world pickup visuals and behavior for a card: hover, rotate, glow, and pickup.
|
||||
/// Expect a trigger collider and optionally a Rigidbody set to kinematic.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class CardBehaviour : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private CardData cardData;
|
||||
[Header("Visual References")]
|
||||
[Tooltip("Renderer(s) that should have emissive color applied.")]
|
||||
[SerializeField] private Renderer[] emissiveRenderers;
|
||||
[Tooltip("Optional glow mesh (cube/cylinder/etc) used for volumetric-looking glow.")]
|
||||
[SerializeField] private Transform glowCube; // kept for backward-compat; any mesh child works
|
||||
[Tooltip("Optional explicit renderer for the glow mesh. If assigned, takes priority over 'glowCube'.")]
|
||||
[SerializeField] private Renderer glowRenderer;
|
||||
[Tooltip("Optional light to boost bloom.")]
|
||||
[SerializeField] private Light glowLight;
|
||||
|
||||
[Header("Glow Sync")]
|
||||
[Tooltip("If true, reads emission color from the first emissive renderer's material and uses it for glow, without overriding the material. If false, uses CardData color to set emission on the renderers.")]
|
||||
[SerializeField] private bool deriveGlowFromMaterial = true;
|
||||
[Tooltip("Scales the glow intensity derived from material or CardData.")]
|
||||
[SerializeField] private float glowIntensityMultiplier = 1f;
|
||||
|
||||
[Header("Motion")]
|
||||
[SerializeField] private float defaultHoverAmplitude = 0.25f;
|
||||
[SerializeField] private float defaultHoverSpeed = 1.0f;
|
||||
[SerializeField] private float defaultRotationSpeed = 35f;
|
||||
|
||||
private Vector3 basePos;
|
||||
private float hoverPhase;
|
||||
private MaterialPropertyBlock mpb;
|
||||
private Collider trigger;
|
||||
private Color baseEmissive = Color.white;
|
||||
|
||||
[Header("Glow Pulse (Optional)")]
|
||||
[SerializeField] private bool pulseGlow = false;
|
||||
[SerializeField] private float pulseSpeed = 2f;
|
||||
[SerializeField, Range(0f, 2f)] private float pulseAmplitude = 0.25f;
|
||||
|
||||
public CardData Data => cardData;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
var col = GetComponent<Collider>();
|
||||
if (col) col.isTrigger = true;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Skip if card system is disabled globally - destroy this card
|
||||
if (CardSpawner.CARD_SYSTEM_DISABLED)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
trigger = GetComponent<Collider>();
|
||||
if (trigger) trigger.isTrigger = true;
|
||||
EnsureMaterialPropertyBlock();
|
||||
basePos = transform.position;
|
||||
ApplyVisuals();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
EnsureMaterialPropertyBlock();
|
||||
ApplyVisuals();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
float amp = (cardData && cardData.hoverAmplitude > 0f) ? cardData.hoverAmplitude : defaultHoverAmplitude;
|
||||
float spd = (cardData && cardData.hoverSpeed > 0f) ? cardData.hoverSpeed : defaultHoverSpeed;
|
||||
float rot = (cardData && cardData.rotationSpeed > 0f) ? cardData.rotationSpeed : defaultRotationSpeed;
|
||||
|
||||
hoverPhase += spd * Time.deltaTime;
|
||||
float offset = Mathf.Sin(hoverPhase) * amp;
|
||||
transform.position = basePos + Vector3.up * offset;
|
||||
transform.Rotate(Vector3.up, rot * Time.deltaTime, Space.World);
|
||||
|
||||
// Optional pulsing for volumetric glow look
|
||||
if (pulseGlow)
|
||||
{
|
||||
float f = 1f + Mathf.Sin(Time.time * pulseSpeed) * pulseAmplitude;
|
||||
Color current = baseEmissive * f;
|
||||
ApplyGlowOnly(current);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Look for a PlayerPocket on the other object or its parents
|
||||
var pocket = other.GetComponentInParent<PlayerPocket>();
|
||||
if (pocket == null) return;
|
||||
|
||||
if (pocket.TryAddCard(cardData))
|
||||
{
|
||||
if (cardData && cardData.particlePrefab)
|
||||
{
|
||||
Instantiate(cardData.particlePrefab, transform.position, Quaternion.identity);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyVisuals()
|
||||
{
|
||||
EnsureMaterialPropertyBlock();
|
||||
|
||||
// Determine target emissive color
|
||||
Color emissive;
|
||||
if (deriveGlowFromMaterial && TryGetMaterialEmissionColor(out emissive))
|
||||
{
|
||||
emissive *= glowIntensityMultiplier;
|
||||
}
|
||||
else
|
||||
{
|
||||
Color c = cardData ? cardData.color : Color.white;
|
||||
float intensity = cardData ? cardData.emissionIntensity : 2.5f;
|
||||
emissive = c * Mathf.LinearToGammaSpace(intensity) * glowIntensityMultiplier;
|
||||
|
||||
// Only in this branch we override renderer emission to match CardData
|
||||
if (emissiveRenderers != null)
|
||||
{
|
||||
for (int i = 0; i < emissiveRenderers.Length; i++)
|
||||
{
|
||||
var r = emissiveRenderers[i];
|
||||
if (r == null) continue;
|
||||
r.GetPropertyBlock(mpb);
|
||||
mpb.SetColor("_EmissionColor", emissive);
|
||||
r.SetPropertyBlock(mpb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseEmissive = emissive; // store for pulse updates
|
||||
ApplyGlowOnly(emissive);
|
||||
}
|
||||
|
||||
private void ApplyGlowOnly(Color emissive)
|
||||
{
|
||||
EnsureMaterialPropertyBlock();
|
||||
|
||||
// Prefer explicit glowRenderer if provided
|
||||
Renderer targetRend = glowRenderer;
|
||||
if (targetRend == null && glowCube != null)
|
||||
targetRend = glowCube.GetComponent<Renderer>();
|
||||
|
||||
if (targetRend)
|
||||
{
|
||||
targetRend.GetPropertyBlock(mpb);
|
||||
mpb.SetColor("_EmissionColor", emissive);
|
||||
targetRend.SetPropertyBlock(mpb);
|
||||
}
|
||||
|
||||
if (glowLight)
|
||||
{
|
||||
Color lightColor = new Color(Mathf.Clamp01(emissive.r), Mathf.Clamp01(emissive.g), Mathf.Clamp01(emissive.b));
|
||||
glowLight.color = lightColor;
|
||||
glowLight.intensity = Mathf.Max(1.5f, emissive.maxColorComponent * 2.5f);
|
||||
glowLight.range = 3f;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetMaterialEmissionColor(out Color color)
|
||||
{
|
||||
color = Color.black;
|
||||
if (emissiveRenderers == null) return false;
|
||||
|
||||
// Common property names across pipelines
|
||||
string[] props = { "_EmissionColor", "_EmissiveColor", "_EmissiveColorLDR" };
|
||||
|
||||
for (int i = 0; i < emissiveRenderers.Length; i++)
|
||||
{
|
||||
var r = emissiveRenderers[i];
|
||||
if (r == null) continue;
|
||||
var mat = r.sharedMaterial != null ? r.sharedMaterial : r.material;
|
||||
if (mat == null) continue;
|
||||
for (int p = 0; p < props.Length; p++)
|
||||
{
|
||||
string prop = props[p];
|
||||
if (mat.HasProperty(prop))
|
||||
{
|
||||
color = mat.GetColor(prop);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Assign the data at runtime for generic prefabs.</summary>
|
||||
public void SetData(CardData data)
|
||||
{
|
||||
cardData = data;
|
||||
ApplyVisuals();
|
||||
}
|
||||
|
||||
private void EnsureMaterialPropertyBlock()
|
||||
{
|
||||
if (mpb == null)
|
||||
{
|
||||
mpb = new MaterialPropertyBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe7b3800b1e49d2949fe10cc9916edb9
|
||||
38
Assets/Scripts/SpecialMoveCardSystem/CardData.cs
Normal file
38
Assets/Scripts/SpecialMoveCardSystem/CardData.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// ScriptableObject defining metadata for a special move card.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Special Move Cards/Card Data", fileName = "CardData")]
|
||||
public class CardData : ScriptableObject
|
||||
{
|
||||
[Header("Identity")]
|
||||
public CardType type;
|
||||
[Tooltip("Display name of the special move.")]
|
||||
public string moveName;
|
||||
[Tooltip("Animator state/clip or Trigger name to play on pickup use.")]
|
||||
public string animationTriggerName;
|
||||
|
||||
[Header("Visuals")]
|
||||
public Color color = Color.white;
|
||||
public Sprite icon;
|
||||
[Tooltip("Optional particle effect to spawn on pickup or use.")]
|
||||
public GameObject particlePrefab;
|
||||
|
||||
[Header("Tuning")]
|
||||
[Tooltip("Hover amplitude for world pickup (meters). If 0, uses default on CardBehaviour.")]
|
||||
public float hoverAmplitude = 0.25f;
|
||||
[Tooltip("Hover speed for world pickup (Hz). If 0, uses default on CardBehaviour.")]
|
||||
public float hoverSpeed = 1.0f;
|
||||
[Tooltip("Self-rotation speed in degrees per second. If 0, uses default on CardBehaviour.")]
|
||||
public float rotationSpeed = 35f;
|
||||
[Tooltip("Emission intensity for the card material.")]
|
||||
public float emissionIntensity = 2.5f;
|
||||
|
||||
[Header("Optional Ability Params")]
|
||||
[Tooltip("Optional scalar param that other systems can read.")]
|
||||
public float powerMultiplier = 1f;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SpecialMoveCardSystem/CardData.cs.meta
Normal file
2
Assets/Scripts/SpecialMoveCardSystem/CardData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c18ff6809eae0eda8780bd1c0ffa057
|
||||
352
Assets/Scripts/SpecialMoveCardSystem/CardSpawner.cs
Normal file
352
Assets/Scripts/SpecialMoveCardSystem/CardSpawner.cs
Normal file
@@ -0,0 +1,352 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns card pickups similar to common pickup systems:
|
||||
/// - Maintains a target count of active cards (default 2)
|
||||
/// - Spawns at random spawn points or within an area
|
||||
/// - When a card is picked up (destroyed), schedules a delayed respawn to keep the target count
|
||||
/// </summary>
|
||||
public class CardSpawner : MonoBehaviour
|
||||
{
|
||||
// ========== MASTER DISABLE FLAG ==========
|
||||
// Set to TRUE to completely disable the card/special move system
|
||||
// This is a quick toggle - you can implement a proper system later
|
||||
public static bool CARD_SYSTEM_DISABLED = true;
|
||||
// ==========================================
|
||||
|
||||
private static readonly List<CardSpawner> s_instances = new List<CardSpawner>();
|
||||
[Header("Card Prefab & Data")]
|
||||
[Tooltip("Generic Card prefab that contains CardBehaviour (used if 'Card Prefabs' list is empty).")]
|
||||
public GameObject cardPrefab;
|
||||
[Tooltip("If provided, a random prefab from this list will be spawned instead of the single prefab above. Each prefab can carry its own CardData and visuals (e.g., per-color prefabs).")]
|
||||
public List<GameObject> cardPrefabs = new List<GameObject>();
|
||||
[Tooltip("Pool of possible CardData to spawn from.")]
|
||||
public List<CardData> cardPool = new List<CardData>();
|
||||
|
||||
[Header("Spawning")]
|
||||
public List<Transform> spawnPoints = new List<Transform>();
|
||||
[Tooltip("If no spawn points, use a box area in local space.")]
|
||||
public Vector3 areaSize = new Vector3(10, 0, 10);
|
||||
[Tooltip("Number of cards to keep alive concurrently. Typical big-game behavior uses small numbers like 1-2.")]
|
||||
public int targetCardsInScene = 2;
|
||||
[Tooltip("Random delay before a replacement card is spawned after pickup (x..y seconds).")]
|
||||
public Vector2 respawnDelay = new Vector2(2f, 5f);
|
||||
[Tooltip("Ensure only one active card per spawn point at a time.")]
|
||||
public bool useUniqueSpawnPoints = true;
|
||||
|
||||
private readonly HashSet<GameObject> alive = new HashSet<GameObject>();
|
||||
private readonly Dictionary<int, GameObject> occupiedPoints = new Dictionary<int, GameObject>();
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// Skip if card system is disabled
|
||||
if (CARD_SYSTEM_DISABLED)
|
||||
{
|
||||
Debug.Log("[CardSpawner] Card System is DISABLED - skipping initialization");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_instances.Contains(this)) s_instances.Add(this);
|
||||
GameEvents.CardPicked += OnAnyCardPicked;
|
||||
MaintainTargetCount();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GameEvents.CardPicked -= OnAnyCardPicked;
|
||||
alive.RemoveWhere(go => go == null);
|
||||
occupiedPoints.Clear();
|
||||
s_instances.Remove(this);
|
||||
}
|
||||
|
||||
private void TrySpawnOnce()
|
||||
{
|
||||
CleanupDead();
|
||||
if (alive.Count >= targetCardsInScene) return;
|
||||
// Build disallowed type set to keep two cards different
|
||||
HashSet<CardType> disallowed = BuildDisallowedTypes();
|
||||
|
||||
Vector3 pos;
|
||||
Quaternion rot = Quaternion.identity;
|
||||
int usedIndex = -1;
|
||||
|
||||
if (spawnPoints != null && spawnPoints.Count > 0)
|
||||
{
|
||||
// Choose a free spawn point if required
|
||||
int safety = 25;
|
||||
int idx = -1;
|
||||
while (safety-- > 0)
|
||||
{
|
||||
int r = Random.Range(0, spawnPoints.Count);
|
||||
if (!useUniqueSpawnPoints || !occupiedPoints.ContainsKey(r)) { idx = r; break; }
|
||||
}
|
||||
if (idx < 0) idx = Random.Range(0, spawnPoints.Count); // fallback
|
||||
var t = spawnPoints[idx];
|
||||
pos = t.position;
|
||||
rot = t.rotation;
|
||||
usedIndex = idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 half = areaSize * 0.5f;
|
||||
Vector3 local = new Vector3(
|
||||
Random.Range(-half.x, half.x),
|
||||
Random.Range(-half.y, half.y),
|
||||
Random.Range(-half.z, half.z));
|
||||
pos = transform.TransformPoint(local);
|
||||
}
|
||||
|
||||
var go = SpawnCard(pos, rot, disallowed);
|
||||
if (go == null) return; // nothing feasible to spawn
|
||||
alive.Add(go);
|
||||
if (useUniqueSpawnPoints && usedIndex >= 0)
|
||||
occupiedPoints[usedIndex] = go;
|
||||
|
||||
// Track destruction to free spawn point
|
||||
var token = go.AddComponent<SpawnedCardToken>();
|
||||
token.Init(this, usedIndex);
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (spawnPoints == null || spawnPoints.Count == 0)
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Gizmos.DrawWireCube(Vector3.zero, areaSize);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Enemy-death drops API ---
|
||||
/// <summary>
|
||||
/// Called by external systems to attempt a drop at a world position with a given probability.
|
||||
/// Respects each spawner's targetCardsInScene limit.
|
||||
/// </summary>
|
||||
public static void TrySpawnOnEnemyDeath(Vector3 position, float chance = 0.25f)
|
||||
{
|
||||
if (s_instances.Count == 0) return;
|
||||
if (Random.value > Mathf.Clamp01(chance)) return;
|
||||
|
||||
// Pick the first spawner that has capacity
|
||||
for (int i = 0; i < s_instances.Count; i++)
|
||||
{
|
||||
var sp = s_instances[i];
|
||||
if (sp == null || !sp.isActiveAndEnabled) continue;
|
||||
sp.CleanupDead();
|
||||
if (sp.alive.Count < sp.targetCardsInScene)
|
||||
{
|
||||
sp.SpawnAtPosition(position + Vector3.up * 0.25f, Quaternion.identity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnAtPosition(Vector3 pos, Quaternion rot)
|
||||
{
|
||||
// For death-drops, also try to keep existing type different if only one is alive
|
||||
var disallowed = BuildDisallowedTypes();
|
||||
var go = SpawnCard(pos, rot, disallowed);
|
||||
if (go == null) return;
|
||||
alive.Add(go);
|
||||
var token = go.AddComponent<SpawnedCardToken>();
|
||||
token.Init(this, -1); // no spawn point occupancy
|
||||
}
|
||||
|
||||
private void MaintainTargetCount()
|
||||
{
|
||||
CleanupDead();
|
||||
while (alive.Count < targetCardsInScene)
|
||||
TrySpawnOnce();
|
||||
}
|
||||
|
||||
private void CleanupDead()
|
||||
{
|
||||
// Remove destroyed refs and free occupied points
|
||||
if (alive.Count == 0) return;
|
||||
var toRemove = new List<GameObject>();
|
||||
foreach (var go in alive)
|
||||
{
|
||||
if (go == null) toRemove.Add(go);
|
||||
}
|
||||
for (int i = 0; i < toRemove.Count; i++)
|
||||
alive.Remove(toRemove[i]);
|
||||
|
||||
// occupiedPoints are freed in NotifyCardDestroyed; this is extra safety if something slipped through
|
||||
var free = new List<int>();
|
||||
foreach (var kvp in occupiedPoints)
|
||||
{
|
||||
if (kvp.Value == null) free.Add(kvp.Key);
|
||||
}
|
||||
for (int i = 0; i < free.Count; i++)
|
||||
occupiedPoints.Remove(free[i]);
|
||||
}
|
||||
|
||||
private void OnAnyCardPicked(CardData _)
|
||||
{
|
||||
// Schedule a replacement after a small random delay like big games do
|
||||
float delay = Random.Range(respawnDelay.x, respawnDelay.y);
|
||||
StartCoroutine(RespawnDelayed(delay));
|
||||
}
|
||||
|
||||
private IEnumerator RespawnDelayed(float delay)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
MaintainTargetCount();
|
||||
}
|
||||
|
||||
internal void NotifyCardDestroyed(SpawnedCardToken token)
|
||||
{
|
||||
if (token == null) return;
|
||||
// Free occupancy & alive set; spawning replacement happens via CardPicked event or periodic Maintain
|
||||
if (token.spawnPointIndex >= 0 && occupiedPoints.ContainsKey(token.spawnPointIndex))
|
||||
occupiedPoints.Remove(token.spawnPointIndex);
|
||||
alive.Remove(token.gameObject);
|
||||
}
|
||||
|
||||
public class SpawnedCardToken : MonoBehaviour
|
||||
{
|
||||
private CardSpawner owner;
|
||||
internal int spawnPointIndex = -1;
|
||||
public void Init(CardSpawner o, int index)
|
||||
{
|
||||
owner = o; spawnPointIndex = index;
|
||||
}
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (owner != null) owner.NotifyCardDestroyed(this);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper logic to enforce different types when two are present ---
|
||||
private HashSet<CardType> BuildDisallowedTypes()
|
||||
{
|
||||
var disallowed = new HashSet<CardType>();
|
||||
// Only enforce strict difference when target is 2 and we currently have exactly 1 alive card
|
||||
if (targetCardsInScene == 2 && alive.Count == 1)
|
||||
{
|
||||
foreach (var go in alive)
|
||||
{
|
||||
if (go == null) continue;
|
||||
var cb = go.GetComponent<CardBehaviour>();
|
||||
if (cb != null && cb.Data != null)
|
||||
{
|
||||
disallowed.Add(cb.Data.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return disallowed;
|
||||
}
|
||||
|
||||
private GameObject SpawnCard(Vector3 pos, Quaternion rot, HashSet<CardType> disallowed)
|
||||
{
|
||||
bool multiPrefabs = cardPrefabs != null && cardPrefabs.Count > 0;
|
||||
if (!multiPrefabs && cardPrefab == null) return null;
|
||||
|
||||
// Collect prefab capabilities
|
||||
var fixedPrefabs = new List<(GameObject prefab, CardType type)>();
|
||||
var genericPrefabs = new List<GameObject>();
|
||||
|
||||
if (multiPrefabs)
|
||||
{
|
||||
for (int i = 0; i < cardPrefabs.Count; i++)
|
||||
{
|
||||
var pf = cardPrefabs[i];
|
||||
if (pf == null) continue;
|
||||
var cb = pf.GetComponent<CardBehaviour>();
|
||||
if (cb != null && cb.Data != null)
|
||||
fixedPrefabs.Add((pf, cb.Data.type));
|
||||
else
|
||||
genericPrefabs.Add(pf);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var pf = cardPrefab;
|
||||
var cb = pf != null ? pf.GetComponent<CardBehaviour>() : null;
|
||||
if (cb != null && cb.Data != null)
|
||||
fixedPrefabs.Add((pf, cb.Data.type));
|
||||
else if (pf != null)
|
||||
genericPrefabs.Add(pf);
|
||||
}
|
||||
|
||||
// Determine feasible types
|
||||
var feasibleTypes = new List<CardType>();
|
||||
var fixedTypes = new HashSet<CardType>();
|
||||
for (int i = 0; i < fixedPrefabs.Count; i++) fixedTypes.Add(fixedPrefabs[i].type);
|
||||
foreach (var t in fixedTypes)
|
||||
if (!disallowed.Contains(t)) feasibleTypes.Add(t);
|
||||
|
||||
bool hasGeneric = genericPrefabs.Count > 0;
|
||||
if (hasGeneric && cardPool != null && cardPool.Count > 0)
|
||||
{
|
||||
var poolTypesSet = new HashSet<CardType>();
|
||||
for (int i = 0; i < cardPool.Count; i++) poolTypesSet.Add(cardPool[i].type);
|
||||
foreach (var t in poolTypesSet)
|
||||
{
|
||||
if (!disallowed.Contains(t) && !feasibleTypes.Contains(t))
|
||||
feasibleTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing feasible with disallowed constraint, relax constraint
|
||||
if (feasibleTypes.Count == 0)
|
||||
{
|
||||
foreach (var t in fixedTypes)
|
||||
if (!feasibleTypes.Contains(t)) feasibleTypes.Add(t);
|
||||
if (hasGeneric && cardPool != null && cardPool.Count > 0)
|
||||
{
|
||||
var poolTypesSet = new HashSet<CardType>();
|
||||
for (int i = 0; i < cardPool.Count; i++) poolTypesSet.Add(cardPool[i].type);
|
||||
foreach (var t in poolTypesSet)
|
||||
if (!feasibleTypes.Contains(t)) feasibleTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if still empty (e.g., no pool and only generic prefabs), just spawn any prefab without enforcing type
|
||||
if (feasibleTypes.Count == 0)
|
||||
{
|
||||
GameObject anyPrefab = multiPrefabs ? (cardPrefabs.Count > 0 ? cardPrefabs[Random.Range(0, cardPrefabs.Count)] : null) : cardPrefab;
|
||||
if (anyPrefab == null) return null;
|
||||
return Instantiate(anyPrefab, pos, rot);
|
||||
}
|
||||
|
||||
// Choose a target type randomly
|
||||
var targetType = feasibleTypes[Random.Range(0, feasibleTypes.Count)];
|
||||
|
||||
// Choose a matching prefab if possible; else a generic prefab
|
||||
GameObject chosenPrefab = null;
|
||||
var matches = new List<GameObject>();
|
||||
for (int i = 0; i < fixedPrefabs.Count; i++)
|
||||
if (fixedPrefabs[i].type.Equals(targetType)) matches.Add(fixedPrefabs[i].prefab);
|
||||
if (matches.Count > 0)
|
||||
chosenPrefab = matches[Random.Range(0, matches.Count)];
|
||||
else if (hasGeneric)
|
||||
chosenPrefab = genericPrefabs[Random.Range(0, genericPrefabs.Count)];
|
||||
else
|
||||
chosenPrefab = multiPrefabs ? cardPrefabs[Random.Range(0, cardPrefabs.Count)] : cardPrefab; // last resort
|
||||
|
||||
var go = Instantiate(chosenPrefab, pos, rot);
|
||||
var behaviour = go.GetComponent<CardBehaviour>();
|
||||
if (behaviour != null && behaviour.Data == null)
|
||||
{
|
||||
// Assign data matching chosen type
|
||||
CardData assign = null;
|
||||
if (cardPool != null && cardPool.Count > 0)
|
||||
{
|
||||
// collect indices with targetType
|
||||
var idxs = new List<int>();
|
||||
for (int i = 0; i < cardPool.Count; i++) if (cardPool[i].type.Equals(targetType)) idxs.Add(i);
|
||||
if (idxs.Count > 0) assign = cardPool[idxs[Random.Range(0, idxs.Count)]];
|
||||
else assign = cardPool[Random.Range(0, cardPool.Count)];
|
||||
}
|
||||
if (assign != null) behaviour.SetData(assign);
|
||||
}
|
||||
return go;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SpecialMoveCardSystem/CardSpawner.cs.meta
Normal file
2
Assets/Scripts/SpecialMoveCardSystem/CardSpawner.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef1918f00b4c054a7ab854d3c51e59f1
|
||||
15
Assets/Scripts/SpecialMoveCardSystem/CardType.cs
Normal file
15
Assets/Scripts/SpecialMoveCardSystem/CardType.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// Types of special move cards. Extend by adding new enum values.
|
||||
/// </summary>
|
||||
public enum CardType
|
||||
{
|
||||
Red,
|
||||
Cyan,
|
||||
Purple,
|
||||
Orange
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SpecialMoveCardSystem/CardType.cs.meta
Normal file
2
Assets/Scripts/SpecialMoveCardSystem/CardType.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13d889c165fbfce3b9510cf2efe8fef9
|
||||
31
Assets/Scripts/SpecialMoveCardSystem/DebugPocketPrinter.cs
Normal file
31
Assets/Scripts/SpecialMoveCardSystem/DebugPocketPrinter.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>Optional helper to log pocket contents for debugging.</summary>
|
||||
public class DebugPocketPrinter : MonoBehaviour
|
||||
{
|
||||
public PlayerPocket pocket;
|
||||
public float printEverySeconds = 5f;
|
||||
private float t;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
pocket = GetComponent<PlayerPocket>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
if (t >= printEverySeconds)
|
||||
{
|
||||
t = 0f;
|
||||
if (pocket == null) return;
|
||||
var snap = pocket.GetSnapshot();
|
||||
string a = snap[0] ? snap[0].moveName : "-";
|
||||
string b = snap[1] ? snap[1].moveName : "-";
|
||||
Debug.Log($"[Pocket] Slot1={a}, Slot2={b}, Count={pocket.Count}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73966c7469b50b4069cf5fb286f1e88c
|
||||
22
Assets/Scripts/SpecialMoveCardSystem/GameEvents.cs
Normal file
22
Assets/Scripts/SpecialMoveCardSystem/GameEvents.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// Central event hub for the Special Move Card system.
|
||||
/// Keeps the PlayerPocket decoupled from UI and other systems.
|
||||
/// </summary>
|
||||
public static class GameEvents
|
||||
{
|
||||
/// <summary>Raised when a card is picked up by a player.</summary>
|
||||
public static event Action<CardData> CardPicked;
|
||||
/// <summary>Raised when a card is consumed/used by the player.</summary>
|
||||
public static event Action<CardData, int> CardUsed; // Card, slotIndex
|
||||
/// <summary>Raised whenever the pocket inventory changes.</summary>
|
||||
public static event Action<CardData[], int> PocketChanged; // snapshot, count
|
||||
|
||||
public static void RaiseCardPicked(CardData data) => CardPicked?.Invoke(data);
|
||||
public static void RaiseCardUsed(CardData data, int slotIndex) => CardUsed?.Invoke(data, slotIndex);
|
||||
public static void RaisePocketChanged(CardData[] snapshot, int count) => PocketChanged?.Invoke(snapshot, count);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SpecialMoveCardSystem/GameEvents.cs.meta
Normal file
2
Assets/Scripts/SpecialMoveCardSystem/GameEvents.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f98557cbc68cf9a35a29a0eb31fa7c60
|
||||
240
Assets/Scripts/SpecialMoveCardSystem/PlayerPocket.cs
Normal file
240
Assets/Scripts/SpecialMoveCardSystem/PlayerPocket.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using UnityEngine;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
using System;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds up to two cards, exposes UseCard(slot), integrates with Animation via SpecialAnimationBridge.
|
||||
/// </summary>
|
||||
public class PlayerPocket : MonoBehaviour
|
||||
{
|
||||
[Header("Animation")] public SpecialAnimationBridge animationBridge;
|
||||
[Header("UI")] public UIController_Pocket pocketUI;
|
||||
[Tooltip("Tag used to find the Pocket UI at runtime if not assigned in inspector.")]
|
||||
[SerializeField] private string pocketUITag = "Pocket UI";
|
||||
private Coroutine resolveUICoroutine;
|
||||
|
||||
[Header("Input (Optional)")]
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
public InputAction useSlot1Action; // bind to keyboard 1
|
||||
public InputAction useSlot2Action; // bind to keyboard 2
|
||||
[Tooltip("Input System action that triggers using the next available card in the pocket (left-most).")]
|
||||
public InputActionReference specialMoveAction; // expected to be bound to e.g. gamepad X / keyboard E
|
||||
#endif
|
||||
[Tooltip("Allow using cards while holding only 1.")]
|
||||
public bool allowUseWithSingleCard = true;
|
||||
|
||||
private CardData[] slots = new CardData[2];
|
||||
public int Count { get { return (slots[0] != null ? 1 : 0) + (slots[1] != null ? 1 : 0); } }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Skip if card system is disabled globally
|
||||
if (CardSpawner.CARD_SYSTEM_DISABLED)
|
||||
{
|
||||
Debug.Log("[PlayerPocket] Card System is DISABLED - disabling component");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (animationBridge == null)
|
||||
animationBridge = GetComponent<SpecialAnimationBridge>();
|
||||
|
||||
// Try to auto-resolve UI early (player is often spawned from prefab)
|
||||
if (pocketUI == null)
|
||||
TryResolvePocketUIOnce();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
// Create defaults if not wired in Inspector
|
||||
if (useSlot1Action == null)
|
||||
{
|
||||
useSlot1Action = new InputAction(name: "UseCardSlot1", binding: "<Keyboard>/1");
|
||||
}
|
||||
if (useSlot2Action == null)
|
||||
{
|
||||
useSlot2Action = new InputAction(name: "UseCardSlot2", binding: "<Keyboard>/2");
|
||||
}
|
||||
|
||||
if (useSlot1Action != null)
|
||||
{
|
||||
useSlot1Action.SafeEnable();
|
||||
useSlot1Action.performed += OnUseSlot1;
|
||||
}
|
||||
if (useSlot2Action != null)
|
||||
{
|
||||
useSlot2Action.SafeEnable();
|
||||
useSlot2Action.performed += OnUseSlot2;
|
||||
}
|
||||
|
||||
// Hook SpecialMove input if provided
|
||||
if (specialMoveAction != null && specialMoveAction.action != null)
|
||||
{
|
||||
specialMoveAction.action.SafeEnable();
|
||||
specialMoveAction.action.performed += OnSpecialMovePerformed;
|
||||
}
|
||||
#endif
|
||||
PushUI();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (useSlot1Action != null) useSlot1Action.performed -= OnUseSlot1;
|
||||
if (useSlot2Action != null) useSlot2Action.performed -= OnUseSlot2;
|
||||
if (useSlot1Action != null) useSlot1Action.Disable();
|
||||
if (useSlot2Action != null) useSlot2Action.Disable();
|
||||
|
||||
if (specialMoveAction != null && specialMoveAction.action != null)
|
||||
{
|
||||
specialMoveAction.action.performed -= OnSpecialMovePerformed;
|
||||
specialMoveAction.action.Disable();
|
||||
}
|
||||
#endif
|
||||
if (resolveUICoroutine != null)
|
||||
{
|
||||
StopCoroutine(resolveUICoroutine);
|
||||
resolveUICoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Legacy fallback
|
||||
if (Input.GetKeyDown(KeyCode.Alpha1)) UseCard(0);
|
||||
if (Input.GetKeyDown(KeyCode.Alpha2)) UseCard(1);
|
||||
}
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
private void OnUseSlot1(InputAction.CallbackContext ctx) { if (ctx.performed) UseCard(0); }
|
||||
private void OnUseSlot2(InputAction.CallbackContext ctx) { if (ctx.performed) UseCard(1); }
|
||||
private void OnSpecialMovePerformed(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (!ctx.performed) return;
|
||||
// Always consume the left-most available card (sequential use)
|
||||
// UseCard will compact and raise events/UI.
|
||||
if (slots[0] != null) { UseCard(0); return; }
|
||||
if (slots[1] != null) { UseCard(1); return; }
|
||||
// no card -> ignore
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Try adding a card to the first empty slot. Returns true if added.
|
||||
/// </summary>
|
||||
public bool TryAddCard(CardData data)
|
||||
{
|
||||
if (data == null) return false;
|
||||
if (slots[0] == null) { slots[0] = data; OnChanged(); GameEvents.RaiseCardPicked(data); return true; }
|
||||
if (slots[1] == null) { slots[1] = data; OnChanged(); GameEvents.RaiseCardPicked(data); return true; }
|
||||
return false; // full
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a card at slot index (0 or 1). Consumes immediately and plays mapped animation.
|
||||
/// </summary>
|
||||
public void UseCard(int slotIndex)
|
||||
{
|
||||
if (slotIndex < 0 || slotIndex > 1) return;
|
||||
var data = slots[slotIndex];
|
||||
if (data == null)
|
||||
{
|
||||
// If only one card and user pressed empty slot, optionally redirect to occupied slot
|
||||
if (allowUseWithSingleCard && Count == 1)
|
||||
{
|
||||
int other = slotIndex == 0 ? 1 : 0;
|
||||
if (slots[other] != null) { UseCard(other); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume first
|
||||
slots[slotIndex] = null;
|
||||
CompactLeft();
|
||||
OnChanged();
|
||||
GameEvents.RaiseCardUsed(data, slotIndex);
|
||||
|
||||
// Play animation
|
||||
if (animationBridge)
|
||||
{
|
||||
animationBridge.PlaySpecialMove(data.animationTriggerName);
|
||||
}
|
||||
|
||||
// Optional particles could be spawned by listening to GameEvents.CardUsed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of slots (length 2). Do not mutate the returned array.
|
||||
/// </summary>
|
||||
public CardData[] GetSnapshot()
|
||||
{
|
||||
var snap = new CardData[2];
|
||||
snap[0] = slots[0];
|
||||
snap[1] = slots[1];
|
||||
return snap;
|
||||
}
|
||||
|
||||
private void CompactLeft()
|
||||
{
|
||||
// Keep items packed to the left: [A,null] => [A,null], [null,B] => [B,null]
|
||||
if (slots[0] == null && slots[1] != null)
|
||||
{
|
||||
slots[0] = slots[1];
|
||||
slots[1] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged()
|
||||
{
|
||||
PushUI();
|
||||
GameEvents.RaisePocketChanged(GetSnapshot(), Count);
|
||||
}
|
||||
|
||||
private void PushUI()
|
||||
{
|
||||
if (pocketUI == null)
|
||||
{
|
||||
// Attempt to resolve again at first use; if still missing, start a short retry loop
|
||||
TryResolvePocketUIOnce();
|
||||
if (pocketUI == null && resolveUICoroutine == null)
|
||||
resolveUICoroutine = StartCoroutine(ResolvePocketUIRoutine());
|
||||
}
|
||||
|
||||
if (pocketUI)
|
||||
pocketUI.UpdateUI(GetSnapshot());
|
||||
}
|
||||
|
||||
private void TryResolvePocketUIOnce()
|
||||
{
|
||||
if (string.IsNullOrEmpty(pocketUITag)) return;
|
||||
var go = GameObject.FindWithTag(pocketUITag);
|
||||
if (go == null) return;
|
||||
var ui = go.GetComponent<UIController_Pocket>();
|
||||
if (ui != null) pocketUI = ui;
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ResolvePocketUIRoutine()
|
||||
{
|
||||
// Try for a short window (e.g., next 1 second) in case UI spawns after the player
|
||||
float timeout = 1.0f;
|
||||
float t = 0f;
|
||||
while (pocketUI == null && t < timeout)
|
||||
{
|
||||
TryResolvePocketUIOnce();
|
||||
if (pocketUI != null) break;
|
||||
t += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
resolveUICoroutine = null;
|
||||
if (pocketUI)
|
||||
pocketUI.UpdateUI(GetSnapshot());
|
||||
else
|
||||
Debug.LogWarning("PlayerPocket: Could not find Pocket UI by tag '" + pocketUITag + "' within 1s. Make sure the UI exists and is tagged.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4feb0601c38c874dca5ff0e390a7b850
|
||||
@@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// Decouples PlayerPocket from AnimationManager or direct Animator usage.
|
||||
/// Prefers AnimationManager if present, else falls back to Animator trigger/state.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Animator))]
|
||||
public class SpecialAnimationBridge : MonoBehaviour
|
||||
{
|
||||
private AnimationManager animManager; // existing class in project
|
||||
private Animator animator;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
animManager = GetComponent<AnimationManager>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play a special move by animation trigger/state name.
|
||||
/// </summary>
|
||||
public void PlaySpecialMove(string triggerOrState)
|
||||
{
|
||||
if (string.IsNullOrEmpty(triggerOrState))
|
||||
{
|
||||
Debug.LogWarning("PlaySpecialMove called with empty name");
|
||||
return;
|
||||
}
|
||||
|
||||
if (animManager != null)
|
||||
{
|
||||
// AnimationManager will CrossFade by clip/state name
|
||||
animManager.PlayAnimation(triggerOrState);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: try to SetTrigger if such a parameter exists. Else CrossFade.
|
||||
if (animator == null) return;
|
||||
|
||||
int paramCount = animator.parameterCount;
|
||||
bool hasTrigger = false;
|
||||
for (int i = 0; i < paramCount; i++)
|
||||
{
|
||||
var p = animator.GetParameter(i);
|
||||
if (p.type == AnimatorControllerParameterType.Trigger && p.name == triggerOrState)
|
||||
{
|
||||
hasTrigger = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTrigger)
|
||||
animator.SetTrigger(triggerOrState);
|
||||
else
|
||||
animator.CrossFade(triggerOrState, 0.1f, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 253f9001740e2220ab5d3c34934b6cd1
|
||||
84
Assets/Scripts/SpecialMoveCardSystem/UIController_Pocket.cs
Normal file
84
Assets/Scripts/SpecialMoveCardSystem/UIController_Pocket.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SpecialMoveCards
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple UI binder for two pocket slots using two Image components.
|
||||
/// </summary>
|
||||
public class UIController_Pocket : MonoBehaviour
|
||||
{
|
||||
[Header("Images")]
|
||||
public Image slot1Image;
|
||||
public Image slot2Image;
|
||||
// We'll draw card sprites on separate child Images so the assigned slot Images
|
||||
// can remain as the background visuals.
|
||||
private Image slot1Content;
|
||||
private Image slot2Content;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureContentImage(ref slot1Content, slot1Image, "CardContent1");
|
||||
EnsureContentImage(ref slot2Content, slot2Image, "CardContent2");
|
||||
}
|
||||
|
||||
private static void EnsureContentImage(ref Image content, Image baseImage, string name)
|
||||
{
|
||||
if (content != null || baseImage == null) return;
|
||||
var go = new GameObject(name, typeof(RectTransform));
|
||||
var rt = go.GetComponent<RectTransform>();
|
||||
rt.SetParent(baseImage.transform, worldPositionStays: false);
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero;
|
||||
rt.offsetMax = Vector2.zero;
|
||||
content = go.AddComponent<Image>();
|
||||
content.raycastTarget = false; // let background handle clicks if needed
|
||||
content.preserveAspect = true;
|
||||
content.enabled = false; // hidden until a card is present
|
||||
// Ensure content renders on top of the background
|
||||
go.transform.SetAsLastSibling();
|
||||
}
|
||||
|
||||
public void UpdateUI(CardData[] cards)
|
||||
{
|
||||
// Make sure content images exist (in case references were added at runtime)
|
||||
if (slot1Content == null && slot1Image != null) EnsureContentImage(ref slot1Content, slot1Image, "CardContent1");
|
||||
if (slot2Content == null && slot2Image != null) EnsureContentImage(ref slot2Content, slot2Image, "CardContent2");
|
||||
|
||||
// Slot 1 content (background image stays untouched)
|
||||
if (slot1Content)
|
||||
{
|
||||
if (cards != null && cards.Length > 0 && cards[0] != null)
|
||||
{
|
||||
slot1Content.enabled = true;
|
||||
slot1Content.sprite = cards[0].icon;
|
||||
slot1Content.color = cards[0].color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No card: hide only the content sprite; keep background visible
|
||||
slot1Content.sprite = null;
|
||||
slot1Content.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Slot 2 content (background image stays untouched)
|
||||
if (slot2Content)
|
||||
{
|
||||
if (cards != null && cards.Length > 1 && cards[1] != null)
|
||||
{
|
||||
slot2Content.enabled = true;
|
||||
slot2Content.sprite = cards[1].icon;
|
||||
slot2Content.color = cards[1].color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No card: hide only the content sprite; keep background visible
|
||||
slot2Content.sprite = null;
|
||||
slot2Content.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b74a024e67bf3756b0da0a02fac5422
|
||||
Reference in New Issue
Block a user