diff --git a/Assets/_Project/Scripts/Gameplay/WaveDefinition.cs b/Assets/_Project/Scripts/Gameplay/WaveDefinition.cs index 5b62aef..3aae46e 100644 --- a/Assets/_Project/Scripts/Gameplay/WaveDefinition.cs +++ b/Assets/_Project/Scripts/Gameplay/WaveDefinition.cs @@ -25,10 +25,6 @@ namespace TD.Gameplay "groups rather than between individual enemies.")] public float SpawnInterval; - [Tooltip("Max X-axis world-unit offset applied randomly to each enemy's spawn position. " + - "0 = all spawn at the exact tile centre. Spreads enemies left/right without " + - "affecting their forward/backward position or A* pathfinding.")] - public float SpawnSpread; } /// diff --git a/Assets/_Project/Scripts/Gameplay/WaveManager.cs b/Assets/_Project/Scripts/Gameplay/WaveManager.cs index d39e908..4ca920f 100644 --- a/Assets/_Project/Scripts/Gameplay/WaveManager.cs +++ b/Assets/_Project/Scripts/Gameplay/WaveManager.cs @@ -341,7 +341,7 @@ namespace TD.Gameplay { int burst = Mathf.Min(chunkSize, entry.Count - i); for (int j = 0; j < burst; j++) - SpawnEnemyInAllZones(entry.EnemyType, entry.SpawnSpread); + SpawnEnemyInAllZones(entry.EnemyType); if (entry.SpawnInterval > 0f) yield return new WaitForSeconds(entry.SpawnInterval); @@ -358,7 +358,7 @@ namespace TD.Gameplay // ----- Spawn helpers ---------------------------------------------- - private void SpawnEnemyInAllZones(EnemyDefinition def, float spawnSpread = 0f) + private void SpawnEnemyInAllZones(EnemyDefinition def) { var loader = LevelLoader.Instance; if (loader?.LevelData?.PlayerZones == null) return; @@ -379,12 +379,35 @@ namespace TD.Gameplay // this enemy for leak attribution — can't be derived from the spawner // tile's owner-grid entry because SpawnerVolume sits outside // PlayerZoneVolume (so OwnerGrid[spawnerTile] = None). - SpawnEnemy(def, zone.Spawners[0].TilePosition, zone.Owner, spawnSpread); + var spawner = zone.Spawners[0]; + (float xHalf, float zHalf) = ComputeSpawnerHalfExtents(spawner.TileArea); + SpawnEnemy(def, spawner.TilePosition, zone.Owner, xHalf, zHalf); } } + // Computes half-extents in world units from a spawner's tile footprint. + // X maps to world X; grid-Y maps to world Z (the grid lives on the XZ plane). + private static (float x, float z) ComputeSpawnerHalfExtents(Vector2Int[] tileArea) + { + if (tileArea == null || tileArea.Length == 0) return (0f, 0f); + + int minX = int.MaxValue, maxX = int.MinValue; + int minY = int.MaxValue, maxY = int.MinValue; + foreach (var tile in tileArea) + { + if (tile.x < minX) minX = tile.x; + if (tile.x > maxX) maxX = tile.x; + if (tile.y < minY) minY = tile.y; + if (tile.y > maxY) maxY = tile.y; + } + + float xHalf = (maxX - minX + 1) * GridCoordinates.TILE_SIZE * 0.5f; + float zHalf = (maxY - minY + 1) * GridCoordinates.TILE_SIZE * 0.5f; + return (xHalf, zHalf); + } + private void SpawnEnemy(EnemyDefinition def, Vector2Int spawnerTile, PlayerSlot ownerSlot, - float spawnSpread = 0f) + float xHalfExtent = 0f, float zHalfExtent = 0f) { if (def.EnemyPrefab == null) { @@ -393,8 +416,10 @@ namespace TD.Gameplay } Vector3 spawnPos = GridCoordinates.GridToWorld(spawnerTile); - if (spawnSpread > 0f) - spawnPos.x += Random.Range(-spawnSpread, spawnSpread); + if (xHalfExtent > 0f) + spawnPos.x += Random.Range(-xHalfExtent, xHalfExtent); + if (zHalfExtent > 0f) + spawnPos.z += Random.Range(-zHalfExtent, zHalfExtent); var go = Instantiate(def.EnemyPrefab, spawnPos, Quaternion.identity);