118 lines
No EOL
4.8 KiB
C#
118 lines
No EOL
4.8 KiB
C#
using UnityEngine;
|
|
using TD.Core;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace TD.Levels
|
|
{
|
|
/// <summary>
|
|
/// Authoring volume marking where enemies spawn into a player's zone. Spawner tiles are
|
|
/// <see cref="PlacementState.Restricted"/> in the baked grid (no tower placement allowed)
|
|
/// but remain walkable so enemies can leave the spawner.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// A zone may have multiple spawners; <see cref="spawnerIdInZone"/> disambiguates them.
|
|
/// Player 5 in the 9-player Wintermaul map is the canonical multi-spawner zone.
|
|
///
|
|
/// The spawner declares an owning player via <see cref="owner"/> rather than relying on
|
|
/// spatial containment within a PlayerZoneVolume. The bake validates (with a soft warning)
|
|
/// that the spawner's tiles fall inside its declared owner's zone.
|
|
/// </remarks>
|
|
public class SpawnerVolume : VolumeBase
|
|
{
|
|
[Tooltip("Which player's zone owns this spawner. Explicit — not inferred from spatial overlap.")]
|
|
public PlayerSlot owner = PlayerSlot.Player1;
|
|
|
|
[Tooltip("Disambiguator for zones with multiple spawners. Should be 0 for single-spawner zones, " +
|
|
"and contiguous starting from 0 for multi-spawner zones (e.g., 0 and 1).")]
|
|
public int spawnerIdInZone = 0;
|
|
|
|
[Tooltip("Direction enemies face when they spawn. Used for visual orientation and may bias " +
|
|
"initial enemy movement direction.")]
|
|
public Direction spawnFacing = Direction.South;
|
|
|
|
[Tooltip("Whether tiles in this volume are buildable. Defaults to Invalid (no placement on spawners).")]
|
|
public PlacementValidity placementValidity = PlacementValidity.Invalid;
|
|
|
|
// Spawners draw above player zones so the player zone color reads as background.
|
|
private const float FillYLevel = 0.06f;
|
|
private const float ArrowYLevel = 0.07f;
|
|
private const float ArrowLength = 1.5f; // 1.5 tiles per gizmo design
|
|
|
|
protected override bool GetAlwaysShowToggle(LevelAuthoring authoring)
|
|
{
|
|
return authoring.alwaysShowSpawners;
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
DrawGizmosCore();
|
|
}
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
if (ShouldDrawAlwaysOn())
|
|
{
|
|
DrawGizmosCore();
|
|
}
|
|
}
|
|
|
|
private void DrawGizmosCore()
|
|
{
|
|
Color baseColor = PlayerColors.Get(owner);
|
|
DrawTileCoverageFill(baseColor, alpha: 0.40f, yLevel: FillYLevel);
|
|
DrawRectangularOutline(baseColor, yLevel: FillYLevel);
|
|
|
|
// Direction arrow originates from the volume's center and extends 1.5 tiles in the
|
|
// declared facing direction, rendered in opaque owner color.
|
|
var col = Collider;
|
|
if (col != null)
|
|
{
|
|
Vector3 arrowOrigin = new Vector3(col.bounds.center.x, ArrowYLevel, col.bounds.center.z);
|
|
Vector3 arrowDir = DirectionToWorld(spawnFacing);
|
|
DrawArrow(arrowOrigin, arrowDir, ArrowLength, baseColor);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
// Label conditional on multi-spawner status:
|
|
// - Single-spawner zone: "Player N Spawn"
|
|
// - Multi-spawner zone: "Player N Spawn 0", "Player N Spawn 1", etc.
|
|
// Multi-spawner detection scans other SpawnerVolumes in the scene with the same owner.
|
|
// Same posture as the leak-exit target lookup: cheap on realistic maps, can be cached
|
|
// if it ever shows up as an editor-perf issue.
|
|
if (col != null)
|
|
{
|
|
Vector3 labelPos = new Vector3(col.bounds.center.x, ArrowYLevel + 0.1f, col.bounds.center.z);
|
|
string labelText = ZoneHasMultipleSpawners()
|
|
? $"{FormatPlayerName(owner)} Spawn {spawnerIdInZone}"
|
|
: $"{FormatPlayerName(owner)} Spawn";
|
|
Handles.Label(labelPos, labelText);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if more than one active SpawnerVolume in the scene shares this spawner's
|
|
/// <see cref="owner"/>. Used to decide whether to suffix the gizmo label with the spawner ID.
|
|
/// </summary>
|
|
private bool ZoneHasMultipleSpawners()
|
|
{
|
|
var spawners = Object.FindObjectsByType<SpawnerVolume>(FindObjectsInactive.Exclude);
|
|
if (spawners == null) return false;
|
|
|
|
int count = 0;
|
|
for (int i = 0; i < spawners.Length; i++)
|
|
{
|
|
if (spawners[i] == null) continue;
|
|
if (spawners[i].owner == owner)
|
|
{
|
|
count++;
|
|
if (count > 1) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
} |