204 lines
6.2 KiB
C#
204 lines
6.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using GeometryTD.DataTable;
|
|
using GeometryTD.Definition;
|
|
using UnityEngine;
|
|
|
|
namespace GeometryTD.CustomComponent
|
|
{
|
|
internal sealed class EnemySpawnDirector
|
|
{
|
|
private sealed class SpawnEntryRuntime
|
|
{
|
|
public DRLevelSpawnEntry Entry;
|
|
public bool Completed;
|
|
public float NextTriggerTime;
|
|
public float EndTime;
|
|
public int RemainingCount;
|
|
}
|
|
|
|
private const float MinStreamInterval = 0.05f;
|
|
private const float MinBurstGap = 0.01f;
|
|
|
|
private readonly List<SpawnEntryRuntime> _spawnRuntimes = new();
|
|
private float _phaseElapsed;
|
|
|
|
public bool IsPhaseSpawnCompleted { get; private set; } = true;
|
|
public bool IsPhaseRunning { get; private set; }
|
|
|
|
public void Reset()
|
|
{
|
|
EndPhase();
|
|
}
|
|
|
|
public void BeginPhase(IReadOnlyList<DRLevelSpawnEntry> spawnEntries)
|
|
{
|
|
EndPhase();
|
|
|
|
_phaseElapsed = 0f;
|
|
IsPhaseRunning = true;
|
|
IsPhaseSpawnCompleted = false;
|
|
if (spawnEntries != null)
|
|
{
|
|
for (int i = 0; i < spawnEntries.Count; i++)
|
|
{
|
|
SpawnEntryRuntime runtime = BuildSpawnRuntime(spawnEntries[i]);
|
|
if (runtime != null)
|
|
{
|
|
_spawnRuntimes.Add(runtime);
|
|
}
|
|
}
|
|
}
|
|
|
|
IsPhaseSpawnCompleted = _spawnRuntimes.Count <= 0;
|
|
}
|
|
|
|
public void OnUpdate(float elapseSeconds, Action<DRLevelSpawnEntry, int> spawnAction)
|
|
{
|
|
if (!IsPhaseRunning || spawnAction == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_phaseElapsed += elapseSeconds;
|
|
UpdateSpawnRuntimes(spawnAction);
|
|
}
|
|
|
|
public void EndPhase()
|
|
{
|
|
IsPhaseRunning = false;
|
|
_phaseElapsed = 0f;
|
|
_spawnRuntimes.Clear();
|
|
IsPhaseSpawnCompleted = true;
|
|
}
|
|
|
|
private SpawnEntryRuntime BuildSpawnRuntime(DRLevelSpawnEntry entry)
|
|
{
|
|
if (entry == null || entry.EntryType == EntryType.None)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
SpawnEntryRuntime runtime = new SpawnEntryRuntime
|
|
{
|
|
Entry = entry,
|
|
Completed = false,
|
|
NextTriggerTime = Mathf.Max(0f, entry.StartTime),
|
|
EndTime = Mathf.Max(0f, entry.StartTime),
|
|
RemainingCount = Mathf.Max(0, entry.Count)
|
|
};
|
|
|
|
switch (entry.EntryType)
|
|
{
|
|
case EntryType.Stream:
|
|
{
|
|
float duration = Mathf.Max(0f, entry.Duration);
|
|
runtime.EndTime = duration > 0f ? runtime.NextTriggerTime + duration : runtime.NextTriggerTime;
|
|
runtime.Completed = entry.Count <= 0;
|
|
return runtime;
|
|
}
|
|
case EntryType.Burst:
|
|
case EntryType.Boss:
|
|
runtime.Completed = runtime.RemainingCount <= 0;
|
|
return runtime;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void UpdateSpawnRuntimes(Action<DRLevelSpawnEntry, int> spawnAction)
|
|
{
|
|
bool allCompleted = true;
|
|
for (int i = 0; i < _spawnRuntimes.Count; i++)
|
|
{
|
|
SpawnEntryRuntime runtime = _spawnRuntimes[i];
|
|
if (runtime.Completed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (runtime.Entry.EntryType)
|
|
{
|
|
case EntryType.Stream:
|
|
ProcessStreamRuntime(runtime, spawnAction);
|
|
break;
|
|
case EntryType.Burst:
|
|
case EntryType.Boss:
|
|
ProcessBurstRuntime(runtime, spawnAction);
|
|
break;
|
|
default:
|
|
runtime.Completed = true;
|
|
break;
|
|
}
|
|
|
|
if (!runtime.Completed)
|
|
{
|
|
allCompleted = false;
|
|
}
|
|
}
|
|
|
|
IsPhaseSpawnCompleted = allCompleted;
|
|
}
|
|
|
|
private void ProcessStreamRuntime(SpawnEntryRuntime runtime, Action<DRLevelSpawnEntry, int> spawnAction)
|
|
{
|
|
if (_phaseElapsed < runtime.NextTriggerTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int countPerWave = Mathf.Max(0, runtime.Entry.Count);
|
|
if (countPerWave <= 0)
|
|
{
|
|
runtime.Completed = true;
|
|
return;
|
|
}
|
|
|
|
float interval = runtime.Entry.Interval > 0f ? runtime.Entry.Interval : MinStreamInterval;
|
|
while (_phaseElapsed >= runtime.NextTriggerTime && runtime.NextTriggerTime <= runtime.EndTime)
|
|
{
|
|
spawnAction(runtime.Entry, countPerWave);
|
|
runtime.NextTriggerTime += interval;
|
|
}
|
|
|
|
if (runtime.NextTriggerTime > runtime.EndTime)
|
|
{
|
|
runtime.Completed = true;
|
|
}
|
|
}
|
|
|
|
private void ProcessBurstRuntime(SpawnEntryRuntime runtime, Action<DRLevelSpawnEntry, int> spawnAction)
|
|
{
|
|
if (_phaseElapsed < runtime.NextTriggerTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (runtime.RemainingCount <= 0)
|
|
{
|
|
runtime.Completed = true;
|
|
return;
|
|
}
|
|
|
|
float gap = runtime.Entry.Gap;
|
|
if (gap <= 0f)
|
|
{
|
|
spawnAction(runtime.Entry, runtime.RemainingCount);
|
|
runtime.RemainingCount = 0;
|
|
runtime.Completed = true;
|
|
return;
|
|
}
|
|
|
|
gap = Mathf.Max(gap, MinBurstGap);
|
|
while (_phaseElapsed >= runtime.NextTriggerTime && runtime.RemainingCount > 0)
|
|
{
|
|
spawnAction(runtime.Entry, 1);
|
|
runtime.RemainingCount--;
|
|
runtime.NextTriggerTime += gap;
|
|
}
|
|
|
|
runtime.Completed = runtime.RemainingCount <= 0;
|
|
}
|
|
}
|
|
}
|