using System; using System.Collections; using System.Collections.Generic; using System.Linq; using DefaultNamespace; using Events; using UnityEngine; using UnityEngine.Assertions; using Random = UnityEngine.Random; namespace Wave { public class WaveSpawner : MonoBehaviour { [SerializeField] private SpawnWaveSOEventChannelSO startNewSpawnWaveEventChannel; [SerializeField] private SpawnWaveSOEventChannelSO endSpawnWaveEventChannel; [SerializeField] private EnemyRuntimeSetSO enemyRuntimeSet; [SerializeField] private Transform spawnCenter; [SerializeField] private float minimumSpawnRadius; [SerializeField] [Min(0)] private float timeBetweenClearedWaves; [SerializeField] [Min(0)] private float timeBeforeFirstWave; private float _timeSinceLastSpawnWaveStarted; private Coroutine _handleSpawnWaves; private bool HasWaveBeenCleared => enemyRuntimeSet.IsEmpty; public void Begin(IEnumerable spawnWaves) { Assert.IsNull(_handleSpawnWaves); _handleSpawnWaves = StartCoroutine(CO_HandleSpawnWaves(spawnWaves)); } public void End() { StopCoroutine(_handleSpawnWaves); } private IEnumerator CO_HandleSpawnWaves(IEnumerable spawnWaves) { yield return new WaitForSeconds(timeBeforeFirstWave); foreach (var spawnWave in spawnWaves) { startNewSpawnWaveEventChannel.RaiseEvent(spawnWave); yield return SpawnWave(spawnWave); _timeSinceLastSpawnWaveStarted = 0.0f; yield return new WaitUntil(() => _timeSinceLastSpawnWaveStarted >= spawnWave.TimeToComplete || HasWaveBeenCleared); endSpawnWaveEventChannel.RaiseEvent(spawnWave); yield return new WaitForSeconds(timeBetweenClearedWaves); } } private IEnumerator SpawnWave(SpawnWaveSO spawnWave) { var enemyPacksToSpawn = spawnWave.Packs.Select(pack => pack.EnemiesToSpawn()).ToList().AsReadOnly(); var totalPacksToSpawn = enemyPacksToSpawn.Count; var numberPacksSpawned = 0; foreach (var enemyPackToSpawn in enemyPacksToSpawn) { numberPacksSpawned++; var waveCompletionPercentage = (float) numberPacksSpawned / (float) totalPacksToSpawn; // TODO (Michael): Both the pack and enemy position samplers need to be a bit smarter. I think in most scenarios the pack circle just needs to not overlap with other pack circles. // The enemy spawns probably need something like a poission disk sampler so that monsters dont overlap. The enemy spawn radius at the pack's position should also probably scale with the size of the pack. var enemyPackSpawnCenter = CalculatePackSpawnPosition(waveCompletionPercentage, spawnWave); foreach (var enemy in enemyPackToSpawn) { var spawnLocation = CalculateEnemySpawnPosition(enemyPackSpawnCenter, spawnWave.PackRadius); var spawnedEnemy = Instantiate(enemy, spawnLocation, Quaternion.identity); enemyRuntimeSet.Add(spawnedEnemy); if (spawnWave.TimeBetweenSpawns > 0.0f) { yield return new WaitForSeconds(spawnWave.TimeBetweenSpawns); } } if (spawnWave.TimeBetweenPacks > 0.0f) { yield return new WaitForSeconds(spawnWave.TimeBetweenPacks); } } } private Vector3 CalculatePackSpawnPosition(float waveCompletionPercentage, SpawnWaveSO spawnWave) { var angle = waveCompletionPercentage * 2 * MathF.PI; var unitCirclePosition = Random.insideUnitCircle; var swizzledUnitCirclePosition = new Vector3(unitCirclePosition.x, 0, unitCirclePosition.y); var spawnOffset = spawnWave.Distribution switch { SpawnDistribution.Uniform => new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * spawnWave.Radius, SpawnDistribution.Random => swizzledUnitCirclePosition.normalized * spawnWave.Radius, SpawnDistribution.DiskRandom => swizzledUnitCirclePosition * spawnWave.Radius, _ => throw new ArgumentOutOfRangeException(nameof(spawnWave.Distribution), spawnWave.Distribution, null) }; spawnOffset += spawnOffset.normalized * (minimumSpawnRadius + spawnWave.PackRadius); return spawnCenter.position + spawnOffset; } private Vector3 CalculateEnemySpawnPosition(Vector3 center, float radius) { var unitCirclePosition = Random.insideUnitCircle; var swizzledUnitCirclePosition = new Vector3(unitCirclePosition.x, 0, unitCirclePosition.y); var spawnOffset = swizzledUnitCirclePosition * radius; return center + spawnOffset; } private void Update() { _timeSinceLastSpawnWaveStarted += Time.deltaTime; } } }