126 lines
4.8 KiB
C#
126 lines
4.8 KiB
C#
// Assets/_Project/Scripts/Gameplay/SelectionRingVisual.cs
|
|
using UnityEngine;
|
|
|
|
namespace TD.Gameplay
|
|
{
|
|
/// <summary>
|
|
/// Local-only visual indicator for builder selection. Sits as a child of the
|
|
/// builder prefab. Subscribes to <see cref="SelectionState.OnSelectionChanged"/>
|
|
/// and toggles the visibility of its own renderers (and any descendant
|
|
/// renderers) when the parent builder becomes selected/unselected.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>Pure local visualization.</b> No NetworkBehaviour. Selection is a
|
|
/// UI concept — every client renders selection state for its own player only.
|
|
/// This component has no networked state.</para>
|
|
///
|
|
/// <para><b>Why a separate component.</b> Keeps Builder focused on gameplay
|
|
/// state. The visual prefab structure can change independently
|
|
/// (disc → ring → decal projector → animated effect) without touching gameplay
|
|
/// code. The contract is just "renderers visible when selected."</para>
|
|
///
|
|
/// <para><b>Why renderer-toggle, not GameObject.SetActive.</b> Disabling our
|
|
/// own GameObject would prevent OnEnable/Update from running, which would
|
|
/// break the SelectionState subscription lifecycle (we'd never receive the
|
|
/// "you're selected again" event). Toggling the renderers' enabled state
|
|
/// achieves the same visual effect without breaking the event flow.</para>
|
|
///
|
|
/// <para><b>Prefab setup.</b> Attach this component to a child GameObject of
|
|
/// the Builder prefab. The child carries (or has descendants carrying) a
|
|
/// flattened cylinder mesh sitting just above the ground plane, with an
|
|
/// unlit transparent green material. The component handles initial visibility
|
|
/// — leave the renderer enabled in the prefab; we'll turn it off in Awake
|
|
/// until selection fires.</para>
|
|
/// </remarks>
|
|
public class SelectionRingVisual : MonoBehaviour
|
|
{
|
|
// Cached parent builder, resolved in Awake.
|
|
private Builder parentBuilder;
|
|
|
|
// Cached renderers (this object plus all descendants). Captured once in
|
|
// Awake to avoid per-event GetComponentsInChildren allocations.
|
|
private Renderer[] cachedRenderers;
|
|
|
|
// Tracks subscription state so OnDisable / OnDestroy unsubscribe correctly,
|
|
// and so Update can retry subscription if SelectionState wasn't ready at OnEnable.
|
|
private bool subscribed;
|
|
|
|
private void Awake()
|
|
{
|
|
parentBuilder = GetComponentInParent<Builder>();
|
|
if (parentBuilder == null)
|
|
{
|
|
Debug.LogError("[SelectionRingVisual] No Builder component found on " +
|
|
"self or any parent. Disabling.");
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
cachedRenderers = GetComponentsInChildren<Renderer>(includeInactive: true);
|
|
|
|
// Start hidden. If selection fires later (including the auto-select
|
|
// in Builder.OnNetworkSpawn), HandleSelectionChanged will turn the
|
|
// renderers back on.
|
|
SetRenderersVisible(false);
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
TrySubscribe();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
Unsubscribe();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
Unsubscribe();
|
|
}
|
|
|
|
// Per-frame fallback: if SelectionState wasn't available at OnEnable
|
|
// (scene load ordering), keep trying. Cost is one null-check per frame
|
|
// until subscription succeeds, then nothing.
|
|
private void Update()
|
|
{
|
|
if (!subscribed) TrySubscribe();
|
|
}
|
|
|
|
private void TrySubscribe()
|
|
{
|
|
if (subscribed) return;
|
|
var sel = SelectionState.Instance;
|
|
if (sel == null) return;
|
|
|
|
sel.OnSelectionChanged += HandleSelectionChanged;
|
|
subscribed = true;
|
|
|
|
// Sync to current state — selection may have happened before we subscribed
|
|
// (e.g., Builder.OnNetworkSpawn auto-selecting before this Awake runs).
|
|
HandleSelectionChanged(sel.SelectedBuilder);
|
|
}
|
|
|
|
private void Unsubscribe()
|
|
{
|
|
if (!subscribed) return;
|
|
var sel = SelectionState.Instance;
|
|
if (sel != null) sel.OnSelectionChanged -= HandleSelectionChanged;
|
|
subscribed = false;
|
|
}
|
|
|
|
private void HandleSelectionChanged(Builder newSelection)
|
|
{
|
|
SetRenderersVisible(newSelection == parentBuilder);
|
|
}
|
|
|
|
private void SetRenderersVisible(bool visible)
|
|
{
|
|
if (cachedRenderers == null) return;
|
|
foreach (var rend in cachedRenderers)
|
|
{
|
|
if (rend != null) rend.enabled = visible;
|
|
}
|
|
}
|
|
}
|
|
}
|