using TMPro; using UnityEngine; /// /// Binds the authored MatchHudCanvas widgets to live match state (sub-phase + countdowns, role /// counts, hunter crosshair); creates nothing at runtime. Passive overlay: the canvas has no /// and every graphic is raycastTarget=false, and its /// sortingOrder (90) sits below PaintHud's (100) so tool panels always win. There is no change event for /// the phase/timer, so values are polled per frame; the visible strings are only rebuilt when the /// displayed second / phase actually changes. /// [DisallowMultipleComponent] public class MatchHud : MonoBehaviour { [Tooltip("HUD canvas, toggled with the match. Empty = the Canvas on this GameObject.")] [SerializeField] private Canvas _canvas; [Tooltip("Sub-phase label in the top bar.")] [SerializeField] private TMP_Text _phaseText; [Tooltip("Countdown label in the top bar.")] [SerializeField] private TMP_Text _timeText; [Tooltip("Seconds until the next radar ping sweep; counts down from the interval and repeats. Shown only " + "during the seek phase (blank otherwise).")] [SerializeField] private TMP_Text _pingTimeText; [SerializeField] private TMP_Text _hunterCount; [SerializeField] private TMP_Text _hiderCount; [Tooltip("Crosshair, shown only for the hunter during the seek phase.")] [SerializeField] private GameObject _crosshair; private int _lastSecond = -1; private int _lastPingSecond = -1; private int _lastHunters = -1; private int _lastHiders = -1; private MatchManager.MatchSubPhase _lastPhase = (MatchManager.MatchSubPhase)(-1); private void Awake() { if (!_canvas) _canvas = GetComponent(); if (!_canvas || !_phaseText || !_timeText || !_pingTimeText || !_crosshair || !_hunterCount || !_hiderCount) { Debug.LogError("[MatchHud] Unwired reference(s) - assign Canvas/Phase/Time/PingTime/Crosshair/HunterCount/HiderCount in the Inspector.", this); enabled = false; return; } // Toggling the Canvas component (not the GameObject) keeps this binder's Update alive while hidden. _canvas.enabled = false; // shown once a match is active } private void Update() { var match = MatchManager.Instance; bool active = match && match.MatchActive; if (_canvas.enabled != active) _canvas.enabled = active; if (!active) return; var phase = match.LocalSubPhase; if (phase != _lastPhase) { _lastPhase = phase; _phaseText.text = PhaseLabel(phase); } int s = Mathf.CeilToInt(Mathf.Max(0f, match.LocalPhaseRemaining)); if (s != _lastSecond) { _lastSecond = s; _timeText.text = $"{s / 60:0}:{s % 60:00}"; } // Next-radar-sweep countdown: shown for everyone during Seek (pings only fire then), 0:00 otherwise. bool showPing = phase == MatchManager.MatchSubPhase.Seek && match.RadarPingInterval > 0f; int p = showPing ? Mathf.CeilToInt(Mathf.Max(0f, match.LocalNextPingIn)) : 0; if (p != _lastPingSecond) { _lastPingSecond = p; _pingTimeText.text = $"{p / 60:0}:{p % 60:00}"; } NetworkPlayerSetup.CountRoles(out int hunters, out int hiders); if (hunters != _lastHunters) { _lastHunters = hunters; _hunterCount.text = hunters.ToString(); } if (hiders != _lastHiders) { _lastHiders = hiders; _hiderCount.text = hiders.ToString(); } bool showCross = phase == MatchManager.MatchSubPhase.Seek && NetworkPlayerSetup.LocalViewerIsHunter(); if (_crosshair.activeSelf != showCross) _crosshair.SetActive(showCross); } private static string PhaseLabel(MatchManager.MatchSubPhase phase) { switch (phase) { case MatchManager.MatchSubPhase.Loading: return "Loading"; case MatchManager.MatchSubPhase.Hide: return "Hide"; case MatchManager.MatchSubPhase.Seek: return "Seek"; case MatchManager.MatchSubPhase.Resolution: return "Resolution"; default: return ""; } } }