diff --git a/Assets/Polyart/PolyartStudio/DreamscapeCastle/Scenes/DemoExterior.unity b/Assets/Polyart/PolyartStudio/DreamscapeCastle/Scenes/DemoExterior.unity index 5e53bbc3..0519d2d5 100644 --- a/Assets/Polyart/PolyartStudio/DreamscapeCastle/Scenes/DemoExterior.unity +++ b/Assets/Polyart/PolyartStudio/DreamscapeCastle/Scenes/DemoExterior.unity @@ -45650,24 +45650,6 @@ Transform: type: 3} m_PrefabInstance: {fileID: 268364652} m_PrefabAsset: {fileID: 0} ---- !u!114 &269148361 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 385312391933029727, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - m_PrefabInstance: {fileID: 447491950} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9de313fb3f367d04bbd130657183cdc8, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!4 &269148367 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - m_PrefabInstance: {fileID: 447491950} - m_PrefabAsset: {fileID: 0} --- !u!1 &270497163 GameObject: m_ObjectHideFlags: 0 @@ -69347,99 +69329,6 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 446839706} m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} ---- !u!1001 &447491950 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 481936153} - m_Modifications: - - target: {fileID: 3951388154437428790, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: enableRegen - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_Name - value: Enemy (1) - objectReference: {fileID: 0} - - target: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_IsActive - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.x - value: 0.05879492 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.y - value: 0.43239498 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.z - value: 0.06264454 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.x - value: -0.103 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.y - value: 0.16271059 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.z - value: -0.274 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.x - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.y - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.z - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: a205c5f30032031428dae83c4a2f2af8, type: 3} --- !u!1001 &447901445 PrefabInstance: m_ObjectHideFlags: 0 @@ -74744,12 +74633,9 @@ Transform: serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 17.00827, y: 2.3127, z: 15.96308} + m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 - m_Children: - - {fileID: 1806068479} - - {fileID: 269148367} - - {fileID: 2008455231} + m_Children: [] m_Father: {fileID: 1078692958} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &483243085 @@ -168780,8 +168666,9 @@ GameObject: m_Component: - component: {fileID: 1078692958} - component: {fileID: 1078692957} + - component: {fileID: 1078692959} m_Layer: 0 - m_Name: GameObject + m_Name: Level 1 m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -168799,11 +168686,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 42850bd66917c2648a04c287cd0fa78f, type: 3} m_Name: m_EditorClassIdentifier: - enemiesParent: {fileID: 481936153} - enemies: - - {fileID: 1806068473} - - {fileID: 269148361} - - {fileID: 2008455225} + spawner: {fileID: 1078692959} checkInterval: 0.25 --- !u!4 &1078692958 Transform: @@ -168821,6 +168704,30 @@ Transform: - {fileID: 481936153} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1078692959 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1078692956} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9274e1d861dffcc4f828e772e0d685d2, type: 3} + m_Name: + m_EditorClassIdentifier: + waves: + - enemyPrefab: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, + type: 3} + count: 2 + spawnRate: 5 + - enemyPrefab: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, + type: 3} + count: 3 + spawnRate: 2 + spawnPoint: {fileID: 481936153} + containerParent: {fileID: 481936153} + timeBetweenWaves: 10 --- !u!1001 &1080331081 PrefabInstance: m_ObjectHideFlags: 0 @@ -301654,24 +301561,6 @@ Transform: type: 3} m_PrefabInstance: {fileID: 1805929970} m_PrefabAsset: {fileID: 0} ---- !u!114 &1806068473 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 385312391933029727, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - m_PrefabInstance: {fileID: 1527259665985799113} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9de313fb3f367d04bbd130657183cdc8, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!4 &1806068479 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - m_PrefabInstance: {fileID: 1527259665985799113} - m_PrefabAsset: {fileID: 0} --- !u!1001 &1806685759 PrefabInstance: m_ObjectHideFlags: 0 @@ -314028,94 +313917,6 @@ Transform: type: 3} m_PrefabInstance: {fileID: 1886256379} m_PrefabAsset: {fileID: 0} ---- !u!1001 &1886577979 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 481936153} - m_Modifications: - - target: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_Name - value: Enemy (2) - objectReference: {fileID: 0} - - target: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_IsActive - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.x - value: 0.05879492 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.y - value: 0.43239498 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.z - value: 0.06264454 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.x - value: -0.049 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.y - value: 0.16271059 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.z - value: 0.118 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.x - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.y - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.z - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: a205c5f30032031428dae83c4a2f2af8, type: 3} --- !u!1001 &1887138831 PrefabInstance: m_ObjectHideFlags: 0 @@ -331623,24 +331424,6 @@ Transform: type: 3} m_PrefabInstance: {fileID: 2008147940} m_PrefabAsset: {fileID: 0} ---- !u!114 &2008455225 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 385312391933029727, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - m_PrefabInstance: {fileID: 1886577979} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9de313fb3f367d04bbd130657183cdc8, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!4 &2008455231 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - m_PrefabInstance: {fileID: 1886577979} - m_PrefabAsset: {fileID: 0} --- !u!1001 &2008475454 PrefabInstance: m_ObjectHideFlags: 0 @@ -355462,99 +355245,6 @@ Transform: type: 3} m_PrefabInstance: {fileID: 2145953391} m_PrefabAsset: {fileID: 0} ---- !u!1001 &1527259665985799113 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 481936153} - m_Modifications: - - target: {fileID: 3951388154437428790, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: enableRegen - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_Name - value: Enemy - objectReference: {fileID: 0} - - target: {fileID: 4006626876238256025, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_IsActive - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.x - value: 0.05879492 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.y - value: 0.43239498 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalScale.z - value: 0.06264454 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.x - value: 0.224 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.y - value: 0.16271059 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalPosition.z - value: -0.136 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.x - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.y - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalRotation.z - value: -0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7154477416992791333, guid: a205c5f30032031428dae83c4a2f2af8, - type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: a205c5f30032031428dae83c4a2f2af8, type: 3} --- !u!4 &1827334621118886615 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Level1.cs b/Assets/Scripts/Level1.cs index 891c02f5..f6f8301f 100644 --- a/Assets/Scripts/Level1.cs +++ b/Assets/Scripts/Level1.cs @@ -1,61 +1,69 @@ using System.Collections; -using System.Collections.Generic; using UnityEngine; public class Level1 : Level { - [Header("Enemy Setup")] - [Tooltip("Optional: parent whose children have EnemyBase components")] - public Transform enemiesParent; - - [Tooltip("Or manually assign enemies here")] - public List enemies = new List(); - - [Header("Monitoring")] - [Tooltip("How often to check alive enemies")] - public float checkInterval = 0.25f; + [Header("Wave Flow")] + public WaveSpawner spawner; // Assign in Inspector + [Min(0.05f)] public float checkInterval = 0.25f; private bool _running; private Coroutine _monitorCo; + // Optional: simple counters for UI hooks + private int _currentWaveIdx = -1; + private int _totalWaves = 0; + private int _currentAlive = 0; + + private void OnEnable() + { + if (spawner != null) + { + spawner.OnWaveStarted += HandleWaveStarted; + spawner.OnWaveCompleted += HandleWaveCompleted; + spawner.OnAllWavesCompleted += HandleAllWavesCompleted; + spawner.OnAliveCountChanged += HandleAliveChanged; + } + } + + private void OnDisable() + { + if (spawner != null) + { + spawner.OnWaveStarted -= HandleWaveStarted; + spawner.OnWaveCompleted -= HandleWaveCompleted; + spawner.OnAllWavesCompleted -= HandleAllWavesCompleted; + spawner.OnAliveCountChanged -= HandleAliveChanged; + } + } + public override void OnLevelStart() { base.OnLevelStart(); - // Collect enemies if using a parent - if (enemiesParent != null) + if (spawner == null) { - enemies.Clear(); - foreach (Transform child in enemiesParent) - { - var eb = child.GetComponent(); - if (eb != null) enemies.Add(eb); - } - } - - // Turn enemies on & let their own Start() find the player - foreach (var e in enemies) - { - if (e == null) continue; - e.gameObject.SetActive(true); - } - - // Edge case: no enemies -> complete immediately - if (CountAlive() == 0) - { - CompleteLevel(); + Debug.LogError("[Level1] No WaveSpawner assigned."); + CompleteLevelImmediate(); return; } + _totalWaves = spawner.TotalWaves; _running = true; - _monitorCo = StartCoroutine(MonitorEnemies()); - Debug.Log("Level1: Started, monitoring enemies..."); + + // Kick off waves + spawner.StartSpawning(); + + // As an extra safeguard, monitor completion state + _monitorCo = StartCoroutine(MonitorForCompletion()); + Debug.Log("[Level1] Level started, waves spawning…"); } public override void OnLevelEnd() { base.OnLevelEnd(); _running = false; + if (_monitorCo != null) { StopCoroutine(_monitorCo); @@ -63,46 +71,55 @@ public class Level1 : Level } } - private IEnumerator MonitorEnemies() + private IEnumerator MonitorForCompletion() { var wait = new WaitForSeconds(checkInterval); while (_running) { - if (CountAlive() == 0) + // Complete when spawner reports all waves completed AND no alive remain + if (spawner.AllWavesCompleted && spawner.CurrentAlive == 0) { - CompleteLevel(); + CompleteLevelImmediate(); yield break; } yield return wait; } } - private int CountAlive() + private void CompleteLevelImmediate() { - int alive = 0; - for (int i = 0; i < enemies.Count; i++) - { - var e = enemies[i]; - if (e != null && e.gameObject.activeInHierarchy && !e.IsDead()) - alive++; - } - return alive; + _running = false; + Debug.Log("[Level1] All waves cleared. Level complete!"); + + if (GameplayManager.Instance != null) + GameplayManager.Instance.CompleteCurrentLevel(); + else + gameObject.SetActive(false); } - private void CompleteLevel() + // ===== Event handlers (useful for UI/logs/hook-ins) ===== + private void HandleWaveStarted(int waveIdx, WaveSpawner.Wave wave) { - Debug.Log("Level1: All enemies defeated. Completing level."); - _running = false; + _currentWaveIdx = waveIdx; + Debug.Log($"[Level1] Wave {waveIdx + 1}/{_totalWaves} started. Count={wave.count}, Rate={wave.spawnRate:F2}/s"); + // TODO: Update UI: “Wave X/Y starting” + } - // Tell GameplayManager if available - if (GameplayManager.Instance != null) - { - GameplayManager.Instance.CompleteCurrentLevel(); - } - else - { - // Fallback: just disable this level object - gameObject.SetActive(false); - } + private void HandleWaveCompleted(int waveIdx) + { + Debug.Log($"[Level1] Wave {waveIdx + 1}/{_totalWaves} completed."); + // TODO: UI “Wave complete” + } + + private void HandleAllWavesCompleted() + { + Debug.Log("[Level1] All waves spawned. Waiting for last enemies to die…"); + // Completion is finalized in MonitorForCompletion once alive == 0 + } + + private void HandleAliveChanged(int currentAlive) + { + _currentAlive = currentAlive; + // TODO: UI “Enemies Left: currentAlive” } } diff --git a/Assets/Scripts/WaveSpawner.cs b/Assets/Scripts/WaveSpawner.cs index f4651c94..f9067dcd 100644 --- a/Assets/Scripts/WaveSpawner.cs +++ b/Assets/Scripts/WaveSpawner.cs @@ -1,69 +1,192 @@ -using UnityEngine; +using System; using System.Collections; using System.Collections.Generic; +using UnityEngine; public class WaveSpawner : MonoBehaviour { - [System.Serializable] + [Serializable] public class Wave { - public GameObject enemyPrefab; // Enemy prefab to spawn - public int count = 5; // Number of enemies in this wave - public float spawnRate = 1f; // Enemies per second (1 = 1/sec) + public GameObject enemyPrefab; + [Min(1)] public int count = 5; + [Tooltip("Enemies per second. 1 = spawn every 1s, 5 = every 0.2s")] + [Min(0.0001f)] public float spawnRate = 1f; } [Header("Setup")] public Wave[] waves; public Transform spawnPoint; - [Tooltip("Optional parent to keep the hierarchy tidy")] + [Tooltip("Optional parent to keep hierarchy clean")] public Transform containerParent; - public float timeBetweenWaves = 5f; - public bool autoStart = false; + [Min(0f)] public float timeBetweenWaves = 3f; + public int CurrentWaveIndex { get; private set; } = -1; // 0-based, -1 = none yet public bool IsSpawning { get; private set; } - public bool AllWavesDone { get; private set; } + public bool AllWavesCompleted { get; private set; } + public int CurrentAlive => _alive.Count; - public readonly List spawned = new List(); + // EVENTS (subscribe from Level1 or any other listener) + public event Action OnWaveStarted; // args: waveIndex, waveDef + public event Action OnWaveCompleted; // args: waveIndex + public event Action OnAllWavesCompleted; + public event Action OnEnemySpawned; // args: instance + public event Action OnAliveCountChanged; // args: currentAlive - void Start() - { - if (autoStart) StartSpawning(); - } + // Internals + private readonly List _alive = new List(); + private Coroutine _routine; + + public int TotalWaves => waves != null ? waves.Length : 0; public void StartSpawning() { - if (IsSpawning || AllWavesDone) return; - StartCoroutine(SpawnRoutine()); + if (IsSpawning || AllWavesCompleted) return; + if (spawnPoint == null || waves == null || waves.Length == 0) + { + Debug.LogWarning("[WaveSpawner] Missing spawnPoint or waves."); + return; + } + _routine = StartCoroutine(SpawnRoutine()); } - IEnumerator SpawnRoutine() + public void StopSpawning() + { + if (_routine != null) + { + StopCoroutine(_routine); + _routine = null; + } + IsSpawning = false; + } + + private IEnumerator SpawnRoutine() { IsSpawning = true; - AllWavesDone = false; + AllWavesCompleted = false; for (int w = 0; w < waves.Length; w++) { + CurrentWaveIndex = w; var wave = waves[w]; + OnWaveStarted?.Invoke(w, wave); + + // spawn phase for (int i = 0; i < wave.count; i++) { - SpawnEnemy(wave.enemyPrefab); - yield return new WaitForSeconds(1f / Mathf.Max(0.0001f, wave.spawnRate)); + SpawnEnemyOnce(wave.enemyPrefab); + yield return new WaitForSeconds(1f / wave.spawnRate); } - if (w < waves.Length - 1) + // wait until wave cleared (no alive remaining from all spawns so far) + yield return StartCoroutine(WaitUntilCleared()); + + OnWaveCompleted?.Invoke(w); + + if (w < waves.Length - 1 && timeBetweenWaves > 0f) yield return new WaitForSeconds(timeBetweenWaves); } IsSpawning = false; - AllWavesDone = true; + AllWavesCompleted = true; + OnAllWavesCompleted?.Invoke(); } - void SpawnEnemy(GameObject enemyPrefab) + private IEnumerator WaitUntilCleared() { - if (enemyPrefab == null || spawnPoint == null) return; - - var go = Instantiate(enemyPrefab, spawnPoint.position, spawnPoint.rotation, containerParent); - var eb = go.GetComponent(); - if (eb != null) spawned.Add(eb); + var wait = new WaitForSeconds(0.25f); + while (true) + { + CompactAliveList(); + if (_alive.Count == 0) break; + yield return wait; + } } -} \ No newline at end of file + + private void SpawnEnemyOnce(GameObject prefab) + { + if (prefab == null) return; + var go = Instantiate(prefab, spawnPoint.position, spawnPoint.rotation, containerParent); + + // Attach relay so we detect destruction/disable cleanly + var relay = go.AddComponent<_EnemyLifeRelay>(); + relay.Init(this); + + _alive.Add(go); + OnEnemySpawned?.Invoke(go); + OnAliveCountChanged?.Invoke(_alive.Count); + } + + private void CompactAliveList() + { + for (int i = _alive.Count - 1; i >= 0; i--) + { + var go = _alive[i]; + if (go == null) + { + _alive.RemoveAt(i); + continue; + } + + // Consider not-alive when: + // 1) Destroyed (handled by null above), OR + // 2) Inactive in hierarchy, OR + // 3) Has EnemyBase and IsDead() is true. + bool alive = go.activeInHierarchy; + if (alive) + { + var eb = go.GetComponent(); + if (eb != null && eb.IsDead()) alive = false; + } + + if (!alive) + { + _alive.RemoveAt(i); + } + } + } + + // Called by relay on death/destroy/disable + private void NotifyGone(GameObject go) + { + if (go == null) return; + int idx = _alive.IndexOf(go); + if (idx >= 0) + { + _alive.RemoveAt(idx); + OnAliveCountChanged?.Invoke(_alive.Count); + } + } + + // Helper component placed on each spawned enemy + private class _EnemyLifeRelay : MonoBehaviour + { + private WaveSpawner _owner; + private EnemyBase _enemyBase; + private bool _notified; + + public void Init(WaveSpawner owner) + { + _owner = owner; + _enemyBase = GetComponent(); + // If your EnemyBase has an OnDeath event, you can hook it here: + // _enemyBase.OnDeath += HandleGone; + } + + private void OnDisable() => HandleGone(); + private void OnDestroy() => HandleGone(); + + private void HandleGone() + { + if (_notified || _owner == null) return; + + // If EnemyBase exists and isn't actually dead yet (e.g., pooled), skip. + // We'll rely on spawner's periodic compaction in that case. + if (_enemyBase != null && !_enemyBase.IsDead() && gameObject.activeInHierarchy) + return; + + _owner.NotifyGone(gameObject); + _notified = true; + } + } +}