enemies spawn held in place

This commit is contained in:
Ian Woods 2026-06-23 22:21:32 -07:00
parent 54f11d91ff
commit ff921080db
4 changed files with 75 additions and 26 deletions

View file

@ -100,6 +100,9 @@ namespace TD.Gameplay
private int currentWaveIndex = -1; // -1 = not yet started
private Coroutine activeWaveCoroutine;
private readonly System.Collections.Generic.List<EnemyHealth> heldEnemies
= new System.Collections.Generic.List<EnemyHealth>();
// ----- NGO lifecycle ----------------------------------------------
public override void OnNetworkSpawn()
@ -294,6 +297,7 @@ namespace TD.Gameplay
// skipping the prep timer so spawning starts immediately.
activeEnemyCount = 0;
spawningComplete = false;
heldEnemies.Clear();
StartNextWave(skipPrep: true);
}
@ -329,26 +333,35 @@ namespace TD.Gameplay
// whether prep was skipped or just expired.
prepCountdown.Value = 0f;
// Spawn phase.
// Spawn all enemies at once in a held (untargetable, immobile) state.
if (def.Entries != null)
{
foreach (var entry in def.Entries)
{
if (entry.EnemyType == null || entry.Count <= 0) continue;
int chunkSize = Mathf.Max(1, Mathf.RoundToInt(entry.Count * 0.1f));
for (int i = 0; i < entry.Count; i += chunkSize)
{
int burst = Mathf.Min(chunkSize, entry.Count - i);
for (int j = 0; j < burst; j++)
SpawnEnemyInAllZones(entry.EnemyType);
if (entry.SpawnInterval > 0f)
yield return new WaitForSeconds(entry.SpawnInterval);
}
for (int i = 0; i < entry.Count; i++)
SpawnEnemyInAllZones(entry.EnemyType, held: true);
}
}
// Release in chunks of 10% of the total held count.
int total = heldEnemies.Count;
int chunkSize = Mathf.Max(1, Mathf.RoundToInt(total * 0.1f));
while (heldEnemies.Count > 0)
{
int burst = Mathf.Min(chunkSize, heldEnemies.Count);
for (int i = 0; i < burst; i++)
{
var h = heldEnemies[0];
heldEnemies.RemoveAt(0);
if (h != null && !h.IsDead)
h.SetHeld(false);
}
if (heldEnemies.Count > 0)
yield return new WaitForSeconds(def.ReleaseInterval);
}
spawningComplete = true;
// If every spawned enemy was already resolved before this coroutine finished
@ -358,7 +371,7 @@ namespace TD.Gameplay
// ----- Spawn helpers ----------------------------------------------
private void SpawnEnemyInAllZones(EnemyDefinition def)
private void SpawnEnemyInAllZones(EnemyDefinition def, bool held = false)
{
var loader = LevelLoader.Instance;
if (loader?.LevelData?.PlayerZones == null) return;
@ -381,7 +394,7 @@ namespace TD.Gameplay
// PlayerZoneVolume (so OwnerGrid[spawnerTile] = None).
var spawner = zone.Spawners[0];
(float xHalf, float zHalf) = ComputeSpawnerHalfExtents(spawner.TileArea);
SpawnEnemy(def, spawner.TilePosition, zone.Owner, xHalf, zHalf);
SpawnEnemy(def, spawner.TilePosition, zone.Owner, xHalf, zHalf, held);
}
}
@ -407,7 +420,7 @@ namespace TD.Gameplay
}
private void SpawnEnemy(EnemyDefinition def, Vector2Int spawnerTile, PlayerSlot ownerSlot,
float xHalfExtent = 0f, float zHalfExtent = 0f)
float xHalfExtent = 0f, float zHalfExtent = 0f, bool held = false)
{
if (def.EnemyPrefab == null)
{
@ -434,8 +447,9 @@ namespace TD.Gameplay
return;
}
health.InitializeServer(def.MaxHp, def.LivesCost, def.IsFlying);
health.InitializeServer(def.MaxHp, def.LivesCost, def.IsFlying, held);
movement.InitializeServer(def.MoveSpeed, spawnerTile, ownerSlot);
if (held) heldEnemies.Add(health);
health.OnDied += HandleEnemyKilled;
movement.OnZoneLeaked += HandleZoneLeak;