idle-survivors/Assets/Scripts/Wave/WaveSpawner.cs

129 lines
5.3 KiB
C#
Raw Normal View History

2023-08-02 17:27:45 -04:00
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<SpawnWaveSO> spawnWaves)
{
Assert.IsNull(_handleSpawnWaves);
_handleSpawnWaves = StartCoroutine(CO_HandleSpawnWaves(spawnWaves));
}
public void End()
{
StopCoroutine(_handleSpawnWaves);
}
private IEnumerator CO_HandleSpawnWaves(IEnumerable<SpawnWaveSO> 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;
}
}
}