implement additional tower

This commit is contained in:
2026-05-27 20:42:44 +02:00
parent 432dd57d07
commit d1a90e498f
17 changed files with 1569 additions and 3922 deletions
+17
View File
@@ -7,6 +7,7 @@ public class Enemy : MonoBehaviour
private EnemyHealth enemyHealth;
private float slowMultiplier = 1f;
private float slowEndTime;
private float stunEndTime;
private Vector2 knockbackVelocity;
private float knockbackEndTime;
@@ -26,6 +27,8 @@ public class Enemy : MonoBehaviour
if (tower == null || enemyHealth == null) return;
if (Time.time >= slowEndTime)
slowMultiplier = 1f;
if (Time.time < stunEndTime)
return;
if (Time.time < knockbackEndTime && knockbackVelocity.sqrMagnitude > 0.01f)
{
@@ -52,6 +55,20 @@ public class Enemy : MonoBehaviour
slowEndTime = Mathf.Max(slowEndTime, Time.time + duration);
}
public void ApplyStun(float duration)
{
stunEndTime = Mathf.Max(stunEndTime, Time.time + duration);
}
public void PullToward(Vector2 point, float distance)
{
Vector2 direction = (point - (Vector2)transform.position).normalized;
if (direction.sqrMagnitude <= Mathf.Epsilon)
return;
transform.position += (Vector3)(direction * distance);
}
public void ApplyKnockback(Vector2 source, float distance)
{
Vector2 direction = ((Vector2)transform.position - source).normalized;
+42 -2
View File
@@ -1,4 +1,5 @@
using TMPro;
using System.Collections;
using UnityEngine;
public class EnemyHealth : MonoBehaviour
@@ -8,14 +9,18 @@ public class EnemyHealth : MonoBehaviour
public EnemySpawner spawner;
public bool isBoss;
private int maxHealth = 1;
private float ionizedDamageMultiplier = 1f;
private float ionizedEndTime;
private Coroutine residualCurrentRoutine;
public bool TakeDamage(int damage)
{
if (health <= 0)
return false;
ShowDamageNumber(damage);
health -= damage;
int finalDamage = Mathf.Max(1, Mathf.RoundToInt(damage * GetDamageTakenMultiplier()));
ShowDamageNumber(finalDamage);
health -= finalDamage;
if (isBoss && spawner != null)
spawner.UpdateBossHealth(this, health, maxHealth);
@@ -52,6 +57,41 @@ public class EnemyHealth : MonoBehaviour
return maxHealth <= 0 ? 0f : (float)health / maxHealth;
}
public float GetDamageTakenMultiplier()
{
if (Time.time >= ionizedEndTime)
ionizedDamageMultiplier = 1f;
return ionizedDamageMultiplier;
}
public void ApplyIonized(float extraDamageMultiplier, float duration)
{
ionizedDamageMultiplier = Mathf.Max(ionizedDamageMultiplier, 1f + extraDamageMultiplier);
ionizedEndTime = Mathf.Max(ionizedEndTime, Time.time + duration);
}
public void ApplyResidualCurrent(int damagePerTick, float duration, float tickInterval)
{
if (residualCurrentRoutine != null)
StopCoroutine(residualCurrentRoutine);
residualCurrentRoutine = StartCoroutine(ResidualCurrent(damagePerTick, duration, tickInterval));
}
IEnumerator ResidualCurrent(int damagePerTick, float duration, float tickInterval)
{
float endTime = Time.time + duration;
while (Time.time < endTime && health > 0)
{
yield return new WaitForSeconds(tickInterval);
if (health > 0)
TakeDamage(damagePerTick);
}
residualCurrentRoutine = null;
}
public void NotifySpawner()
{
if (spawner != null)
+15 -4
View File
@@ -8,6 +8,7 @@ public class EnemySpawner : MonoBehaviour
{
const int WavesPerStage = 15;
const string CurrentStageKey = "CurrentStage";
const string TeslaTowerUnlockedKey = "TeslaTowerUnlocked";
public GameObject enemyPrefab;
public Transform tower;
@@ -223,9 +224,12 @@ public class EnemySpawner : MonoBehaviour
isSpawning = false;
HideBossHealth(activeBoss);
int nextStage = currentStage + 1;
bool unlockedTeslaTower = currentStage == 1 && PlayerPrefs.GetInt(TeslaTowerUnlockedKey, 0) == 0;
PlayerPrefs.SetInt(CurrentStageKey, nextStage);
if (unlockedTeslaTower)
PlayerPrefs.SetInt(TeslaTowerUnlockedKey, 1);
PlayerPrefs.Save();
ShowWinScreen(nextStage);
ShowWinScreen(nextStage, unlockedTeslaTower);
}
public void UpdateBossHealth(EnemyHealth boss, int currentHealth, int maxHealth)
@@ -262,7 +266,7 @@ public class EnemySpawner : MonoBehaviour
bossHealthBar.gameObject.SetActive(false);
}
void ShowWinScreen(int nextStage)
void ShowWinScreen(int nextStage, bool unlockedTeslaTower)
{
Time.timeScale = 0f;
@@ -287,10 +291,17 @@ public class EnemySpawner : MonoBehaviour
PlaceOverlayRect(title.rectTransform, 0f, 76f, 420f, 70f);
TextMeshProUGUI subtitle = CreateOverlayText("Subtitle", rect, $"Stage {currentStage} complete. Stage {nextStage} unlocked.", 20f, FontStyles.Normal);
PlaceOverlayRect(subtitle.rectTransform, 0f, 22f, 520f, 42f);
PlaceOverlayRect(subtitle.rectTransform, 0f, unlockedTeslaTower ? 30f : 22f, 520f, 42f);
if (unlockedTeslaTower)
{
TextMeshProUGUI unlockText = CreateOverlayText("UnlockText", rect, "You unlocked: Tesla Tower", 22f, FontStyles.Bold);
unlockText.color = new Color(0.52f, 0.86f, 1f, 1f);
PlaceOverlayRect(unlockText.rectTransform, 0f, -14f, 520f, 42f);
}
Button menuButton = CreateOverlayButton("MenuButton", rect, "MAIN MENU", ReturnToMenu);
PlaceOverlayRect(menuButton.GetComponent<RectTransform>(), 0f, -58f, 220f, 54f);
PlaceOverlayRect(menuButton.GetComponent<RectTransform>(), 0f, unlockedTeslaTower ? -86f : -58f, 220f, 54f);
}
void ReturnToMenu()
+37 -10
View File
@@ -7,7 +7,9 @@ public class MainMenu : MonoBehaviour
{
const string CurrentStageKey = "CurrentStage";
const string SelectedTowerKey = "SelectedTower";
const string TeslaTowerUnlockedKey = "TeslaTowerUnlocked";
const string CannonTowerId = "cannon";
const string TeslaTowerId = "tesla";
Canvas canvas;
RectTransform contentRoot;
@@ -21,6 +23,10 @@ public class MainMenu : MonoBehaviour
PlayerPrefs.SetInt(CurrentStageKey, 1);
if (!PlayerPrefs.HasKey(SelectedTowerKey))
PlayerPrefs.SetString(SelectedTowerKey, CannonTowerId);
if (PlayerPrefs.GetInt(CurrentStageKey, 1) > 1)
PlayerPrefs.SetInt(TeslaTowerUnlockedKey, 1);
if (!IsTowerUnlocked(PlayerPrefs.GetString(SelectedTowerKey, CannonTowerId)))
PlayerPrefs.SetString(SelectedTowerKey, CannonTowerId);
BuildMenu();
ShowHome();
@@ -28,7 +34,8 @@ public class MainMenu : MonoBehaviour
public void StartGame()
{
PlayerPrefs.SetString(SelectedTowerKey, CannonTowerId);
if (!IsTowerUnlocked(PlayerPrefs.GetString(SelectedTowerKey, CannonTowerId)))
PlayerPrefs.SetString(SelectedTowerKey, CannonTowerId);
PlayerPrefs.Save();
SceneManager.LoadScene("SampleScene");
}
@@ -146,24 +153,44 @@ public class MainMenu : MonoBehaviour
TextMeshProUGUI heading = CreateText("Heading", contentRoot, "TOWERS", 26f, FontStyles.Bold, TextAlignmentOptions.Center);
PlaceTop(heading.rectTransform, -34f, 36f);
RectTransform card = CreatePanel("CannonTowerCard", contentRoot, new Color(0.105f, 0.12f, 0.15f, 1f));
PlaceCenter(card, 0f, -10f, 420f, 198f);
CreateTowerCard("CannonTowerCard", "CANNON TOWER", "Explosive shells with splash and heavy upgrade scaling.", CannonTowerId, true, -216f);
CreateTowerCard("TeslaTowerCard", "TESLA TOWER", "Rapid arcs with longer range and steadier damage.", TeslaTowerId, IsTowerUnlocked(TeslaTowerId), 216f);
}
TextMeshProUGUI name = CreateText("Name", card, "CANNON TOWER", 24f, FontStyles.Bold, TextAlignmentOptions.Center);
void CreateTowerCard(string cardName, string towerName, string descriptionText, string towerId, bool unlocked, float x)
{
string selectedTower = PlayerPrefs.GetString(SelectedTowerKey, CannonTowerId);
bool selected = selectedTower == towerId && unlocked;
Color cardColor = unlocked ? new Color(0.105f, 0.12f, 0.15f, 1f) : new Color(0.078f, 0.084f, 0.096f, 1f);
RectTransform card = CreatePanel(cardName, contentRoot, cardColor);
PlaceCenter(card, x, -10f, 196f, 198f);
TextMeshProUGUI name = CreateText("Name", card, towerName, 20f, FontStyles.Bold, TextAlignmentOptions.Center);
PlaceTop(name.rectTransform, -18f, 34f);
name.color = unlocked ? new Color(0.91f, 0.94f, 0.98f, 1f) : new Color(0.58f, 0.62f, 0.68f, 1f);
TextMeshProUGUI description = CreateText("Description", card, "Single-target bullets with Fire Rate, Range and Damage upgrades.", 16f, FontStyles.Normal, TextAlignmentOptions.Center);
PlaceCenter(description.rectTransform, 0f, 18f, 320f, 54f);
TextMeshProUGUI description = CreateText("Description", card, unlocked ? descriptionText : "Locked until you beat stage 1.", 14f, FontStyles.Normal, TextAlignmentOptions.Center);
PlaceCenter(description.rectTransform, 0f, 18f, 160f, 64f);
description.color = unlocked ? new Color(0.78f, 0.83f, 0.9f, 1f) : new Color(0.5f, 0.54f, 0.6f, 1f);
Button equipButton = CreateButton("EquipButton", card, "EQUIPPED", () =>
Button equipButton = CreateButton("EquipButton", card, selected ? "EQUIPPED" : unlocked ? "EQUIP" : "LOCKED", () =>
{
PlayerPrefs.SetString(SelectedTowerKey, CannonTowerId);
if (!unlocked)
return;
PlayerPrefs.SetString(SelectedTowerKey, towerId);
PlayerPrefs.Save();
ShowTowers();
});
RectTransform equipRect = equipButton.GetComponent<RectTransform>();
PlaceCenter(equipRect, 0f, -54f, 170f, 42f);
equipButton.interactable = false;
PlaceCenter(equipRect, 0f, -54f, 144f, 42f);
equipButton.interactable = unlocked && !selected;
}
static bool IsTowerUnlocked(string towerId)
{
return towerId == CannonTowerId || (towerId == TeslaTowerId && PlayerPrefs.GetInt(TeslaTowerUnlockedKey, 0) == 1);
}
void SetTabState(bool homeActive)
+315 -13
View File
@@ -3,6 +3,9 @@ using UnityEngine;
public class Tower : MonoBehaviour
{
const string SelectedTowerKey = "SelectedTower";
const string TeslaTowerId = "tesla";
public static Tower Instance;
public GameObject bulletPrefab;
public int damage = 1;
@@ -33,13 +36,40 @@ public class Tower : MonoBehaviour
public float volatileSecondaryExplosionMultiplier;
public bool glassCannonDesign;
public bool apocalypseRound;
public bool isTeslaTower;
public float teslaDamageMultiplier = 1f;
public int teslaChainCount = 1;
public float teslaChainFalloff = 0.72f;
public float teslaChainRange = 3.25f;
public float teslaForkChance;
public bool teslaArcBounce;
public int teslaSuperconductorStacks;
public float teslaSlowMultiplier = 1f;
public float teslaSlowDuration;
public int teslaParalysisStacks;
public float teslaIonizedBonus;
public float teslaResidualDamageMultiplier;
public int teslaEmpSurgeStacks;
public int teslaCapacitorBankStacks;
public float teslaOverflowDamage;
public bool teslaEnergyOverflow;
public float teslaFeedbackLoopMultiplier;
public float teslaCriticalExplosionChance;
public int teslaFieldStacks;
public float teslaMagneticPull;
public float teslaVoltageCollapseMultiplier;
public int teslaMeltdownStacks;
public bool teslaInfiniteArc;
public bool teslaStormbringerProtocol;
private float nextFireTime;
private GameObject currentTarget;
private int shotsFired;
private float cooldownUntil;
private bool cannonBaselineApplied;
private bool towerBaselineApplied;
private SpriteRenderer rangeCircle;
private Camera mainCamera;
private float teslaFieldNextTick;
private int teslaCapacitorStacks;
public int extraBullets = 0; // For "Shoot +1 bullet"
public bool homingEnabled = false;
public bool ricochetEnabled = false;
@@ -47,12 +77,12 @@ public class Tower : MonoBehaviour
void Awake()
{
ApplyCannonBaseline();
ApplySelectedTowerBaseline();
}
void Start()
{
ApplyCannonBaseline();
ApplySelectedTowerBaseline();
EnsureRangeCircle();
FitCameraToRange();
}
@@ -61,14 +91,15 @@ public class Tower : MonoBehaviour
{
UpdateRangeCircle();
FitCameraToRange();
TickTeslaField();
if (Time.time < cooldownUntil)
return;
if (Time.time >= nextFireTime)
{
RetargetAndFire();
nextFireTime = Time.time + fireRate;
float cooldownMultiplier = RetargetAndFire();
nextFireTime = Time.time + fireRate * cooldownMultiplier;
}
}
@@ -135,25 +166,91 @@ public class Tower : MonoBehaviour
return Sprite.Create(texture, new Rect(0f, 0f, size, size), new Vector2(0.5f, 0.5f), size);
}
void ApplyCannonBaseline()
static void CreateTeslaArc(Vector2 start, Vector2 end)
{
if (cannonBaselineApplied)
return;
GameObject arc = new GameObject("TeslaArc", typeof(LineRenderer), typeof(ShockwaveEffect));
LineRenderer line = arc.GetComponent<LineRenderer>();
line.useWorldSpace = true;
line.positionCount = 6;
line.startWidth = 0.055f;
line.endWidth = 0.018f;
line.material = new Material(Shader.Find("Sprites/Default"));
line.startColor = new Color(0.5f, 0.9f, 1f, 0.98f);
line.endColor = new Color(0.95f, 1f, 1f, 0.2f);
fireRate *= 1.1f;
cannonBaselineApplied = true;
for (int i = 0; i < line.positionCount; i++)
{
float t = (float)i / (line.positionCount - 1);
Vector2 point = Vector2.Lerp(start, end, t);
if (i > 0 && i < line.positionCount - 1)
point += Random.insideUnitCircle * 0.12f;
line.SetPosition(i, point);
}
arc.GetComponent<ShockwaveEffect>().Configure(0.12f);
}
void RetargetAndFire()
static void CreateTeslaPulseVisual(Vector2 center, float radius)
{
GameObject ring = new GameObject("TeslaPulse", typeof(LineRenderer), typeof(ShockwaveEffect));
ring.transform.position = center;
LineRenderer line = ring.GetComponent<LineRenderer>();
line.useWorldSpace = false;
line.loop = true;
line.positionCount = 56;
line.startWidth = 0.03f;
line.endWidth = 0.03f;
line.material = new Material(Shader.Find("Sprites/Default"));
line.startColor = new Color(0.42f, 0.88f, 1f, 0.78f);
line.endColor = new Color(0.42f, 0.88f, 1f, 0.78f);
for (int i = 0; i < line.positionCount; i++)
{
float angle = (float)i / line.positionCount * Mathf.PI * 2f;
line.SetPosition(i, new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0f) * radius);
}
ring.GetComponent<ShockwaveEffect>().Configure(0.25f);
}
void ApplySelectedTowerBaseline()
{
if (towerBaselineApplied)
return;
string selectedTower = PlayerPrefs.GetString(SelectedTowerKey, "cannon");
if (selectedTower == TeslaTowerId)
{
isTeslaTower = true;
damage = Mathf.Max(1, damage);
fireRate *= 0.62f;
attackRange *= 1.2f;
splashRadius *= 0.45f;
splashDamageMultiplier *= 0.2f;
projectileSpeedMultiplier *= 1.35f;
}
else
{
fireRate *= 1.1f;
}
towerBaselineApplied = true;
}
float RetargetAndFire()
{
currentTarget = GetNearestEnemy();
if (currentTarget != null)
{
FireAtTarget(currentTarget);
return isTeslaTower ? FireTeslaAtTarget(currentTarget) : FireAtTarget(currentTarget);
}
teslaCapacitorStacks = 0;
return 1f;
}
void FireAtTarget(GameObject target)
float FireAtTarget(GameObject target)
{
shotsFired++;
FireShell(target, BuildShellStats());
@@ -163,6 +260,211 @@ public class Tower : MonoBehaviour
if (glassCannonDesign && shotsFired % 10 == 0)
cooldownUntil = Time.time + 1.75f;
return 1f;
}
float FireTeslaAtTarget(GameObject target)
{
shotsFired++;
if (teslaCapacitorBankStacks > 0)
teslaCapacitorStacks = Mathf.Min(teslaCapacitorStacks + 1, teslaCapacitorBankStacks * 6);
List<EnemyHealth> hitEnemies = new List<EnemyHealth>();
EnemyHealth targetHealth = target.GetComponent<EnemyHealth>();
if (targetHealth == null)
return 1f;
int maxTargets = teslaInfiniteArc ? 64 : Mathf.Max(1, 1 + teslaChainCount);
float damageMultiplier = teslaDamageMultiplier * (1f + teslaCapacitorStacks * 0.035f);
if (teslaStormbringerProtocol)
damageMultiplier *= 1f + Mathf.Min(0.75f, CountEnemiesInRange(transform.position, attackRange) * 0.025f);
if (teslaOverflowDamage > 0f)
{
damageMultiplier += teslaOverflowDamage;
teslaOverflowDamage = 0f;
}
Vector2 chainSource = transform.position;
EnemyHealth current = targetHealth;
int chainIndex = 0;
int totalHit = 0;
while (current != null && chainIndex < maxTargets)
{
float falloff = Mathf.Pow(teslaChainFalloff, chainIndex);
int dealtDamage = DamageTeslaTarget(current, chainSource, damageMultiplier * falloff, hitEnemies.Count == 0);
totalHit++;
hitEnemies.Add(current);
chainSource = current.transform.position;
TryForkTesla(current, hitEnemies, damageMultiplier * falloff * 0.62f);
current = FindNextTeslaTarget(chainSource, hitEnemies);
if (current == null && teslaArcBounce && hitEnemies.Count > 1)
current = hitEnemies[Random.Range(0, hitEnemies.Count)];
chainIndex++;
if (teslaInfiniteArc && damageMultiplier * Mathf.Pow(teslaChainFalloff, chainIndex) < 0.2f)
break;
}
if (totalHit == 1 && teslaSuperconductorStacks > 0)
DamageTeslaTarget(targetHealth, transform.position, teslaSuperconductorStacks * 1.2f, false);
if (teslaEmpSurgeStacks > 0 && shotsFired % 20 == 0)
TeslaPulse(transform.position, attackRange * 0.9f, 0.7f + teslaEmpSurgeStacks * 0.35f);
if (teslaFieldStacks > 0 && Time.time >= teslaFieldNextTick)
{
teslaFieldNextTick = Time.time + 0.55f;
TeslaPulse(transform.position, 1.9f + teslaFieldStacks * 0.7f, 0.22f * teslaFieldStacks);
}
if (teslaMeltdownStacks > 0 && Random.value < 0.06f * teslaMeltdownStacks)
cooldownUntil = Time.time + 0.5f;
float feedback = Mathf.Clamp01(1f - totalHit * teslaFeedbackLoopMultiplier);
return Mathf.Clamp(feedback, 0.42f, 1f);
}
int DamageTeslaTarget(EnemyHealth enemyHealth, Vector2 source, float multiplier, bool primary)
{
if (enemyHealth == null)
return 0;
int healthBefore = enemyHealth.GetCurrentHealth();
int shockDamage = Mathf.Max(1, Mathf.RoundToInt(damage * multiplier));
CreateTeslaArc(source, enemyHealth.transform.position);
bool killed = enemyHealth.TakeDamage(shockDamage);
ApplyTeslaControl(enemyHealth);
NotifyTeslaHit();
if (teslaCriticalExplosionChance > 0f && Random.value < teslaCriticalExplosionChance)
TeslaPulse(enemyHealth.transform.position, 1.1f, 0.55f);
if (killed)
{
if (teslaEnergyOverflow)
teslaOverflowDamage += Mathf.Clamp01((shockDamage - healthBefore) / 5f);
if (teslaVoltageCollapseMultiplier > 0f)
TeslaPulse(enemyHealth.transform.position, 1.3f, teslaVoltageCollapseMultiplier);
}
return shockDamage;
}
void TryForkTesla(EnemyHealth fromEnemy, List<EnemyHealth> hitEnemies, float multiplier)
{
if (teslaForkChance <= 0f || Random.value > teslaForkChance || fromEnemy == null)
return;
EnemyHealth forkTarget = FindNextTeslaTarget(fromEnemy.transform.position, hitEnemies);
if (forkTarget != null)
DamageTeslaTarget(forkTarget, fromEnemy.transform.position, multiplier, false);
}
void ApplyTeslaControl(EnemyHealth enemyHealth)
{
Enemy enemy = enemyHealth.GetComponent<Enemy>();
if (enemy != null)
{
if (teslaSlowDuration > 0f)
enemy.ApplySlow(teslaSlowMultiplier, teslaSlowDuration);
if (teslaParalysisStacks > 0 && Random.value < 0.08f * teslaParalysisStacks)
enemy.ApplyStun(0.28f + 0.12f * teslaParalysisStacks);
if (teslaMagneticPull > 0f)
PullEnemiesToward(enemyHealth.transform.position, teslaMagneticPull);
}
if (teslaIonizedBonus > 0f)
enemyHealth.ApplyIonized(teslaIonizedBonus, 2.5f);
if (teslaResidualDamageMultiplier > 0f)
enemyHealth.ApplyResidualCurrent(Mathf.Max(1, Mathf.RoundToInt(damage * teslaResidualDamageMultiplier)), 2.4f, 0.8f);
}
EnemyHealth FindNextTeslaTarget(Vector2 source, List<EnemyHealth> ignored)
{
EnemyHealth best = null;
float bestDistance = float.MaxValue;
Collider2D[] colliders = Physics2D.OverlapCircleAll(source, teslaChainRange);
foreach (Collider2D collider in colliders)
{
if (!collider.CompareTag("Enemy"))
continue;
EnemyHealth enemyHealth = collider.GetComponent<EnemyHealth>();
if (enemyHealth == null || ignored.Contains(enemyHealth))
continue;
float distance = Vector2.Distance(source, collider.transform.position);
if (distance < bestDistance)
{
best = enemyHealth;
bestDistance = distance;
}
}
return best;
}
void TeslaPulse(Vector2 center, float radius, float damageMultiplier)
{
CreateTeslaPulseVisual(center, radius);
Collider2D[] colliders = Physics2D.OverlapCircleAll(center, radius);
foreach (Collider2D collider in colliders)
{
if (!collider.CompareTag("Enemy"))
continue;
EnemyHealth enemyHealth = collider.GetComponent<EnemyHealth>();
if (enemyHealth == null)
continue;
enemyHealth.TakeDamage(Mathf.Max(1, Mathf.RoundToInt(damage * teslaDamageMultiplier * damageMultiplier)));
ApplyTeslaControl(enemyHealth);
}
}
void TickTeslaField()
{
if (!isTeslaTower || teslaFieldStacks <= 0 || Time.time < teslaFieldNextTick)
return;
teslaFieldNextTick = Time.time + 0.55f;
TeslaPulse(transform.position, 1.9f + teslaFieldStacks * 0.7f, 0.22f * teslaFieldStacks);
}
void PullEnemiesToward(Vector2 center, float distance)
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(center, 1.8f);
foreach (Collider2D collider in colliders)
{
if (!collider.CompareTag("Enemy"))
continue;
Enemy enemy = collider.GetComponent<Enemy>();
if (enemy != null)
enemy.PullToward(center, distance);
}
}
int CountEnemiesInRange(Vector2 center, float radius)
{
int count = 0;
Collider2D[] colliders = Physics2D.OverlapCircleAll(center, radius);
foreach (Collider2D collider in colliders)
{
if (collider.CompareTag("Enemy"))
count++;
}
return count;
}
void NotifyTeslaHit()
{
UpgradeManager upgradeManager = FindAnyObjectByType<UpgradeManager>();
if (upgradeManager != null)
upgradeManager.EnemyHitByBullet();
}
void FireShell(GameObject target, Bullet.ShellStats stats)
+50
View File
@@ -31,6 +31,31 @@ public class UpgradeCard : MonoBehaviour
VolatileMunitions = 22,
GlassCannonDesign = 23,
ApocalypseRound = 24,
HighVoltageCoils = 25,
RapidCapacitors = 26,
ConductiveReach = 27,
ArcStability = 28,
OverclockedWiring = 29,
ExtendedArc = 30,
ForkedLightning = 31,
ArcBounce = 32,
StormCurrent = 33,
Superconductor = 34,
StaticCharge = 35,
ParalysisField = 36,
IonizedArmor = 37,
ResidualCurrent = 38,
EmpSurge = 39,
CapacitorBanks = 40,
EnergyOverflow = 41,
FeedbackLoop = 42,
UnstablePlasma = 43,
TeslaField = 44,
MagneticStorm = 45,
VoltageCollapse = 46,
MeltdownCore = 47,
InfiniteArc = 48,
StormbringerProtocol = 49,
}
@@ -123,6 +148,31 @@ public class UpgradeCard : MonoBehaviour
case UpgradeType.VolatileMunitions: return "VOLATILE MUNITIONS";
case UpgradeType.GlassCannonDesign: return "GLASS CANNON DESIGN";
case UpgradeType.ApocalypseRound: return "APOCALYPSE ROUND";
case UpgradeType.HighVoltageCoils: return "HIGH VOLTAGE COILS";
case UpgradeType.RapidCapacitors: return "RAPID CAPACITORS";
case UpgradeType.ConductiveReach: return "CONDUCTIVE REACH";
case UpgradeType.ArcStability: return "ARC STABILITY";
case UpgradeType.OverclockedWiring: return "OVERCLOCKED WIRING";
case UpgradeType.ExtendedArc: return "EXTENDED ARC";
case UpgradeType.ForkedLightning: return "FORKED LIGHTNING";
case UpgradeType.ArcBounce: return "ARC BOUNCE";
case UpgradeType.StormCurrent: return "STORM CURRENT";
case UpgradeType.Superconductor: return "SUPERCONDUCTOR";
case UpgradeType.StaticCharge: return "STATIC CHARGE";
case UpgradeType.ParalysisField: return "PARALYSIS FIELD";
case UpgradeType.IonizedArmor: return "IONIZED ARMOR";
case UpgradeType.ResidualCurrent: return "RESIDUAL CURRENT";
case UpgradeType.EmpSurge: return "EMP SURGE";
case UpgradeType.CapacitorBanks: return "CAPACITOR BANKS";
case UpgradeType.EnergyOverflow: return "ENERGY OVERFLOW";
case UpgradeType.FeedbackLoop: return "FEEDBACK LOOP";
case UpgradeType.UnstablePlasma: return "UNSTABLE PLASMA";
case UpgradeType.TeslaField: return "TESLA FIELD";
case UpgradeType.MagneticStorm: return "MAGNETIC STORM";
case UpgradeType.VoltageCollapse: return "VOLTAGE COLLAPSE";
case UpgradeType.MeltdownCore: return "MELTDOWN CORE";
case UpgradeType.InfiniteArc: return "INFINITE ARC";
case UpgradeType.StormbringerProtocol: return "STORMBRINGER PROTOCOL";
default: return type.ToString().ToUpperInvariant();
}
}
+131 -3
View File
@@ -6,6 +6,9 @@ using UnityEngine.UI;
public class UpgradeManager : MonoBehaviour
{
const string SelectedTowerKey = "SelectedTower";
const string TeslaTowerId = "tesla";
enum FreeUpgradeType
{
KillBonus,
@@ -77,6 +80,35 @@ public class UpgradeManager : MonoBehaviour
Card(UpgradeCard.UpgradeType.ApocalypseRound, "APOCALYPSE ROUND", "Every 20th shot fires an enormous extreme-damage shell.", "Legendary", 1, 14),
};
static readonly CardDefinition[] TeslaCards =
{
Card(UpgradeCard.UpgradeType.HighVoltageCoils, "HIGH VOLTAGE COILS", "Lightning Damage +12%.", "Common", 8, 3),
Card(UpgradeCard.UpgradeType.RapidCapacitors, "RAPID CAPACITORS", "Attack Speed +10%.", "Common", 8, 3),
Card(UpgradeCard.UpgradeType.ConductiveReach, "CONDUCTIVE REACH", "Tower Range +15%.", "Common", 5, 3),
Card(UpgradeCard.UpgradeType.ArcStability, "ARC STABILITY", "Chain damage falloff reduced.", "Uncommon", 5, 5),
Card(UpgradeCard.UpgradeType.OverclockedWiring, "OVERCLOCKED WIRING", "Attack Speed +18%, Range -8%.", "Uncommon", 4, 5),
Card(UpgradeCard.UpgradeType.ExtendedArc, "EXTENDED ARC", "Lightning chains to +1 additional enemy.", "Rare", 6, 7),
Card(UpgradeCard.UpgradeType.ForkedLightning, "FORKED LIGHTNING", "Each chain has a 20% chance to split into another nearby enemy.", "Rare", 4, 7),
Card(UpgradeCard.UpgradeType.ArcBounce, "ARC BOUNCE", "Chains can bounce back to previously hit enemies for reduced damage.", "Epic", 2, 10),
Card(UpgradeCard.UpgradeType.StormCurrent, "STORM CURRENT", "Chain distance increased significantly.", "Uncommon", 4, 5),
Card(UpgradeCard.UpgradeType.Superconductor, "SUPERCONDUCTOR", "If only one enemy is hit, damage massively increases.", "Epic", 3, 10),
Card(UpgradeCard.UpgradeType.StaticCharge, "STATIC CHARGE", "Hit enemies are slowed by 10%.", "Common", 5, 3),
Card(UpgradeCard.UpgradeType.ParalysisField, "PARALYSIS FIELD", "Repeated hits briefly stun enemies.", "Epic", 2, 10),
Card(UpgradeCard.UpgradeType.IonizedArmor, "IONIZED ARMOR", "Shocked enemies take increased damage from all sources.", "Rare", 4, 7),
Card(UpgradeCard.UpgradeType.ResidualCurrent, "RESIDUAL CURRENT", "Enemies continue taking electrical damage over time after being shocked.", "Rare", 5, 7),
Card(UpgradeCard.UpgradeType.EmpSurge, "EMP SURGE", "Every 20 attacks emits a large pulse damaging all nearby enemies.", "Epic", 3, 10),
Card(UpgradeCard.UpgradeType.CapacitorBanks, "CAPACITOR BANKS", "Consecutive attacks without interruption gain stacking damage.", "Rare", 4, 7),
Card(UpgradeCard.UpgradeType.EnergyOverflow, "ENERGY OVERFLOW", "Excess damage from kills transfers into the next chain.", "Rare", 3, 7),
Card(UpgradeCard.UpgradeType.FeedbackLoop, "FEEDBACK LOOP", "Each chained enemy slightly reduces reload time for the next shot.", "Rare", 5, 7),
Card(UpgradeCard.UpgradeType.UnstablePlasma, "UNSTABLE PLASMA", "Critical strikes create mini lightning explosions.", "Epic", 3, 10),
Card(UpgradeCard.UpgradeType.TeslaField, "TESLA FIELD", "Creates a passive electric field around the tower damaging nearby enemies.", "Epic", 3, 10),
Card(UpgradeCard.UpgradeType.MagneticStorm, "MAGNETIC STORM", "Shocked enemies slightly pull nearby enemies toward them.", "Epic", 2, 10),
Card(UpgradeCard.UpgradeType.VoltageCollapse, "VOLTAGE COLLAPSE", "Enemies killed by lightning explode in a small electrical burst.", "Rare", 4, 7),
Card(UpgradeCard.UpgradeType.MeltdownCore, "MELTDOWN CORE", "Massive damage increase, but occasional self-stun after firing.", "Legendary", 2, 14),
Card(UpgradeCard.UpgradeType.InfiniteArc, "INFINITE ARC", "Chains no longer have a maximum target count while damage keeps decaying.", "Legendary", 1, 14),
Card(UpgradeCard.UpgradeType.StormbringerProtocol, "STORMBRINGER PROTOCOL", "During high enemy density, attack speed ramps up dramatically.", "Legendary", 1, 14),
};
void Start()
{
InitializeUpgrades();
@@ -142,7 +174,7 @@ public class UpgradeManager : MonoBehaviour
void InitializeUpgrades()
{
foreach (CardDefinition card in CannonCards)
foreach (CardDefinition card in GetActiveCards())
{
if (!upgradeLevels.ContainsKey(card.type))
upgradeLevels[card.type] = 0;
@@ -154,7 +186,7 @@ public class UpgradeManager : MonoBehaviour
List<UpgradeOffer> BuildOffers(bool guaranteeEpicOrHigher)
{
List<UpgradeOffer> paidOffers = new List<UpgradeOffer>();
foreach (CardDefinition card in CannonCards)
foreach (CardDefinition card in GetActiveCards())
{
int cost = upgradeCosts[card.type];
if (bits < cost || IsAtMaxStacks(card.type))
@@ -518,6 +550,86 @@ public class UpgradeManager : MonoBehaviour
case UpgradeCard.UpgradeType.ApocalypseRound:
tower.apocalypseRound = true;
break;
case UpgradeCard.UpgradeType.HighVoltageCoils:
tower.teslaDamageMultiplier *= 1.12f;
break;
case UpgradeCard.UpgradeType.RapidCapacitors:
tower.fireRate *= 0.9f;
break;
case UpgradeCard.UpgradeType.ConductiveReach:
tower.attackRange *= 1.15f;
break;
case UpgradeCard.UpgradeType.ArcStability:
tower.teslaChainFalloff = Mathf.Min(0.94f, tower.teslaChainFalloff + 0.045f);
break;
case UpgradeCard.UpgradeType.OverclockedWiring:
tower.fireRate *= 0.82f;
tower.attackRange *= 0.92f;
break;
case UpgradeCard.UpgradeType.ExtendedArc:
tower.teslaChainCount++;
break;
case UpgradeCard.UpgradeType.ForkedLightning:
tower.teslaForkChance += 0.2f;
break;
case UpgradeCard.UpgradeType.ArcBounce:
tower.teslaArcBounce = true;
tower.teslaChainFalloff = Mathf.Min(0.96f, tower.teslaChainFalloff + 0.04f);
break;
case UpgradeCard.UpgradeType.StormCurrent:
tower.teslaChainRange *= 1.28f;
break;
case UpgradeCard.UpgradeType.Superconductor:
tower.teslaSuperconductorStacks++;
break;
case UpgradeCard.UpgradeType.StaticCharge:
tower.teslaSlowMultiplier = Mathf.Min(tower.teslaSlowMultiplier, Mathf.Max(0.45f, 0.9f - 0.06f * upgradeLevels[type]));
tower.teslaSlowDuration = Mathf.Max(tower.teslaSlowDuration, 1.5f);
break;
case UpgradeCard.UpgradeType.ParalysisField:
tower.teslaParalysisStacks++;
break;
case UpgradeCard.UpgradeType.IonizedArmor:
tower.teslaIonizedBonus += 0.12f;
break;
case UpgradeCard.UpgradeType.ResidualCurrent:
tower.teslaResidualDamageMultiplier += 0.14f;
break;
case UpgradeCard.UpgradeType.EmpSurge:
tower.teslaEmpSurgeStacks++;
break;
case UpgradeCard.UpgradeType.CapacitorBanks:
tower.teslaCapacitorBankStacks++;
break;
case UpgradeCard.UpgradeType.EnergyOverflow:
tower.teslaEnergyOverflow = true;
tower.teslaDamageMultiplier *= 1.06f;
break;
case UpgradeCard.UpgradeType.FeedbackLoop:
tower.teslaFeedbackLoopMultiplier += 0.025f;
break;
case UpgradeCard.UpgradeType.UnstablePlasma:
tower.teslaCriticalExplosionChance += 0.12f;
break;
case UpgradeCard.UpgradeType.TeslaField:
tower.teslaFieldStacks++;
break;
case UpgradeCard.UpgradeType.MagneticStorm:
tower.teslaMagneticPull += 0.08f;
break;
case UpgradeCard.UpgradeType.VoltageCollapse:
tower.teslaVoltageCollapseMultiplier += 0.22f;
break;
case UpgradeCard.UpgradeType.MeltdownCore:
tower.teslaMeltdownStacks++;
tower.teslaDamageMultiplier *= 1.55f;
break;
case UpgradeCard.UpgradeType.InfiniteArc:
tower.teslaInfiniteArc = true;
break;
case UpgradeCard.UpgradeType.StormbringerProtocol:
tower.teslaStormbringerProtocol = true;
break;
}
}
@@ -640,7 +752,7 @@ public class UpgradeManager : MonoBehaviour
image.color = new Color(0f, 0f, 0f, 0.74f);
image.raycastTarget = true;
string titleText = freeFallback ? "LOW BITS: CHOOSE SUPPLY BOOST" : "CHOOSE CANNON UPGRADE";
string titleText = freeFallback ? "LOW BITS: CHOOSE SUPPLY BOOST" : IsTeslaTowerSelected() ? "CHOOSE TESLA UPGRADE" : "CHOOSE CANNON UPGRADE";
TextMeshProUGUI title = CreateText("Title", rect, titleText, 28f, FontStyles.Bold, TextAlignmentOptions.Center);
RectTransform titleRect = title.rectTransform;
titleRect.anchorMin = new Vector2(0.5f, 0.5f);
@@ -682,9 +794,25 @@ public class UpgradeManager : MonoBehaviour
return card;
}
foreach (CardDefinition card in TeslaCards)
{
if (card.type == type)
return card;
}
return CannonCards[0];
}
static CardDefinition[] GetActiveCards()
{
return IsTeslaTowerSelected() ? TeslaCards : CannonCards;
}
static bool IsTeslaTowerSelected()
{
return PlayerPrefs.GetString(SelectedTowerKey, "cannon") == TeslaTowerId;
}
static RectTransform CreateRect(string name, Transform parent)
{
GameObject obj = new GameObject(name, typeof(RectTransform));