Adding major combat changes and features
This commit is contained in:
parent
abcefcd7f1
commit
42ee0bf65d
28 changed files with 1653 additions and 46 deletions
135
Assets/_Project/Scripts/Combat/TowerRangeIndicator.cs
Normal file
135
Assets/_Project/Scripts/Combat/TowerRangeIndicator.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// Assets/_Project/Scripts/Combat/TowerRangeIndicator.cs
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using TD.Gameplay;
|
||||
|
||||
namespace TD.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays a translucent decal circle representing this tower's attack range
|
||||
/// when the local player selects the tower.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Visual only.</b> No networking — selection is a local UI concept.
|
||||
/// All clients independently show the range indicator for whatever tower
|
||||
/// they have selected.
|
||||
///
|
||||
/// <b>Prefab setup:</b>
|
||||
/// <list type="number">
|
||||
/// <item>Add this component to the tower prefab root (alongside TowerInstance).</item>
|
||||
/// <item>Add a child GameObject named "RangeIndicator".</item>
|
||||
/// <item>Add a <c>DecalProjector</c> to that child and assign it to
|
||||
/// <see cref="rangeProjector"/> (or leave it unassigned — auto-found
|
||||
/// via <c>GetComponentInChildren</c> in Start).</item>
|
||||
/// <item>Assign a translucent range-circle material to the DecalProjector.</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <b>Sizing:</b> The projector diameter is set once in <c>Start</c> from
|
||||
/// <c>TowerDefinition.Range</c>. Towers are static, so no per-frame resize is needed.
|
||||
///
|
||||
/// <b>Subscription timing:</b> Follows the same deferred-subscribe pattern as
|
||||
/// <see cref="SelectionVisualizer"/> — retries until <see cref="SelectionState"/>
|
||||
/// is available, then stops polling.
|
||||
/// </remarks>
|
||||
public class TowerRangeIndicator : MonoBehaviour
|
||||
{
|
||||
[Tooltip("DecalProjector used to render the range circle on the ground. " +
|
||||
"Auto-found via GetComponentInChildren if left empty.")]
|
||||
[SerializeField] private DecalProjector rangeProjector;
|
||||
|
||||
[Tooltip("Vertical extent of the decal projection volume in world units. " +
|
||||
"Must be tall enough to project onto terrain at any height in your map.")]
|
||||
[SerializeField] private float projectionDepth = 50f;
|
||||
|
||||
private TowerInstance towerInstance;
|
||||
private bool subscribed;
|
||||
|
||||
// ----- Lifecycle ---------------------------------------------------
|
||||
|
||||
private void Start()
|
||||
{
|
||||
towerInstance = GetComponent<TowerInstance>();
|
||||
if (towerInstance == null)
|
||||
{
|
||||
Debug.LogError("[TowerRangeIndicator] No TowerInstance found on this " +
|
||||
"GameObject. TowerRangeIndicator must sit on the same " +
|
||||
"prefab root as TowerInstance.");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rangeProjector == null)
|
||||
rangeProjector = GetComponentInChildren<DecalProjector>();
|
||||
if (rangeProjector == null)
|
||||
{
|
||||
Debug.LogError("[TowerRangeIndicator] No DecalProjector found. " +
|
||||
"Add one as a child of this GameObject and assign it " +
|
||||
"to the rangeProjector field.");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// TowerInstance resolves its Definition in OnNetworkSpawn, which runs
|
||||
// before Start, so Definition is guaranteed available here.
|
||||
float range = towerInstance.Definition != null ? towerInstance.Definition.Range : 0f;
|
||||
float diameter = range * 2f;
|
||||
|
||||
rangeProjector.size = new Vector3(diameter, diameter, projectionDepth);
|
||||
rangeProjector.pivot = Vector3.zero;
|
||||
|
||||
// DecalProjector projects along its local +Z axis; rotate 90° around X
|
||||
// to project downward onto the ground plane.
|
||||
rangeProjector.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
|
||||
rangeProjector.transform.localPosition = Vector3.zero;
|
||||
|
||||
rangeProjector.enabled = false;
|
||||
|
||||
TrySubscribe();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Unsubscribe();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Retry subscription each frame until SelectionState is ready.
|
||||
// Once subscribed the branch short-circuits immediately.
|
||||
if (!subscribed) TrySubscribe();
|
||||
}
|
||||
|
||||
// ----- Subscription -----------------------------------------------
|
||||
|
||||
private void TrySubscribe()
|
||||
{
|
||||
if (subscribed) return;
|
||||
var sel = SelectionState.Instance;
|
||||
if (sel == null) return;
|
||||
|
||||
sel.OnSelectionChanged += HandleSelectionChanged;
|
||||
subscribed = true;
|
||||
|
||||
// Catch whatever is already selected before we subscribed.
|
||||
HandleSelectionChanged(sel.SelectedObject);
|
||||
}
|
||||
|
||||
private void Unsubscribe()
|
||||
{
|
||||
if (!subscribed) return;
|
||||
var sel = SelectionState.Instance;
|
||||
if (sel != null) sel.OnSelectionChanged -= HandleSelectionChanged;
|
||||
subscribed = false;
|
||||
}
|
||||
|
||||
// ----- Selection handler ------------------------------------------
|
||||
|
||||
private void HandleSelectionChanged(ISelectable newSelection)
|
||||
{
|
||||
if (rangeProjector == null) return;
|
||||
|
||||
// Show only when THIS tower's TowerInstance is the selected object.
|
||||
rangeProjector.enabled = (object)newSelection == (object)towerInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue