diff --git a/Assembly-CSharp.Player.csproj b/Assembly-CSharp.Player.csproj
index 4840741..01c1f8b 100644
--- a/Assembly-CSharp.Player.csproj
+++ b/Assembly-CSharp.Player.csproj
@@ -53,7 +53,9 @@
+
+
diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj
index f47d18f..f0d61ca 100644
--- a/Assembly-CSharp.csproj
+++ b/Assembly-CSharp.csproj
@@ -50,11 +50,13 @@
+
+
diff --git a/Assets/Scenes/MainMenu.unity b/Assets/Scenes/MainMenu.unity
index 20bf87c..43e2e8c 100644
--- a/Assets/Scenes/MainMenu.unity
+++ b/Assets/Scenes/MainMenu.unity
@@ -468,7 +468,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 1
+ m_IsActive: 0
--- !u!224 &1038208932
RectTransform:
m_ObjectHideFlags: 0
@@ -738,7 +738,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 1
+ m_IsActive: 0
--- !u!224 &1406026031
RectTransform:
m_ObjectHideFlags: 0
diff --git a/Assets/Scripts/Bullet.cs b/Assets/Scripts/Bullet.cs
index 645dd66..8f05239 100644
--- a/Assets/Scripts/Bullet.cs
+++ b/Assets/Scripts/Bullet.cs
@@ -1,45 +1,98 @@
+using System.Collections;
+using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
+ public struct ShellStats
+ {
+ public int directDamage;
+ public float directHitDamageMultiplier;
+ public float splashRadius;
+ public float splashDamageMultiplier;
+ public float speedMultiplier;
+ public float knockback;
+ public float shrapnelEdgeDamageMultiplier;
+ public int fragmentCount;
+ public float burningGroundDuration;
+ public float concussiveSlowMultiplier;
+ public float concussiveSlowDuration;
+ public float craterDuration;
+ public float craterSlowMultiplier;
+ public float chainDetonationMultiplier;
+ public float executionDamageMultiplier;
+ public float finisherDamageMultiplier;
+ public float delayedFuseSeconds;
+ public float volatileSecondaryExplosionMultiplier;
+ }
+
public float speed = 5f;
- private int damage = 1;
- private Transform target;
public GameObject shatterEffectPrefab;
- private Vector2 lastKnownDirection;
- private bool hasTarget = true; // Track if bullet has an active target
+
+ ShellStats stats;
+ Transform target;
+ Vector2 lastKnownDirection;
+ bool hasTarget = true;
+ bool exploded;
+
+ void Awake()
+ {
+ stats = new ShellStats
+ {
+ directDamage = 1,
+ directHitDamageMultiplier = 1f,
+ splashRadius = 1.15f,
+ splashDamageMultiplier = 0.45f,
+ speedMultiplier = 1f,
+ concussiveSlowMultiplier = 1f,
+ craterSlowMultiplier = 1f,
+ };
+ }
+
+ public void Configure(ShellStats shellStats)
+ {
+ stats = shellStats;
+ stats.directDamage = Mathf.Max(1, stats.directDamage);
+ stats.directHitDamageMultiplier = Mathf.Max(0f, stats.directHitDamageMultiplier);
+ stats.splashRadius = Mathf.Max(0.25f, stats.splashRadius);
+ stats.speedMultiplier = Mathf.Max(0.1f, stats.speedMultiplier);
+ if (stats.concussiveSlowMultiplier <= 0f)
+ stats.concussiveSlowMultiplier = 1f;
+ if (stats.craterSlowMultiplier <= 0f)
+ stats.craterSlowMultiplier = 1f;
+ }
public void SetTarget(GameObject enemy)
{
if (enemy != null)
{
target = enemy.transform;
- lastKnownDirection = (target.position - transform.position).normalized; // Store initial direction
+ lastKnownDirection = (target.position - transform.position).normalized;
UpdateRotation();
}
}
public void SetDamage(int newDamage)
{
- damage = newDamage;
+ stats.directDamage = newDamage;
}
void Update()
{
+ if (exploded)
+ return;
+
if (hasTarget && target != null)
{
- lastKnownDirection = (target.position - transform.position).normalized; // Keep updating direction
+ lastKnownDirection = (target.position - transform.position).normalized;
}
else
{
- hasTarget = false; // The target is gone, continue in last direction
+ hasTarget = false;
}
- // Move bullet in the last known direction
UpdateRotation();
- transform.position += (Vector3)lastKnownDirection * speed * Time.deltaTime;
-
- // Destroy bullet after 5 seconds to prevent infinite movement
+ transform.position += (Vector3)lastKnownDirection * (speed * stats.speedMultiplier) * Time.deltaTime;
Destroy(gameObject, 5f);
}
@@ -53,19 +106,217 @@ public class Bullet : MonoBehaviour
void OnTriggerEnter2D(Collider2D other)
{
- if (other.CompareTag("Enemy"))
+ if (!exploded && other.CompareTag("Enemy"))
+ StartCoroutine(ExplodeAfterFuse(other.GetComponent()));
+ }
+
+ IEnumerator ExplodeAfterFuse(EnemyHealth directTarget)
+ {
+ exploded = true;
+ if (stats.delayedFuseSeconds > 0f)
{
- other.GetComponent().TakeDamage(damage);
- ShatterEffect();
- Destroy(gameObject);
+ GetComponent().enabled = false;
+ float elapsed = 0f;
+ while (elapsed < stats.delayedFuseSeconds)
+ {
+ elapsed += Time.deltaTime;
+ transform.position += (Vector3)lastKnownDirection * (speed * stats.speedMultiplier) * Time.deltaTime;
+ yield return null;
+ }
}
+
+ Explode(directTarget);
+ Destroy(gameObject);
+ }
+
+ void Explode(EnemyHealth directTarget)
+ {
+ Vector2 center = transform.position;
+ NotifyBulletHit();
+ ShatterEffect();
+ CreateShockwave(center, stats.splashRadius);
+
+ HashSet hitEnemies = new HashSet();
+ bool directKilled = false;
+
+ if (directTarget != null)
+ {
+ Collider2D directCollider = directTarget.GetComponent();
+ if (directCollider != null)
+ ApplyExplosionControl(directCollider, center);
+
+ int directDamage = CalculateDirectDamage(directTarget);
+ directKilled = directTarget.TakeDamage(directDamage);
+ hitEnemies.Add(directTarget);
+ }
+
+ Collider2D[] colliders = Physics2D.OverlapCircleAll(center, stats.splashRadius);
+ foreach (Collider2D collider in colliders)
+ {
+ if (!collider.CompareTag("Enemy"))
+ continue;
+
+ EnemyHealth enemyHealth = collider.GetComponent();
+ if (enemyHealth == null || hitEnemies.Contains(enemyHealth))
+ continue;
+
+ float distance = Vector2.Distance(center, collider.transform.position);
+ float falloff = Mathf.Clamp01(1f - distance / stats.splashRadius);
+ int splashDamage = Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * stats.splashDamageMultiplier * Mathf.Lerp(0.5f, 1f, falloff)));
+
+ if (stats.shrapnelEdgeDamageMultiplier > 0f && distance >= stats.splashRadius * 0.68f)
+ splashDamage += Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * stats.shrapnelEdgeDamageMultiplier));
+
+ bool killed = enemyHealth.TakeDamage(splashDamage);
+ hitEnemies.Add(enemyHealth);
+ ApplyExplosionControl(collider, center);
+
+ if (killed)
+ TriggerChainDetonation(collider.transform.position);
+ }
+
+ if (directKilled)
+ {
+ TriggerChainDetonation(center);
+ TriggerVolatileDetonation(center);
+ }
+
+ SpawnFragments(center);
+ SpawnLingeringArea(center);
+ }
+
+ int CalculateDirectDamage(EnemyHealth targetHealth)
+ {
+ float multiplier = stats.directHitDamageMultiplier;
+ float healthPercent = targetHealth.GetHealthPercent();
+
+ if (healthPercent >= 0.8f)
+ multiplier *= 1f + stats.executionDamageMultiplier;
+ if (healthPercent <= 0.3f)
+ multiplier *= 1f + stats.finisherDamageMultiplier;
+
+ return Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * multiplier));
+ }
+
+ void ApplyExplosionControl(Collider2D enemyCollider, Vector2 center)
+ {
+ Enemy enemy = enemyCollider.GetComponent();
+ if (enemy == null)
+ return;
+
+ if (stats.knockback > 0f)
+ enemy.ApplyKnockback(center, stats.knockback);
+ if (stats.concussiveSlowDuration > 0f)
+ enemy.ApplySlow(stats.concussiveSlowMultiplier, stats.concussiveSlowDuration);
+ }
+
+ void SpawnFragments(Vector2 center)
+ {
+ if (stats.fragmentCount <= 0)
+ return;
+
+ Collider2D[] colliders = Physics2D.OverlapCircleAll(center, stats.splashRadius * 1.75f);
+ int fragmentsLeft = stats.fragmentCount;
+ foreach (Collider2D collider in colliders)
+ {
+ if (fragmentsLeft <= 0)
+ break;
+ if (!collider.CompareTag("Enemy"))
+ continue;
+
+ EnemyHealth enemyHealth = collider.GetComponent();
+ if (enemyHealth == null)
+ continue;
+
+ enemyHealth.TakeDamage(Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * 0.35f)));
+ fragmentsLeft--;
+ }
+ }
+
+ void SpawnLingeringArea(Vector2 center)
+ {
+ if (stats.burningGroundDuration <= 0f && stats.craterDuration <= 0f)
+ return;
+
+ GameObject area = new GameObject("CannonLingeringArea", typeof(CircleCollider2D), typeof(CannonAreaEffect));
+ area.transform.position = center;
+
+ CircleCollider2D circle = area.GetComponent();
+ circle.isTrigger = true;
+ circle.radius = stats.splashRadius * 0.72f;
+
+ CannonAreaEffect effect = area.GetComponent();
+ effect.Configure(
+ Mathf.Max(stats.burningGroundDuration, stats.craterDuration),
+ stats.burningGroundDuration > 0f ? Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * 0.18f)) : 0,
+ stats.craterDuration > 0f ? stats.craterSlowMultiplier : 1f);
+ }
+
+ void TriggerChainDetonation(Vector2 center)
+ {
+ if (stats.chainDetonationMultiplier <= 0f)
+ return;
+
+ DamageSecondaryExplosion(center, stats.splashRadius * 0.65f, stats.chainDetonationMultiplier);
+ }
+
+ void TriggerVolatileDetonation(Vector2 center)
+ {
+ if (stats.volatileSecondaryExplosionMultiplier <= 0f || Random.value > 0.2f)
+ return;
+
+ DamageSecondaryExplosion(center, stats.splashRadius * 0.75f, stats.volatileSecondaryExplosionMultiplier);
+ }
+
+ void DamageSecondaryExplosion(Vector2 center, float radius, float damageMultiplier)
+ {
+ CreateShockwave(center, radius);
+ Collider2D[] colliders = Physics2D.OverlapCircleAll(center, radius);
+ foreach (Collider2D collider in colliders)
+ {
+ if (!collider.CompareTag("Enemy"))
+ continue;
+
+ EnemyHealth enemyHealth = collider.GetComponent();
+ if (enemyHealth != null)
+ enemyHealth.TakeDamage(Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * damageMultiplier)));
+ }
+ }
+
+ void NotifyBulletHit()
+ {
+ UpgradeManager upgradeManager = FindAnyObjectByType();
+ if (upgradeManager != null)
+ upgradeManager.EnemyHitByBullet();
}
void ShatterEffect()
{
if (shatterEffectPrefab != null)
- {
Instantiate(shatterEffectPrefab, transform.position, Quaternion.identity);
+ }
+
+ static void CreateShockwave(Vector2 center, float radius)
+ {
+ GameObject ring = new GameObject("CannonShockwave", typeof(LineRenderer), typeof(ShockwaveEffect));
+ ring.transform.position = center;
+
+ LineRenderer line = ring.GetComponent();
+ line.useWorldSpace = false;
+ line.loop = true;
+ line.positionCount = 48;
+ line.startWidth = 0.035f;
+ line.endWidth = 0.035f;
+ line.material = new Material(Shader.Find("Sprites/Default"));
+ line.startColor = new Color(0.95f, 0.82f, 0.45f, 0.82f);
+ line.endColor = new Color(0.95f, 0.82f, 0.45f, 0.82f);
+
+ 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().Configure(0.28f);
}
}
diff --git a/Assets/Scripts/CannonAreaEffect.cs b/Assets/Scripts/CannonAreaEffect.cs
new file mode 100644
index 0000000..8623e48
--- /dev/null
+++ b/Assets/Scripts/CannonAreaEffect.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+public class CannonAreaEffect : MonoBehaviour
+{
+ readonly Dictionary nextDamageTick = new Dictionary();
+
+ float duration;
+ int damagePerSecond;
+ float slowMultiplier = 1f;
+ float expiresAt;
+
+ public void Configure(float effectDuration, int burnDamagePerSecond, float craterSlowMultiplier)
+ {
+ duration = effectDuration;
+ damagePerSecond = burnDamagePerSecond;
+ slowMultiplier = Mathf.Clamp(craterSlowMultiplier, 0.1f, 1f);
+ expiresAt = Time.time + duration;
+ Destroy(gameObject, duration);
+ }
+
+ void OnTriggerStay2D(Collider2D other)
+ {
+ if (!other.CompareTag("Enemy"))
+ return;
+
+ Enemy enemy = other.GetComponent();
+ if (enemy != null && slowMultiplier < 1f)
+ enemy.ApplySlow(slowMultiplier, 0.18f);
+
+ if (damagePerSecond <= 0)
+ return;
+
+ EnemyHealth health = other.GetComponent();
+ if (health == null)
+ return;
+
+ if (!nextDamageTick.TryGetValue(health, out float nextTick))
+ nextTick = 0f;
+
+ if (Time.time < nextTick || Time.time >= expiresAt)
+ return;
+
+ health.TakeDamage(Mathf.Max(1, damagePerSecond));
+ nextDamageTick[health] = Time.time + 1f;
+ }
+}
diff --git a/Assets/Scripts/Enemy.cs b/Assets/Scripts/Enemy.cs
index c604800..a4b6c44 100644
--- a/Assets/Scripts/Enemy.cs
+++ b/Assets/Scripts/Enemy.cs
@@ -5,6 +5,8 @@ public class Enemy : MonoBehaviour
public float speed = 1.5f;
private Transform tower;
private EnemyHealth enemyHealth;
+ private float slowMultiplier = 1f;
+ private float slowEndTime;
void Start()
{
@@ -20,7 +22,10 @@ public class Enemy : MonoBehaviour
void Update()
{
if (tower == null || enemyHealth == null) return;
- transform.position = Vector2.MoveTowards(transform.position, tower.position, speed * Time.deltaTime);
+ if (Time.time >= slowEndTime)
+ slowMultiplier = 1f;
+
+ transform.position = Vector2.MoveTowards(transform.position, tower.position, speed * slowMultiplier * Time.deltaTime);
if (Vector2.Distance(transform.position, tower.position) < 0.5f)
{
int damageDealt = enemyHealth.GetCurrentHealth();
@@ -29,4 +34,19 @@ public class Enemy : MonoBehaviour
Destroy(gameObject);
}
}
-}
\ No newline at end of file
+
+ public void ApplySlow(float multiplier, float duration)
+ {
+ slowMultiplier = Mathf.Min(slowMultiplier, Mathf.Clamp(multiplier, 0.1f, 1f));
+ slowEndTime = Mathf.Max(slowEndTime, Time.time + duration);
+ }
+
+ public void ApplyKnockback(Vector2 source, float distance)
+ {
+ Vector2 direction = ((Vector2)transform.position - source).normalized;
+ if (direction.sqrMagnitude <= Mathf.Epsilon)
+ return;
+
+ transform.position += (Vector3)(direction * distance);
+ }
+}
diff --git a/Assets/Scripts/EnemyHealth.cs b/Assets/Scripts/EnemyHealth.cs
index 4753161..7d723c5 100644
--- a/Assets/Scripts/EnemyHealth.cs
+++ b/Assets/Scripts/EnemyHealth.cs
@@ -5,9 +5,13 @@ public class EnemyHealth : MonoBehaviour
public int health = 1;
public GameObject shatterEffectPrefab;
public EnemySpawner spawner;
+ private int maxHealth = 1;
- public void TakeDamage(int damage)
+ public bool TakeDamage(int damage)
{
+ if (health <= 0)
+ return false;
+
health -= damage;
if (health <= 0)
{
@@ -15,7 +19,10 @@ public class EnemyHealth : MonoBehaviour
ShatterEffect();
NotifyUpgradeManager();
Destroy(gameObject);
+ return true;
}
+
+ return false;
}
public int GetCurrentHealth()
@@ -26,6 +33,12 @@ public class EnemyHealth : MonoBehaviour
public void SetHealth(int newHealth)
{
health = newHealth;
+ maxHealth = Mathf.Max(1, newHealth);
+ }
+
+ public float GetHealthPercent()
+ {
+ return maxHealth <= 0 ? 0f : (float)health / maxHealth;
}
public void NotifySpawner()
@@ -38,7 +51,7 @@ public class EnemyHealth : MonoBehaviour
void NotifyUpgradeManager()
{
- UpgradeManager upgradeManager = FindObjectOfType();
+ UpgradeManager upgradeManager = FindAnyObjectByType();
if (upgradeManager != null)
{
upgradeManager.EnemyKilled();
@@ -52,4 +65,4 @@ public class EnemyHealth : MonoBehaviour
Instantiate(shatterEffectPrefab, transform.position, Quaternion.identity);
}
}
-}
\ No newline at end of file
+}
diff --git a/Assets/Scripts/EnemySpawner.cs b/Assets/Scripts/EnemySpawner.cs
index 4cc7ea7..1e48faa 100644
--- a/Assets/Scripts/EnemySpawner.cs
+++ b/Assets/Scripts/EnemySpawner.cs
@@ -2,28 +2,33 @@ using System.Collections;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
+using UnityEngine.SceneManagement;
public class EnemySpawner : MonoBehaviour
{
+ const int WavesPerStage = 15;
+ const string CurrentStageKey = "CurrentStage";
+
public GameObject enemyPrefab;
public Transform tower;
public TextMeshProUGUI waveText;
public Button startWaveButton;
public float spawnRadius = 10f;
- private int currentWave = 1;
+ private int currentWave = 0;
+ private int currentStage = 1;
private bool isSpawning = false;
+ private bool stageComplete = false;
private int enemiesRemaining = 0;
private int baseEnemyCount = 5;
private int enemyCountIncrease = 3;
private float enemySpeed = 1.5f;
private int enemyBaseHP = 1;
- private float speedIncreaseRate = 0.2f;
- private int hpIncreaseRate = 1;
private float maxEnemySpeed = 6f;
private int maxEnemyHP = 20;
void Start()
{
+ currentStage = Mathf.Max(1, PlayerPrefs.GetInt(CurrentStageKey, 1));
EnsureWaveText();
StyleStartWaveButton();
UpdateWaveText();
@@ -31,31 +36,37 @@ public class EnemySpawner : MonoBehaviour
public void StartWave()
{
- if (!isSpawning)
- {
- currentWave++;
- UpdateWaveText();
- enemiesRemaining = baseEnemyCount + (currentWave) * enemyCountIncrease;
+ if (isSpawning || stageComplete || currentWave >= WavesPerStage)
+ return;
+
+ currentWave++;
+ isSpawning = true;
+ UpdateWaveText();
+ enemiesRemaining = GetEnemyCountForWave(currentWave);
+ if (startWaveButton != null)
startWaveButton.gameObject.SetActive(false);
- StartCoroutine(SpawnWave());
- }
+ StartCoroutine(SpawnWave());
}
IEnumerator SpawnWave()
{
- isSpawning = true;
- int enemiesToSpawn = baseEnemyCount + (currentWave) * enemyCountIncrease;
+ int enemiesToSpawn = GetNormalEnemyCount(currentWave);
for (int i = 0; i < enemiesToSpawn; i++)
{
- SpawnEnemy();
+ SpawnEnemy(false);
yield return new WaitForSeconds(1f);
}
+ if (IsBossWave(currentWave))
+ {
+ SpawnEnemy(true);
+ }
+
isSpawning = false;
}
- void SpawnEnemy()
+ void SpawnEnemy(bool boss)
{
Vector2 spawnPos = GetRandomSpawnPosition();
GameObject newEnemy = Instantiate(enemyPrefab, spawnPos, Quaternion.identity);
@@ -65,19 +76,33 @@ public class EnemySpawner : MonoBehaviour
{
// Lower speed scale to 0.1f
float waveSpeedBonus = currentWave * 0.1f;
- enemyComponent.speed = Mathf.Min(maxEnemySpeed, enemySpeed + waveSpeedBonus);
+ float stageSpeedBonus = (currentStage - 1) * 0.08f;
+ enemyComponent.speed = Mathf.Min(maxEnemySpeed, enemySpeed + waveSpeedBonus + stageSpeedBonus);
+ if (boss)
+ enemyComponent.speed *= 0.72f;
}
EnemyHealth enemyHealth = newEnemy.GetComponent();
if (enemyHealth != null)
{
// For HP: baseHP + wave * 0.3 (round it)
- int scaledHP = enemyBaseHP + Mathf.RoundToInt(currentWave * 0.3f);
- scaledHP = Mathf.Min(maxEnemyHP, scaledHP);
+ int scaledHP = enemyBaseHP + Mathf.RoundToInt(currentWave * 0.3f) + Mathf.RoundToInt((currentStage - 1) * 0.5f);
+ if (boss)
+ scaledHP = Mathf.RoundToInt((scaledHP + currentStage + currentWave) * 4.5f);
+ scaledHP = Mathf.Min(boss ? maxEnemyHP * 4 : maxEnemyHP, scaledHP);
enemyHealth.SetHealth(scaledHP);
enemyHealth.spawner = this;
}
+
+ if (boss)
+ {
+ newEnemy.name = $"Boss Wave {currentWave}";
+ newEnemy.transform.localScale *= 1.8f;
+ SpriteRenderer spriteRenderer = newEnemy.GetComponent();
+ if (spriteRenderer != null)
+ spriteRenderer.color = new Color(0.95f, 0.32f, 0.28f, 1f);
+ }
}
Vector2 GetRandomSpawnPosition()
@@ -90,29 +115,105 @@ public class EnemySpawner : MonoBehaviour
{
if (waveText != null)
{
- waveText.text = $"WAVE {currentWave}";
+ int displayWave = Mathf.Clamp(isSpawning ? currentWave : currentWave + 1, 1, WavesPerStage);
+ waveText.text = $"Wave {displayWave}/{WavesPerStage}";
}
}
public void EnemyDefeated()
{
+ if (stageComplete)
+ return;
+
enemiesRemaining--;
if (enemiesRemaining <= 0)
{
- startWaveButton.gameObject.SetActive(true);
-
- // If wave is multiple of 5, show special upgrade choice
- if (currentWave % 5 == 0)
+ if (currentWave >= WavesPerStage)
{
- UpgradeManager upgradeManager = FindAnyObjectByType();
- if (upgradeManager != null)
- {
- upgradeManager.ShowSpecialUpgradeChoice();
- }
+ CompleteStage();
+ return;
}
+
+ UpgradeManager upgradeManager = FindAnyObjectByType();
+ if (upgradeManager != null)
+ {
+ upgradeManager.ShowWaveUpgradeChoice(PrepareNextWave);
+ return;
+ }
+
+ PrepareNextWave();
}
}
+ void PrepareNextWave()
+ {
+ if (startWaveButton != null)
+ startWaveButton.gameObject.SetActive(true);
+ UpdateWaveText();
+ }
+
+ int GetNormalEnemyCount(int wave)
+ {
+ return baseEnemyCount + wave * enemyCountIncrease + Mathf.Max(0, currentStage - 1);
+ }
+
+ int GetEnemyCountForWave(int wave)
+ {
+ return GetNormalEnemyCount(wave) + (IsBossWave(wave) ? 1 : 0);
+ }
+
+ static bool IsBossWave(int wave)
+ {
+ return wave == 5 || wave == 10 || wave == 15;
+ }
+
+ void CompleteStage()
+ {
+ stageComplete = true;
+ isSpawning = false;
+ int nextStage = currentStage + 1;
+ PlayerPrefs.SetInt(CurrentStageKey, nextStage);
+ PlayerPrefs.Save();
+ ShowWinScreen(nextStage);
+ }
+
+ void ShowWinScreen(int nextStage)
+ {
+ Time.timeScale = 0f;
+
+ Canvas canvas = FindAnyObjectByType