120 lines
5.1 KiB
C#
120 lines
5.1 KiB
C#
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);
|
|
|
|
if (HasWaveBeenCleared)
|
|
{
|
|
yield return new WaitForSeconds(timeBetweenClearedWaves);
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerator SpawnWave(SpawnWaveSO spawnWave)
|
|
{
|
|
var enemyPacksToSpawn = spawnWave.Packs.Select(pack => pack.EnemiesToSpawn()).ToList().AsReadOnly();
|
|
|
|
for (var packN = 0; packN < enemyPacksToSpawn.Count; packN++)
|
|
{
|
|
var waveCompletionPercentage = (float)packN / (float)enemyPacksToSpawn.Count;
|
|
|
|
var enemyPackSpawnOffset = SampleSpawnOffset(spawnWave.Distribution, spawnWave.Radius, waveCompletionPercentage);
|
|
enemyPackSpawnOffset += enemyPackSpawnOffset.normalized * (minimumSpawnRadius + spawnWave.PackRadius);
|
|
|
|
var enemyPackSpawnPosition = spawnCenter.position + enemyPackSpawnOffset;
|
|
|
|
var enemyPackToSpawn = enemyPacksToSpawn[packN];
|
|
for (var enemyN = 0; enemyN < enemyPackToSpawn.Count; enemyN++)
|
|
{
|
|
var packCompletionPercentage = (float)enemyN / (float)enemyPackToSpawn.Count;
|
|
var enemySpawnOffset = SampleSpawnOffset(SpawnDistribution.DiskRandom, spawnWave.PackRadius, packCompletionPercentage);
|
|
var enemySpawnPosition = enemyPackSpawnPosition + enemySpawnOffset;
|
|
|
|
var spawnedEnemy = Instantiate(enemyPackToSpawn[enemyN], enemySpawnPosition, 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 static Vector3 SampleSpawnOffset(SpawnDistribution distribution, float radius, float completionPercentage)
|
|
{
|
|
// 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 angle = completionPercentage * 2 * MathF.PI;
|
|
var unitCirclePosition = Random.insideUnitCircle;
|
|
var swizzledUnitCirclePosition = new Vector3(unitCirclePosition.x, 0, unitCirclePosition.y);
|
|
|
|
return distribution switch
|
|
{
|
|
SpawnDistribution.Uniform => new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * radius,
|
|
SpawnDistribution.Random => swizzledUnitCirclePosition.normalized * radius,
|
|
SpawnDistribution.DiskRandom => swizzledUnitCirclePosition * radius,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(distribution), distribution, null)
|
|
};
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
_timeSinceLastSpawnWaveStarted += Time.deltaTime;
|
|
}
|
|
}
|
|
} |