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 ;
2023-08-02 17:27:45 -04:00
yield return new WaitUntil ( ( ) = > _timeSinceLastSpawnWaveStarted > = spawnWave . TimeToComplete | | HasWaveBeenCleared ) ;
2023-08-02 17:27:45 -04:00
endSpawnWaveEventChannel . RaiseEvent ( spawnWave ) ;
2023-08-02 17:27:45 -04:00
if ( HasWaveBeenCleared )
{
yield return new WaitForSeconds ( timeBetweenClearedWaves ) ;
}
2023-08-02 17:27:45 -04:00
}
}
private IEnumerator SpawnWave ( SpawnWaveSO spawnWave )
{
var enemyPacksToSpawn = spawnWave . Packs . Select ( pack = > pack . EnemiesToSpawn ( ) ) . ToList ( ) . AsReadOnly ( ) ;
2023-08-02 17:27:45 -04:00
for ( var packN = 0 ; packN < enemyPacksToSpawn . Count ; packN + + )
2023-08-02 17:27:45 -04:00
{
2023-08-02 17:27:45 -04:00
var waveCompletionPercentage = ( float ) packN / ( float ) enemyPacksToSpawn . Count ;
2023-08-02 17:27:45 -04:00
2023-08-02 17:27:45 -04:00
var enemyPackSpawnOffset = SampleSpawnOffset ( spawnWave . Distribution , spawnWave . Radius , waveCompletionPercentage ) ;
enemyPackSpawnOffset + = enemyPackSpawnOffset . normalized * ( minimumSpawnRadius + spawnWave . PackRadius ) ;
2023-08-02 17:27:45 -04:00
2023-08-02 17:27:45 -04:00
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 ;
2023-08-02 17:27:45 -04:00
2023-08-02 17:27:45 -04:00
var spawnedEnemy = Instantiate ( enemyPackToSpawn [ enemyN ] , enemySpawnPosition , Quaternion . identity ) ;
enemyRuntimeSet . Add ( spawnedEnemy ) ;
2023-08-02 17:27:45 -04:00
if ( spawnWave . TimeBetweenSpawns > 0.0f )
{
yield return new WaitForSeconds ( spawnWave . TimeBetweenSpawns ) ;
}
2023-08-02 17:27:45 -04:00
2023-08-02 17:27:45 -04:00
}
if ( spawnWave . TimeBetweenPacks > 0.0f )
{
yield return new WaitForSeconds ( spawnWave . TimeBetweenPacks ) ;
}
}
}
2023-08-02 17:27:45 -04:00
private static Vector3 SampleSpawnOffset ( SpawnDistribution distribution , float radius , float completionPercentage )
2023-08-02 17:27:45 -04:00
{
2023-08-02 17:27:45 -04:00
// 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 ;
2023-08-02 17:27:45 -04:00
var unitCirclePosition = Random . insideUnitCircle ;
var swizzledUnitCirclePosition = new Vector3 ( unitCirclePosition . x , 0 , unitCirclePosition . y ) ;
2023-08-02 17:27:45 -04:00
return distribution switch
2023-08-02 17:27:45 -04:00
{
2023-08-02 17:27:45 -04:00
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 )
2023-08-02 17:27:45 -04:00
} ;
}
private void Update ( )
{
_timeSinceLastSpawnWaveStarted + = Time . deltaTime ;
}
}
}