implement changes to game

This commit is contained in:
2026-05-26 11:48:51 +02:00
parent 8162e2b331
commit 8b88a9ca97
156 changed files with 7479 additions and 3905 deletions
+11 -1
View File
@@ -15,6 +15,7 @@ public class Bullet : MonoBehaviour
{
target = enemy.transform;
lastKnownDirection = (target.position - transform.position).normalized; // Store initial direction
UpdateRotation();
}
}
@@ -35,12 +36,21 @@ public class Bullet : MonoBehaviour
}
// Move bullet in the last known direction
UpdateRotation();
transform.position += (Vector3)lastKnownDirection * speed * Time.deltaTime;
// Destroy bullet after 5 seconds to prevent infinite movement
Destroy(gameObject, 5f);
}
void UpdateRotation()
{
if (lastKnownDirection.sqrMagnitude <= Mathf.Epsilon)
return;
transform.right = lastKnownDirection;
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Enemy"))
@@ -58,4 +68,4 @@ public class Bullet : MonoBehaviour
Instantiate(shatterEffectPrefab, transform.position, Quaternion.identity);
}
}
}
}
+96 -3
View File
@@ -22,6 +22,13 @@ public class EnemySpawner : MonoBehaviour
private float maxEnemySpeed = 6f;
private int maxEnemyHP = 20;
void Start()
{
EnsureWaveText();
StyleStartWaveButton();
UpdateWaveText();
}
public void StartWave()
{
if (!isSpawning)
@@ -83,7 +90,7 @@ public class EnemySpawner : MonoBehaviour
{
if (waveText != null)
{
waveText.text = "Wave: " + currentWave;
waveText.text = $"WAVE {currentWave}";
}
}
@@ -97,7 +104,7 @@ public class EnemySpawner : MonoBehaviour
// If wave is multiple of 5, show special upgrade choice
if (currentWave % 5 == 0)
{
UpgradeManager upgradeManager = FindObjectOfType<UpgradeManager>();
UpgradeManager upgradeManager = FindAnyObjectByType<UpgradeManager>();
if (upgradeManager != null)
{
upgradeManager.ShowSpecialUpgradeChoice();
@@ -105,4 +112,90 @@ public class EnemySpawner : MonoBehaviour
}
}
}
}
void EnsureWaveText()
{
if (waveText != null)
return;
Canvas canvas = FindAnyObjectByType<Canvas>();
if (canvas == null)
return;
Transform existing = canvas.transform.Find("WaveCounter");
if (existing != null)
{
waveText = existing.GetComponentInChildren<TextMeshProUGUI>();
if (waveText != null)
return;
}
GameObject panelObject = new GameObject("WaveCounter", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
RectTransform panel = panelObject.GetComponent<RectTransform>();
panel.SetParent(canvas.transform, false);
panel.anchorMin = new Vector2(0.5f, 1f);
panel.anchorMax = new Vector2(0.5f, 1f);
panel.pivot = new Vector2(0.5f, 1f);
panel.anchoredPosition = new Vector2(0f, -24f);
panel.sizeDelta = new Vector2(150f, 42f);
Image panelImage = panelObject.GetComponent<Image>();
panelImage.color = new Color(0.05f, 0.06f, 0.08f, 0.84f);
GameObject textObject = new GameObject("Text", typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
RectTransform textRect = textObject.GetComponent<RectTransform>();
textRect.SetParent(panel, false);
textRect.anchorMin = Vector2.zero;
textRect.anchorMax = Vector2.one;
textRect.offsetMin = Vector2.zero;
textRect.offsetMax = Vector2.zero;
waveText = textObject.GetComponent<TextMeshProUGUI>();
waveText.alignment = TextAlignmentOptions.Center;
waveText.fontSize = 21f;
waveText.fontStyle = FontStyles.Bold;
waveText.color = new Color(0.92f, 0.95f, 1f, 1f);
waveText.raycastTarget = false;
}
void StyleStartWaveButton()
{
if (startWaveButton == null)
return;
RectTransform rect = startWaveButton.GetComponent<RectTransform>();
if (rect != null)
{
rect.anchorMin = new Vector2(0.5f, 1f);
rect.anchorMax = new Vector2(0.5f, 1f);
rect.pivot = new Vector2(0.5f, 1f);
rect.anchoredPosition = new Vector2(0f, -76f);
rect.sizeDelta = new Vector2(180f, 44f);
}
Image image = startWaveButton.GetComponent<Image>();
if (image != null)
{
image.color = new Color(0.2f, 0.66f, 0.9f, 1f);
}
ColorBlock colors = startWaveButton.colors;
colors.normalColor = new Color(0.2f, 0.66f, 0.9f, 1f);
colors.highlightedColor = new Color(0.33f, 0.78f, 1f, 1f);
colors.pressedColor = new Color(0.12f, 0.48f, 0.7f, 1f);
colors.selectedColor = colors.highlightedColor;
colors.disabledColor = new Color(0.18f, 0.22f, 0.27f, 0.7f);
colors.colorMultiplier = 1f;
startWaveButton.colors = colors;
TextMeshProUGUI label = startWaveButton.GetComponentInChildren<TextMeshProUGUI>();
if (label != null)
{
label.text = "START WAVE";
label.fontSize = 20f;
label.fontStyle = FontStyles.Bold;
label.color = Color.white;
label.raycastTarget = false;
}
}
}
+150 -8
View File
@@ -1,19 +1,33 @@
using System.Collections; // Required for IEnumerator
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using System.Collections;
public class TowerHealth : MonoBehaviour
{
public int maxHealth = 100;
public GameObject floatingTextPrefab;
[Header("HUD")]
public Image healthFill;
public TextMeshProUGUI healthText;
public Color highHealthColor = new Color(0.18f, 0.88f, 0.42f);
public Color lowHealthColor = new Color(0.95f, 0.22f, 0.18f);
private int currentHealth;
private SpriteRenderer spriteRenderer;
public GameObject floatingTextPrefab;
void Start()
{
currentHealth = maxHealth;
spriteRenderer = GetComponent<SpriteRenderer>();
UpdateColor();
if (spriteRenderer != null)
{
spriteRenderer.color = Color.white;
}
EnsureHealthBar();
UpdateHealthBar();
}
public void TakeDamage(int damage)
@@ -21,7 +35,7 @@ public class TowerHealth : MonoBehaviour
currentHealth -= damage;
if (currentHealth < 0) currentHealth = 0;
ShowFloatingDamage(damage);
UpdateColor();
UpdateHealthBar();
if (currentHealth <= 0)
{
@@ -29,10 +43,138 @@ public class TowerHealth : MonoBehaviour
}
}
void UpdateColor()
void UpdateHealthBar()
{
float healthPercentage = (float)currentHealth / maxHealth;
spriteRenderer.color = Color.Lerp(Color.red, Color.green, healthPercentage);
float healthPercentage = maxHealth <= 0 ? 0f : (float)currentHealth / maxHealth;
if (healthFill != null)
{
healthFill.fillAmount = healthPercentage;
healthFill.color = Color.Lerp(lowHealthColor, highHealthColor, healthPercentage);
}
if (healthText != null)
{
healthText.text = $"{currentHealth}/{maxHealth}";
}
}
void EnsureHealthBar()
{
if (healthFill != null && healthText != null)
return;
Canvas canvas = FindAnyObjectByType<Canvas>();
if (canvas == null)
{
GameObject canvasObject = new GameObject("GameUI", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
canvas = canvasObject.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
CanvasScaler scaler = canvasObject.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(800f, 600f);
scaler.matchWidthOrHeight = 0.5f;
}
Transform existing = canvas.transform.Find("TowerHealthBar");
RectTransform container = existing != null
? existing.GetComponent<RectTransform>()
: CreateRect("TowerHealthBar", canvas.transform);
container.anchorMin = new Vector2(1f, 1f);
container.anchorMax = new Vector2(1f, 1f);
container.pivot = new Vector2(1f, 1f);
container.anchoredPosition = new Vector2(-24f, -24f);
container.sizeDelta = new Vector2(240f, 54f);
Image panel = container.GetComponent<Image>();
if (panel == null)
panel = container.gameObject.AddComponent<Image>();
panel.color = new Color(0.05f, 0.06f, 0.08f, 0.84f);
TextMeshProUGUI label = container.Find("Label")?.GetComponent<TextMeshProUGUI>();
if (label == null)
{
label = CreateText("Label", container, "TOWER", 14f);
RectTransform labelRect = label.rectTransform;
labelRect.anchorMin = new Vector2(0f, 1f);
labelRect.anchorMax = new Vector2(0f, 1f);
labelRect.pivot = new Vector2(0f, 1f);
labelRect.anchoredPosition = new Vector2(12f, -7f);
labelRect.sizeDelta = new Vector2(90f, 18f);
}
if (healthText == null)
{
healthText = container.Find("Value")?.GetComponent<TextMeshProUGUI>();
if (healthText == null)
{
healthText = CreateText("Value", container, "", 14f);
healthText.alignment = TextAlignmentOptions.Right;
RectTransform textRect = healthText.rectTransform;
textRect.anchorMin = new Vector2(1f, 1f);
textRect.anchorMax = new Vector2(1f, 1f);
textRect.pivot = new Vector2(1f, 1f);
textRect.anchoredPosition = new Vector2(-12f, -7f);
textRect.sizeDelta = new Vector2(100f, 18f);
}
}
RectTransform track = container.Find("Track")?.GetComponent<RectTransform>();
if (track == null)
{
track = CreateRect("Track", container);
track.anchorMin = new Vector2(0f, 0f);
track.anchorMax = new Vector2(1f, 0f);
track.pivot = new Vector2(0.5f, 0f);
track.anchoredPosition = new Vector2(0f, 10f);
track.sizeDelta = new Vector2(-24f, 16f);
Image trackImage = track.gameObject.AddComponent<Image>();
trackImage.color = new Color(0.16f, 0.17f, 0.2f, 1f);
}
if (healthFill == null)
{
healthFill = track.Find("Fill")?.GetComponent<Image>();
if (healthFill == null)
{
RectTransform fill = CreateRect("Fill", track);
fill.anchorMin = Vector2.zero;
fill.anchorMax = Vector2.one;
fill.offsetMin = Vector2.zero;
fill.offsetMax = Vector2.zero;
healthFill = fill.gameObject.AddComponent<Image>();
healthFill.type = Image.Type.Filled;
healthFill.fillMethod = Image.FillMethod.Horizontal;
healthFill.fillOrigin = (int)Image.OriginHorizontal.Left;
}
}
}
static RectTransform CreateRect(string name, Transform parent)
{
GameObject obj = new GameObject(name, typeof(RectTransform));
RectTransform rect = obj.GetComponent<RectTransform>();
rect.SetParent(parent, false);
return rect;
}
static TextMeshProUGUI CreateText(string name, Transform parent, string text, float fontSize)
{
GameObject obj = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
RectTransform rect = obj.GetComponent<RectTransform>();
rect.SetParent(parent, false);
TextMeshProUGUI label = obj.GetComponent<TextMeshProUGUI>();
label.text = text;
label.fontSize = fontSize;
label.fontStyle = FontStyles.Bold;
label.color = new Color(0.92f, 0.95f, 1f, 1f);
label.raycastTarget = false;
return label;
}
void ShowFloatingDamage(int damage)
@@ -68,4 +210,4 @@ public class TowerHealth : MonoBehaviour
yield return null;
}
}
}
}
+63 -6
View File
@@ -1,5 +1,6 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class UpgradeCard : MonoBehaviour
{
@@ -22,17 +23,21 @@ public class UpgradeCard : MonoBehaviour
public TextMeshProUGUI upgradeText;
public TextMeshProUGUI upgradeDescription;
public TextMeshProUGUI upgradeCostText;
[SerializeField] private float titleFontSize = 17f;
[SerializeField] private float detailFontSize = 11f;
[SerializeField] private float worldCanvasDynamicPixelsPerUnit = 64f;
void Start()
{
// If not assigned in Inspector, try to find them by name
if (upgradeText == null)
upgradeText = transform.Find("Canvas/UpgradeText").GetComponent<TextMeshProUGUI>();
upgradeText = FindText("Canvas/UpgradeText");
if (upgradeDescription == null)
upgradeDescription = transform.Find("Canvas/UpgradeDescription").GetComponent<TextMeshProUGUI>();
upgradeDescription = FindText("Canvas/UpgradeDescription");
if (upgradeCostText == null)
upgradeCostText = transform.Find("Canvas/UpgradeCost").GetComponent<TextMeshProUGUI>();
upgradeCostText = FindText("Canvas/UpgradeCost");
ImproveTextRendering();
// Update all UI fields
UpdateUpgradeUI();
}
@@ -45,10 +50,12 @@ public class UpgradeCard : MonoBehaviour
public void UpdateUpgradeUI()
{
ImproveTextRendering();
if (upgradeText != null)
{
string roman = ConvertToRoman(upgradeLevel);
upgradeText.text = $"{upgradeType} {roman}";
upgradeText.text = $"{GetDisplayName(upgradeType)} {roman}";
}
// 2) Description is presumably already set in the prefab,
@@ -65,6 +72,56 @@ public class UpgradeCard : MonoBehaviour
string ConvertToRoman(int number)
{
string[] numerals = { "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X" };
return number > 10 ? number.ToString() : numerals[number - 1];
return number < 1 || number > 10 ? number.ToString() : numerals[number - 1];
}
}
static string GetDisplayName(UpgradeType type)
{
switch (type)
{
case UpgradeType.FireRate: return "FIRE RATE";
case UpgradeType.Range: return "RANGE";
case UpgradeType.Damage: return "DAMAGE";
case UpgradeType.MultiShot: return "MULTI SHOT";
case UpgradeType.Homing: return "HOMING";
case UpgradeType.Ricochet: return "RICOCHET";
default: return type.ToString().ToUpperInvariant();
}
}
TextMeshProUGUI FindText(string path)
{
Transform textTransform = transform.Find(path);
return textTransform != null ? textTransform.GetComponent<TextMeshProUGUI>() : null;
}
void ImproveTextRendering()
{
Canvas canvas = GetComponentInChildren<Canvas>(true);
if (canvas != null)
{
canvas.pixelPerfect = true;
}
CanvasScaler canvasScaler = GetComponentInChildren<CanvasScaler>(true);
if (canvasScaler != null)
{
canvasScaler.dynamicPixelsPerUnit = worldCanvasDynamicPixelsPerUnit;
}
ConfigureText(upgradeText, titleFontSize);
ConfigureText(upgradeDescription, detailFontSize);
ConfigureText(upgradeCostText, detailFontSize);
}
static void ConfigureText(TextMeshProUGUI text, float fontSize)
{
if (text == null)
return;
text.fontSize = fontSize;
text.enableAutoSizing = false;
text.extraPadding = true;
text.isTextObjectScaleStatic = false;
}
}
+327 -104
View File
@@ -1,43 +1,62 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class UpgradeManager : MonoBehaviour
{
public GameObject[] normalUpgradePrefabs; // FireRate, Range, Damage
public GameObject[] specialUpgradePrefabs; // MultiShot, Homing, Ricochet, etc.
public GameObject[] normalUpgradePrefabs;
public GameObject[] specialUpgradePrefabs;
[HideInInspector] public List<UpgradeCard.UpgradeType> unlockedUpgrades = new List<UpgradeCard.UpgradeType>();
// Stores which special upgrades have been permanently unlocked.
// UI placeholder to spawn the 3 choices
public Transform specialUpgradeSpawnParent; // A center-of-screen parent or coordinates
public Transform specialUpgradeSpawnParent;
public float specialUpgradeSpacing = 2f;
public Tower tower;
private Dictionary<UpgradeCard.UpgradeType, int> upgradeLevels = new Dictionary<UpgradeCard.UpgradeType, int>();
private Dictionary<UpgradeCard.UpgradeType, int> upgradeCosts = new Dictionary<UpgradeCard.UpgradeType, int>();
public int bits = 5;
public TextMeshProUGUI bitsText;
private readonly Dictionary<UpgradeCard.UpgradeType, int> upgradeLevels = new Dictionary<UpgradeCard.UpgradeType, int>();
private readonly Dictionary<UpgradeCard.UpgradeType, int> upgradeCosts = new Dictionary<UpgradeCard.UpgradeType, int>();
private readonly Dictionary<UpgradeCard.UpgradeType, UpgradeCard> activeCards = new Dictionary<UpgradeCard.UpgradeType, UpgradeCard>();
private RectTransform upgradeDock;
private RectTransform upgradeCardRow;
private GameObject specialOverlay;
private static readonly UpgradeCard.UpgradeType[] NormalUpgradeTypes =
{
UpgradeCard.UpgradeType.FireRate,
UpgradeCard.UpgradeType.Range,
UpgradeCard.UpgradeType.Damage
};
private static readonly UpgradeCard.UpgradeType[] SpecialUpgradeTypes =
{
UpgradeCard.UpgradeType.MultiShot,
UpgradeCard.UpgradeType.Homing,
UpgradeCard.UpgradeType.Ricochet
};
void Start()
{
EnsureUpgradeDock();
InitializeUpgrades();
UpdateBitsText();
Debug.Log($"specialUpgradePrefabs length: {specialUpgradePrefabs.Length}");
}
public void EnemyKilled()
{
bits++;
UpdateBitsText();
UpdateUpgradeCards();
}
void InitializeUpgrades()
{
float spacing = 2.5f;
float startX = -((normalUpgradePrefabs.Length - 1) * spacing / 2f);
foreach (UpgradeCard.UpgradeType type in System.Enum.GetValues(typeof(UpgradeCard.UpgradeType)))
foreach (UpgradeCard.UpgradeType type in Enum.GetValues(typeof(UpgradeCard.UpgradeType)))
{
if (!upgradeLevels.ContainsKey(type))
upgradeLevels[type] = 0;
@@ -45,56 +64,33 @@ public class UpgradeManager : MonoBehaviour
upgradeCosts[type] = GetBaseCost(type);
}
for (int i = 0; i < normalUpgradePrefabs.Length; i++)
foreach (UpgradeCard.UpgradeType type in NormalUpgradeTypes)
{
UpgradeCard.UpgradeType upgradeType = (UpgradeCard.UpgradeType)i;
SpawnUpgradeCard(upgradeType, startX + (i * spacing));
SpawnUpgradeCard(type, 0f);
}
}
void SpawnUpgradeCard(UpgradeCard.UpgradeType upgradeType, float xPos)
{
Vector3 spawnPosition = new Vector3(xPos, -4f, 0);
if (activeCards.ContainsKey(upgradeType))
return;
// Ensure dictionaries have entries for this type
if (!upgradeLevels.ContainsKey(upgradeType))
upgradeLevels[upgradeType] = 0;
if (!upgradeCosts.ContainsKey(upgradeType))
upgradeCosts[upgradeType] = GetBaseCost(upgradeType);
EnsureUpgradeDock();
// Determine this upgrade's current level
int currentLevel = upgradeLevels[upgradeType] + 1;
GameObject cardObject = CreateUpgradeCardObject(upgradeType, upgradeCardRow, false);
UpgradeCard upgradeCard = cardObject.GetComponent<UpgradeCard>();
upgradeCard.upgradeType = upgradeType;
upgradeCard.upgradeLevel = currentLevel;
upgradeCard.upgradeCost = upgradeCosts[upgradeType];
upgradeCard.upgradeManager = this;
upgradeCard.UpdateUpgradeUI();
// 1) Choose which prefab array to use
GameObject prefab;
if ((int)upgradeType < normalUpgradePrefabs.Length)
{
// It's one of FireRate=0, Range=1, Damage=2
prefab = normalUpgradePrefabs[(int)upgradeType];
}
else
{
// It's a special upgrade, e.g. MultiShot, Homing, Ricochet
int specialIndex = (int)upgradeType - normalUpgradePrefabs.Length;
// e.g. MultiShot=3 => specialIndex=0
// Homing=4 => specialIndex=1, etc.
prefab = specialUpgradePrefabs[specialIndex];
}
Button button = cardObject.GetComponent<Button>();
button.onClick.AddListener(() => TryUpgrade(upgradeType));
// 2) Instantiate the correct prefab
GameObject upgradeCardObj = Instantiate(prefab, spawnPosition, Quaternion.identity);
// 3) Set up the UpgradeCard component
UpgradeCard upgradeCard = upgradeCardObj.GetComponentInChildren<UpgradeCard>();
if (upgradeCard != null)
{
upgradeCard.upgradeType = upgradeType;
upgradeCard.upgradeLevel = currentLevel;
upgradeCard.upgradeCost = upgradeCosts[upgradeType];
upgradeCard.upgradeManager = this;
upgradeCard.upgradeText = upgradeCardObj.GetComponentInChildren<TextMeshProUGUI>(true);
upgradeCard.UpdateUpgradeUI();
}
activeCards[upgradeType] = upgradeCard;
UpdateCardAffordability(upgradeCard);
}
public int GetBaseCost(UpgradeCard.UpgradeType type)
@@ -110,14 +106,14 @@ public class UpgradeManager : MonoBehaviour
public void TryUpgrade(UpgradeCard.UpgradeType type)
{
if (bits >= upgradeCosts[type])
{
bits -= upgradeCosts[type];
upgradeLevels[type]++;
upgradeCosts[type] += (int)(upgradeCosts[type] * 0.5f);
ApplyUpgrade(type);
UpdateBitsText();
}
if (!upgradeCosts.ContainsKey(type) || bits < upgradeCosts[type])
return;
bits -= upgradeCosts[type];
upgradeLevels[type]++;
upgradeCosts[type] += Mathf.Max(1, Mathf.RoundToInt(upgradeCosts[type] * 0.5f));
ApplyUpgrade(type);
UpdateBitsText();
}
void ApplyUpgrade(UpgradeCard.UpgradeType type)
@@ -149,92 +145,319 @@ public class UpgradeManager : MonoBehaviour
void UpdateUpgradeCards()
{
UpgradeCard[] allCards = FindObjectsOfType<UpgradeCard>();
foreach (UpgradeCard card in allCards)
foreach (UpgradeCard card in activeCards.Values)
{
card.upgradeLevel = upgradeLevels[card.upgradeType] + 1;
card.upgradeCost = upgradeCosts[card.upgradeType];
card.UpdateUpgradeUI();
UpdateCardAffordability(card);
}
}
void UpdateBitsText()
{
if (bitsText != null)
if (bitsText == null)
return;
StyleBitsText();
bitsText.text = "BITS " + bits;
}
void StyleBitsText()
{
RectTransform rect = bitsText.GetComponent<RectTransform>();
if (rect != null)
{
bitsText.text = "Bits: " + bits;
rect.anchorMin = new Vector2(0f, 1f);
rect.anchorMax = new Vector2(0f, 1f);
rect.pivot = new Vector2(0f, 1f);
rect.anchoredPosition = new Vector2(24f, -24f);
rect.sizeDelta = new Vector2(150f, 42f);
}
bitsText.alignment = TextAlignmentOptions.Center;
bitsText.fontSize = 21f;
bitsText.fontStyle = FontStyles.Bold;
bitsText.color = new Color(0.98f, 0.87f, 0.32f, 1f);
bitsText.raycastTarget = false;
Outline outline = bitsText.GetComponent<Outline>();
if (outline == null)
outline = bitsText.gameObject.AddComponent<Outline>();
outline.effectColor = new Color(0f, 0f, 0f, 0.55f);
outline.effectDistance = new Vector2(1.5f, -1.5f);
}
public void ShowSpecialUpgradeChoice()
{
Debug.Log(specialUpgradeSpawnParent.name);
Debug.Log("Showing Upgrades...");
List<GameObject> available = new List<GameObject>(specialUpgradePrefabs);
EnsureUpgradeDock();
List<UpgradeCard.UpgradeType> available = new List<UpgradeCard.UpgradeType>();
foreach (UpgradeCard.UpgradeType type in SpecialUpgradeTypes)
{
if (!unlockedUpgrades.Contains(type))
available.Add(type);
}
if (available.Count == 0)
return;
// Shuffle the list to randomize order (Fisher-Yates)
for (int i = 0; i < available.Count; i++)
{
int rand = Random.Range(i, available.Count);
var temp = available[i];
int rand = UnityEngine.Random.Range(i, available.Count);
UpgradeCard.UpgradeType temp = available[i];
available[i] = available[rand];
available[rand] = temp;
}
// Determine how many upgrades we can actually show
Time.timeScale = 0f;
specialOverlay = CreateOverlay();
RectTransform row = CreateRect("SpecialUpgradeRow", specialOverlay.transform);
row.anchorMin = new Vector2(0.5f, 0.5f);
row.anchorMax = new Vector2(0.5f, 0.5f);
row.pivot = new Vector2(0.5f, 0.5f);
row.sizeDelta = new Vector2(590f, 190f);
HorizontalLayoutGroup layout = row.gameObject.AddComponent<HorizontalLayoutGroup>();
layout.spacing = 16f;
layout.childAlignment = TextAnchor.MiddleCenter;
layout.childControlWidth = true;
layout.childControlHeight = true;
layout.childForceExpandWidth = false;
layout.childForceExpandHeight = false;
int numToShow = Mathf.Min(3, available.Count);
// If there are no upgrades left, resume game and return
if (numToShow == 0)
{
Debug.Log("No special upgrades available!");
Time.timeScale = 1f;
return;
}
// Instantiate the first 'numToShow' from the shuffled list
float startX = -specialUpgradeSpacing;
for (int i = 0; i < numToShow; i++)
{
float xPos = startX + i * specialUpgradeSpacing;
Vector3 spawnPos = specialUpgradeSpawnParent.position + new Vector3(xPos, 0, 0);
UpgradeCard.UpgradeType type = available[i];
GameObject cardObject = CreateUpgradeCardObject(type, row, true);
UpgradeCard card = cardObject.GetComponent<UpgradeCard>();
card.upgradeType = type;
card.upgradeLevel = 1;
card.upgradeCost = 0;
card.upgradeManager = this;
card.UpdateUpgradeUI();
card.upgradeCostText.text = "UNLOCK";
GameObject specialCardObj =
Instantiate(available[i], spawnPos, Quaternion.identity, specialUpgradeSpawnParent);
Debug.Log("Spawned special card: " + specialCardObj, specialCardObj);
// Add your click-to-choose script
SpecialChoiceCard choiceCard = specialCardObj.AddComponent<SpecialChoiceCard>();
choiceCard.upgradeManager = this;
Button button = cardObject.GetComponent<Button>();
button.onClick.AddListener(() => SelectSpecialUpgrade(type));
}
}
public void UnlockSpecialUpgrade(UpgradeCard.UpgradeType type)
{
Debug.Log("Card chosen! Applying upgrade...");
// 1) Add this special upgrade type to the dictionary if not present
if (!upgradeLevels.ContainsKey(type))
{
upgradeLevels[type] = 0;
upgradeCosts[type] = GetBaseCost(type);
}
// 2) Add to the “unlockedUpgrades” list if you want to track them
if (!unlockedUpgrades.Contains(type))
unlockedUpgrades.Add(type);
// 3) Instantiate it at the bottom row with the other upgrades
float newXPos = CalculateNextUpgradeXPos(); // A method to place new card to the right
SpawnUpgradeCard(type, newXPos);
SpawnUpgradeCard(type, 0f);
}
// Example method to find a new X position for the newly unlocked card
float CalculateNextUpgradeXPos()
void SelectSpecialUpgrade(UpgradeCard.UpgradeType type)
{
// If you originally placed normal upgrades from -X to +X,
// find how many total upgrades are unlocked, and space them out.
// For simplicity:
int totalUnlocked = FindObjectsOfType<UpgradeCard>().Length;
float spacing = 2.5f;
return -((totalUnlocked - 1) * spacing / 2f) + (totalUnlocked - 1) * spacing;
UnlockSpecialUpgrade(type);
if (specialOverlay != null)
Destroy(specialOverlay);
specialOverlay = null;
Time.timeScale = 1f;
}
}
void EnsureUpgradeDock()
{
if (upgradeDock != null)
return;
Canvas canvas = FindAnyObjectByType<Canvas>();
if (canvas == null)
{
GameObject canvasObject = new GameObject("GameUI", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
canvas = canvasObject.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
CanvasScaler scaler = canvasObject.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(800f, 600f);
scaler.matchWidthOrHeight = 0.5f;
}
Transform existingDock = canvas.transform.Find("UpgradeDock");
upgradeDock = existingDock != null ? existingDock.GetComponent<RectTransform>() : CreateRect("UpgradeDock", canvas.transform);
upgradeDock.anchorMin = new Vector2(0f, 0f);
upgradeDock.anchorMax = new Vector2(1f, 0f);
upgradeDock.pivot = new Vector2(0.5f, 0f);
upgradeDock.anchoredPosition = Vector2.zero;
upgradeDock.sizeDelta = new Vector2(0f, 150f);
upgradeDock.SetAsLastSibling();
Image dockImage = upgradeDock.GetComponent<Image>();
if (dockImage == null)
dockImage = upgradeDock.gameObject.AddComponent<Image>();
dockImage.color = new Color(0.03f, 0.035f, 0.045f, 0.94f);
dockImage.raycastTarget = true;
Transform existingRow = upgradeDock.Find("Cards");
upgradeCardRow = existingRow != null ? existingRow.GetComponent<RectTransform>() : CreateRect("Cards", upgradeDock);
upgradeCardRow.anchorMin = new Vector2(0.5f, 0.5f);
upgradeCardRow.anchorMax = new Vector2(0.5f, 0.5f);
upgradeCardRow.pivot = new Vector2(0.5f, 0.5f);
upgradeCardRow.anchoredPosition = new Vector2(0f, 6f);
upgradeCardRow.sizeDelta = new Vector2(720f, 126f);
HorizontalLayoutGroup layout = upgradeCardRow.GetComponent<HorizontalLayoutGroup>();
if (layout == null)
layout = upgradeCardRow.gameObject.AddComponent<HorizontalLayoutGroup>();
layout.spacing = 12f;
layout.childAlignment = TextAnchor.MiddleCenter;
layout.childControlWidth = true;
layout.childControlHeight = true;
layout.childForceExpandWidth = false;
layout.childForceExpandHeight = false;
}
GameObject CreateUpgradeCardObject(UpgradeCard.UpgradeType type, Transform parent, bool specialChoice)
{
GameObject cardObject = new GameObject(type + "Card", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button), typeof(UpgradeCard));
RectTransform cardRect = cardObject.GetComponent<RectTransform>();
cardRect.SetParent(parent, false);
LayoutElement layout = cardObject.AddComponent<LayoutElement>();
layout.preferredWidth = specialChoice ? 180f : 150f;
layout.preferredHeight = specialChoice ? 180f : 116f;
layout.minWidth = layout.preferredWidth;
layout.minHeight = layout.preferredHeight;
Image image = cardObject.GetComponent<Image>();
image.color = specialChoice ? new Color(0.11f, 0.12f, 0.16f, 0.98f) : new Color(0.1f, 0.11f, 0.14f, 0.98f);
Button button = cardObject.GetComponent<Button>();
ColorBlock colors = button.colors;
colors.normalColor = image.color;
colors.highlightedColor = new Color(0.16f, 0.19f, 0.25f, 1f);
colors.pressedColor = new Color(0.06f, 0.28f, 0.36f, 1f);
colors.selectedColor = colors.highlightedColor;
colors.disabledColor = new Color(0.08f, 0.08f, 0.09f, 0.78f);
colors.colorMultiplier = 1f;
button.colors = colors;
UpgradeCard card = cardObject.GetComponent<UpgradeCard>();
card.upgradeText = CreateText("UpgradeText", cardRect, GetDisplayName(type), specialChoice ? 20f : 17f, FontStyles.Bold, TextAlignmentOptions.Center);
card.upgradeDescription = CreateText("UpgradeDescription", cardRect, GetDescription(type), specialChoice ? 13f : 11f, FontStyles.Normal, TextAlignmentOptions.TopLeft);
card.upgradeCostText = CreateText("UpgradeCost", cardRect, "", specialChoice ? 12f : 11f, FontStyles.Bold, TextAlignmentOptions.Center);
PlaceCardText(card.upgradeText.rectTransform, 10f, -8f, -10f, 28f, true);
PlaceCardText(card.upgradeDescription.rectTransform, 12f, -40f, -12f, 44f, true);
PlaceCardText(card.upgradeCostText.rectTransform, 10f, 8f, -10f, 22f, false);
return cardObject;
}
GameObject CreateOverlay()
{
Canvas canvas = upgradeDock.GetComponentInParent<Canvas>();
GameObject overlay = new GameObject("SpecialUpgradeOverlay", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
RectTransform rect = overlay.GetComponent<RectTransform>();
rect.SetParent(canvas.transform, false);
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
rect.SetAsLastSibling();
Image image = overlay.GetComponent<Image>();
image.color = new Color(0f, 0f, 0f, 0.74f);
image.raycastTarget = true;
TextMeshProUGUI title = CreateText("Title", rect, "CHOOSE UPGRADE", 28f, FontStyles.Bold, TextAlignmentOptions.Center);
RectTransform titleRect = title.rectTransform;
titleRect.anchorMin = new Vector2(0.5f, 0.5f);
titleRect.anchorMax = new Vector2(0.5f, 0.5f);
titleRect.pivot = new Vector2(0.5f, 0.5f);
titleRect.anchoredPosition = new Vector2(0f, 128f);
titleRect.sizeDelta = new Vector2(420f, 42f);
return overlay;
}
void UpdateCardAffordability(UpgradeCard card)
{
Button button = card.GetComponent<Button>();
if (button != null)
button.interactable = bits >= card.upgradeCost;
if (card.upgradeCostText != null)
{
card.upgradeCostText.color = bits >= card.upgradeCost
? new Color(0.98f, 0.87f, 0.32f, 1f)
: new Color(0.95f, 0.34f, 0.32f, 1f);
}
}
static RectTransform CreateRect(string name, Transform parent)
{
GameObject obj = new GameObject(name, typeof(RectTransform));
RectTransform rect = obj.GetComponent<RectTransform>();
rect.SetParent(parent, false);
return rect;
}
static TextMeshProUGUI CreateText(string name, Transform parent, string text, float fontSize, FontStyles style, TextAlignmentOptions alignment)
{
GameObject obj = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
RectTransform rect = obj.GetComponent<RectTransform>();
rect.SetParent(parent, false);
TextMeshProUGUI label = obj.GetComponent<TextMeshProUGUI>();
label.text = text;
label.fontSize = fontSize;
label.fontStyle = style;
label.alignment = alignment;
label.color = new Color(0.9f, 0.93f, 0.98f, 1f);
label.textWrappingMode = TextWrappingModes.Normal;
label.raycastTarget = false;
return label;
}
static void PlaceCardText(RectTransform rect, float left, float top, float right, float height, bool fromTop)
{
rect.anchorMin = fromTop ? new Vector2(0f, 1f) : new Vector2(0f, 0f);
rect.anchorMax = fromTop ? new Vector2(1f, 1f) : new Vector2(1f, 0f);
rect.pivot = fromTop ? new Vector2(0.5f, 1f) : new Vector2(0.5f, 0f);
rect.offsetMin = new Vector2(left, fromTop ? -height : top);
rect.offsetMax = new Vector2(right, fromTop ? top : top + height);
}
static string GetDisplayName(UpgradeCard.UpgradeType type)
{
switch (type)
{
case UpgradeCard.UpgradeType.FireRate: return "FIRE RATE";
case UpgradeCard.UpgradeType.Range: return "RANGE";
case UpgradeCard.UpgradeType.Damage: return "DAMAGE";
case UpgradeCard.UpgradeType.MultiShot: return "MULTI SHOT";
case UpgradeCard.UpgradeType.Homing: return "HOMING";
case UpgradeCard.UpgradeType.Ricochet: return "RICOCHET";
default: return type.ToString().ToUpperInvariant();
}
}
static string GetDescription(UpgradeCard.UpgradeType type)
{
switch (type)
{
case UpgradeCard.UpgradeType.FireRate: return "Shoot 10% faster.";
case UpgradeCard.UpgradeType.Range: return "Increase targeting range.";
case UpgradeCard.UpgradeType.Damage: return "Add 1 damage per bullet.";
case UpgradeCard.UpgradeType.MultiShot: return "Fire one extra bullet.";
case UpgradeCard.UpgradeType.Homing: return "Bullets track targets.";
case UpgradeCard.UpgradeType.Ricochet: return "Shots can bounce onward.";
default: return "";
}
}
}