geometry-tower-defense/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager.cs

466 lines
14 KiB
C#

using System.Collections.Generic;
using GameFramework.DataTable;
using GameFramework.Event;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using GeometryTD.Entity;
using GeometryTD.Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public class EnemyManager
{
private sealed class SpawnEntryRuntime
{
public DRLevelSpawnEntry Entry;
public bool Completed;
public float NextTriggerTime;
public float EndTime;
public int RemainingCount;
}
private const int DefaultEnemyConfigId = 1;
private const float MinStreamInterval = 0.05f;
private const float MinBurstGap = 0.01f;
private readonly List<Spawner> _spawners = new List<Spawner>();
private readonly Dictionary<int, Spawner> _spawnerByOrder = new Dictionary<int, Spawner>();
private readonly List<Vector3> _pathBuffer = new List<Vector3>();
private readonly List<SpawnEntryRuntime> _spawnRuntimes = new List<SpawnEntryRuntime>();
private CombatScheduler _combatScheduler;
private EntityComponent _entity;
private IDataTable<DREnemy> _drEnemy;
private int _spawnEnemyMaxCount = 5000;
private int _currentEnemyCount;
private int _nextSpawnerIndex;
private int _currentMapEntityId;
private bool _initialized;
private bool _enemyConfigMissingLogged;
private float _phaseElapsed;
private bool _isPhaseRunning;
public int AliveEnemyCount => _currentEnemyCount;
public bool IsPhaseSpawnCompleted { get; private set; } = true;
public bool IsPhaseRunning => _isPhaseRunning;
public void OnInit(CombatScheduler combatScheduler)
{
_combatScheduler = combatScheduler;
if (_initialized)
{
return;
}
_entity = GameEntry.Entity;
_drEnemy = GameEntry.DataTable.GetDataTable<DREnemy>();
_currentEnemyCount = 0;
_nextSpawnerIndex = 0;
_currentMapEntityId = 0;
_enemyConfigMissingLogged = false;
_spawners.Clear();
_spawnerByOrder.Clear();
_pathBuffer.Clear();
_spawnRuntimes.Clear();
_phaseElapsed = 0f;
_isPhaseRunning = false;
IsPhaseSpawnCompleted = true;
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_initialized = true;
}
public void BeginPhase(DRLevelPhase phase, IReadOnlyList<DRLevelSpawnEntry> spawnEntries)
{
if (!_initialized || _combatScheduler == null)
{
return;
}
_ = phase;
EndPhase();
_phaseElapsed = 0f;
_isPhaseRunning = true;
IsPhaseSpawnCompleted = false;
RefreshSpawnerCache(true);
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, float realElapseSeconds)
{
if (!_initialized || _combatScheduler == null || !_isPhaseRunning)
{
return;
}
RefreshSpawnerCache(false);
_phaseElapsed += elapseSeconds;
UpdateSpawnRuntimes();
}
public void EndPhase()
{
_isPhaseRunning = false;
_phaseElapsed = 0f;
_spawnRuntimes.Clear();
IsPhaseSpawnCompleted = true;
}
public void OnDestroy()
{
if (!_initialized)
{
_combatScheduler = null;
return;
}
EndPhase();
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_spawners.Clear();
_spawnerByOrder.Clear();
_pathBuffer.Clear();
_currentMapEntityId = 0;
_nextSpawnerIndex = 0;
_combatScheduler = null;
_initialized = false;
}
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()
{
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);
break;
case EntryType.Burst:
case EntryType.Boss:
ProcessBurstRuntime(runtime);
break;
default:
runtime.Completed = true;
break;
}
if (!runtime.Completed)
{
allCompleted = false;
}
}
IsPhaseSpawnCompleted = allCompleted;
}
private void ProcessStreamRuntime(SpawnEntryRuntime runtime)
{
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)
{
SpawnEnemies(runtime.Entry, countPerWave);
runtime.NextTriggerTime += interval;
}
if (runtime.NextTriggerTime > runtime.EndTime)
{
runtime.Completed = true;
}
}
private void ProcessBurstRuntime(SpawnEntryRuntime runtime)
{
if (_phaseElapsed < runtime.NextTriggerTime)
{
return;
}
if (runtime.RemainingCount <= 0)
{
runtime.Completed = true;
return;
}
float gap = runtime.Entry.Gap;
if (gap <= 0f)
{
SpawnEnemies(runtime.Entry, runtime.RemainingCount);
runtime.RemainingCount = 0;
runtime.Completed = true;
return;
}
gap = Mathf.Max(gap, MinBurstGap);
while (_phaseElapsed >= runtime.NextTriggerTime && runtime.RemainingCount > 0)
{
SpawnEnemies(runtime.Entry, 1);
runtime.RemainingCount--;
runtime.NextTriggerTime += gap;
}
runtime.Completed = runtime.RemainingCount <= 0;
}
private void SpawnEnemies(DRLevelSpawnEntry entry, int spawnCount)
{
if (spawnCount <= 0)
{
return;
}
Spawner spawner = ResolveSpawner(entry.SpawnPointId);
if (spawner == null)
{
return;
}
MapEntity currentMap = _combatScheduler != null ? _combatScheduler.CurrentMap : null;
if (currentMap == null ||
!currentMap.TryFindPathWorldPoints(spawner, null, _pathBuffer) ||
_pathBuffer.Count <= 0)
{
return;
}
DREnemy enemyConfig = GetEnemyConfig(entry.EnemyId);
if (enemyConfig == null)
{
return;
}
for (int i = 0; i < spawnCount; i++)
{
if (_currentEnemyCount >= _spawnEnemyMaxCount)
{
break;
}
int enemyEntityId = _entity.GenerateSerialId();
EnemyData enemyData = new EnemyData(
enemyEntityId,
enemyConfig.EntityId,
_pathBuffer[0],
enemyConfig.BaseHp,
enemyConfig.Speed,
_pathBuffer);
_entity.ShowEnemy(enemyData);
}
}
private DREnemy GetEnemyConfig(int enemyId)
{
if (_drEnemy == null)
{
_drEnemy = GameEntry.DataTable.GetDataTable<DREnemy>();
if (_drEnemy == null)
{
if (!_enemyConfigMissingLogged)
{
Log.Warning("EnemyManagerComponent can not find DREnemy data table.");
_enemyConfigMissingLogged = true;
}
return null;
}
}
if (enemyId > 0)
{
DREnemy targetConfig = _drEnemy.GetDataRow(enemyId);
if (targetConfig != null)
{
return targetConfig;
}
}
DREnemy defaultConfig = _drEnemy.GetDataRow(DefaultEnemyConfigId);
if (defaultConfig != null)
{
return defaultConfig;
}
DREnemy[] allConfigs = _drEnemy.GetAllDataRows();
if (allConfigs.Length > 0)
{
return allConfigs[0];
}
if (!_enemyConfigMissingLogged)
{
Log.Warning("EnemyManagerComponent found no enemy configs.");
_enemyConfigMissingLogged = true;
}
return null;
}
private Spawner ResolveSpawner(int spawnPointId)
{
if (spawnPointId > 0 && _spawnerByOrder.TryGetValue(spawnPointId, out Spawner mappedSpawner))
{
return mappedSpawner;
}
if (_spawners.Count <= 0)
{
return null;
}
Spawner fallbackSpawner = _spawners[_nextSpawnerIndex % _spawners.Count];
_nextSpawnerIndex++;
return fallbackSpawner;
}
private void RefreshSpawnerCache(bool force)
{
MapEntity currentMap = _combatScheduler != null ? _combatScheduler.CurrentMap : null;
if (currentMap == null)
{
_spawners.Clear();
_spawnerByOrder.Clear();
_currentMapEntityId = 0;
_nextSpawnerIndex = 0;
return;
}
if (!force && _currentMapEntityId == currentMap.Id && _spawners.Count > 0)
{
return;
}
_spawners.Clear();
_spawnerByOrder.Clear();
_nextSpawnerIndex = 0;
_currentMapEntityId = currentMap.Id;
Spawner[] mapSpawners = currentMap.Spawners;
for (int i = 0; i < mapSpawners.Length; i++)
{
Spawner spawner = mapSpawners[i];
if (spawner == null)
{
continue;
}
if (!currentMap.TryGetDefaultPathCells(spawner, out _))
{
continue;
}
_spawners.Add(spawner);
if (spawner.SpawnOrder > 0 && !_spawnerByOrder.ContainsKey(spawner.SpawnOrder))
{
_spawnerByOrder[spawner.SpawnOrder] = spawner;
}
}
_spawners.Sort((left, right) => left.SpawnOrder.CompareTo(right.SpawnOrder));
}
private void OnShowEntitySuccess(object sender, GameEventArgs e)
{
if (e is ShowEntitySuccessEventArgs ne)
{
if (ne.EntityLogicType == typeof(EnemyEntity))
{
_currentEnemyCount++;
}
}
}
private void OnShowEntityFailure(object sender, GameEventArgs e)
{
}
private void OnHideEntityComplete(object sender, GameEventArgs e)
{
if (!(e is HideEntityCompleteEventArgs ne))
{
return;
}
if (ne.EntityGroup.Name != "Enemy")
{
return;
}
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
}
}
}