UnityTowerDefense/Assets/_Project/Levels/LeakExitVolume.cs

131 lines
No EOL
5.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using TD.Core;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TD.Levels
{
/// <summary>
/// Authoring volume marking the boundary where enemies leak from one player's zone into
/// another's. Leak exit tiles are <see cref="PlacementState.Restricted"/> in the baked grid
/// but remain walkable.
/// </summary>
/// <remarks>
/// Leak topology is recorded as metadata in the baked <see cref="LevelData"/> for lobby UI
/// and future wave-balancing systems, but the runtime gameplay loop does not depend on it —
/// enemies pathfind on the unified walkability grid and naturally cross zone boundaries.
///
/// Final defenders (the player whose zone is goal-adjacent) do NOT have a LeakExitVolume.
/// They are identified by zone-to-goal adjacency at bake time.
///
/// Multiple leak exits with the same source zone may exist (e.g., Player 5 in the 9-player
/// map splits leaks 50/50 between Player 4 and Player 6). The <see cref="weight"/> field
/// controls the split. The bake normalizes weights to sum to 1.0 across a source zone's
/// leak exits.
/// </remarks>
public class LeakExitVolume : VolumeBase
{
[Tooltip("Which player's zone this leak exits FROM.")]
public PlayerSlot sourceZone = PlayerSlot.Player1;
[Tooltip("Which player's zone this leak feeds INTO.")]
public PlayerSlot target = PlayerSlot.Player2;
[Tooltip("Relative weight for split exits. Bake normalizes weights to sum to 1.0 across all " +
"leak exits sharing the same source zone. Set both leaks' weights to 1.0 for a 50/50 split.")]
public float weight = 1.0f;
[Tooltip("Whether tiles in this volume are buildable. Defaults to Invalid.")]
public PlacementValidity placementValidity = PlacementValidity.Invalid;
// Leak exits draw above player zones but below spawners.
private const float FillYLevel = 0.04f;
private const float ArrowYLevel = 0.05f;
private const float ArrowLength = 1.5f;
protected override bool GetAlwaysShowToggle(LevelAuthoring authoring)
{
return authoring.alwaysShowLeakExits;
}
private void OnDrawGizmosSelected()
{
DrawGizmosCore();
}
private void OnDrawGizmos()
{
if (ShouldDrawAlwaysOn())
{
DrawGizmosCore();
}
}
private void DrawGizmosCore()
{
// Leak exit fill uses the SOURCE zone's color (visually attaches the leak to the zone
// it leaves). The target is conveyed by the arrow direction and label.
Color baseColor = PlayerColors.Get(sourceZone);
DrawTileCoverageFill(baseColor, alpha: 0.40f, yLevel: FillYLevel);
DrawRectangularOutline(baseColor, yLevel: FillYLevel);
// Arrow points from this leak exit's center toward the target zone's center.
var col = Collider;
if (col != null)
{
Vector3 origin = new Vector3(col.bounds.center.x, ArrowYLevel, col.bounds.center.z);
Vector3 targetCenter = FindTargetZoneCenter();
if (targetCenter.sqrMagnitude > 0f) // sentinel: zero means "not found"
{
Vector3 directionXZ = new Vector3(targetCenter.x - origin.x, 0f, targetCenter.z - origin.z);
DrawArrow(origin, directionXZ, ArrowLength, baseColor);
}
}
#if UNITY_EDITOR
if (col != null)
{
Vector3 labelPos = new Vector3(col.bounds.center.x, ArrowYLevel + 0.1f, col.bounds.center.z);
Handles.Label(labelPos, $"→ {FormatPlayerName(target)}");
}
#endif
}
/// <summary>
/// Computes the world-space center of the target zone by averaging the bounds-centers
/// of all PlayerZoneVolumes whose <see cref="PlayerZoneVolume.owner"/> matches
/// <see cref="target"/>. Returns <c>Vector3.zero</c> if no matching zone is found —
/// the caller treats zero as a "not found" sentinel.
/// </summary>
/// <remarks>
/// Performs a scene-wide query each gizmo draw. This is fine for our worst case
/// (9-player map: 8 leak exits × ~10 zone volumes = ~80 considerations per frame, only
/// when gizmos are drawing). If this ever shows up as an editor-perf issue, switch to a
/// cached lookup invalidated on hierarchy change.
/// </remarks>
private Vector3 FindTargetZoneCenter()
{
var zones = Object.FindObjectsByType<PlayerZoneVolume>(FindObjectsInactive.Exclude);
if (zones == null || zones.Length == 0) return Vector3.zero;
Vector3 accumulated = Vector3.zero;
int count = 0;
for (int i = 0; i < zones.Length; i++)
{
if (zones[i] == null) continue;
if (zones[i].owner != target) continue;
var c = zones[i].GetComponent<BoxCollider>();
if (c == null) continue;
accumulated += c.bounds.center;
count++;
}
if (count == 0) return Vector3.zero;
return accumulated / count;
}
}
}