diff --git a/Assets/Scripts/Network/MatchManager.cs b/Assets/Scripts/Network/MatchManager.cs index 921d386..e9fd7bb 100644 --- a/Assets/Scripts/Network/MatchManager.cs +++ b/Assets/Scripts/Network/MatchManager.cs @@ -212,9 +212,6 @@ public class MatchManager : MonoBehaviour _playerPrefab = _net.NetworkConfig.PlayerPrefab; _net.NetworkConfig.PlayerPrefab = null; } - - // Placeholder on-screen match HUD, added at runtime so it needs no scene/prefab authoring. - if (!GetComponent()) gameObject.AddComponent(); } private void OnEnable() diff --git a/Assets/Scripts/Network/UI/MatchHud.cs b/Assets/Scripts/Network/UI/MatchHud.cs index 78d5d50..eb65e0d 100644 --- a/Assets/Scripts/Network/UI/MatchHud.cs +++ b/Assets/Scripts/Network/UI/MatchHud.cs @@ -1,42 +1,72 @@ +using TMPro; using UnityEngine; /// -/// Placeholder on-screen match HUD: the current sub-phase + countdown (top centre) and a hunter crosshair -/// during the seek phase. Drawn with IMGUI (OnGUI) on purpose - it needs no Canvas / font asset authoring, -/// so the match-state logic can be exercised immediately; swap it for a proper uGUI canvas once the look is -/// finalised. Added at runtime by (AddComponent), so it rides the persistent -/// manager object and reads on every machine. +/// On-screen match HUD binder: drives the authored MatchHudCanvas widgets from live match state - +/// the current sub-phase + countdown (top centre) and a hunter crosshair during the seek phase. The visual +/// layout lives in the scene; this component creates nothing at runtime, it only binds. Attach it to the +/// MatchHudCanvas and wire the references in the Inspector. +/// +/// Passive overlay: the authored canvas has no and every graphic +/// is raycastTarget=false, so it never consumes pointer events, and its sortingOrder (90) sits below PaintHud's +/// (100) so tool panels always win. There is no change event for the phase/timer, so the values are polled once +/// per frame; the visible strings are only rebuilt when the displayed second / phase actually changes. /// [DisallowMultipleComponent] public class MatchHud : MonoBehaviour { - private GUIStyle _phaseStyle; - private GUIStyle _timeStyle; - private int _lastSecond = -1; // countdown text cache: OnGUI runs 2+ times per frame, - private string _timeText = ""; // so the string is only rebuilt when the displayed second changes + [Tooltip("HUD canvas, toggled with the match. Empty = the Canvas on this GameObject.")] + [SerializeField] private Canvas _canvas; - private void OnGUI() + [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("Crosshair, shown only for the hunter during the seek phase.")] + [SerializeField] private GameObject _crosshair; + + private int _lastSecond = -1; + private MatchManager.MatchSubPhase _lastPhase = (MatchManager.MatchSubPhase)(-1); + + private void Awake() + { + if (!_canvas) _canvas = GetComponent(); + if (!_canvas || !_phaseText || !_timeText || !_crosshair) + { + Debug.LogError("[MatchHud] Unwired reference(s) - assign Canvas/Phase/Time/Crosshair 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; - if (!match || !match.MatchActive) return; + bool active = match && match.MatchActive; + if (_canvas.enabled != active) _canvas.enabled = active; + if (!active) return; - EnsureStyles(); + 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 = $"{s / 60:0}:{s % 60:00}"; + _timeText.text = $"{s / 60:0}:{s % 60:00}"; } - const float w = 360f; - var phaseRect = new Rect((Screen.width - w) * 0.5f, 12f, w, 30f); - var timeRect = new Rect(phaseRect.x, phaseRect.y + 28f, w, 36f); - DrawShadowed(phaseRect, PhaseLabel(match.LocalSubPhase), _phaseStyle); - DrawShadowed(timeRect, _timeText, _timeStyle); - - if (match.LocalSubPhase == MatchManager.MatchSubPhase.Seek && NetworkPlayerSetup.LocalViewerIsHunter()) - DrawCrosshair(); + bool showCross = phase == MatchManager.MatchSubPhase.Seek && NetworkPlayerSetup.LocalViewerIsHunter(); + if (_crosshair.activeSelf != showCross) _crosshair.SetActive(showCross); } private static string PhaseLabel(MatchManager.MatchSubPhase phase) @@ -49,45 +79,4 @@ public class MatchHud : MonoBehaviour default: return ""; } } - - private static void DrawCrosshair() - { - float cx = Screen.width * 0.5f; - float cy = Screen.height * 0.5f; - const float r = 8f, t = 2f; - Color prev = GUI.color; - GUI.color = new Color(1f, 1f, 1f, 0.85f); - GUI.DrawTexture(new Rect(cx - r, cy - t * 0.5f, r * 2f, t), Texture2D.whiteTexture); - GUI.DrawTexture(new Rect(cx - t * 0.5f, cy - r, t, r * 2f), Texture2D.whiteTexture); - GUI.color = prev; - } - - // One label drawn twice (dark offset behind, then the real colour) so it stays readable on any backdrop. - private static void DrawShadowed(Rect r, string text, GUIStyle style) - { - if (string.IsNullOrEmpty(text)) return; - Color real = style.normal.textColor; - style.normal.textColor = new Color(0f, 0f, 0f, 0.7f); - GUI.Label(new Rect(r.x + 1f, r.y + 1f, r.width, r.height), text, style); - style.normal.textColor = real; - GUI.Label(r, text, style); - } - - private void EnsureStyles() - { - if (_phaseStyle == null) _phaseStyle = MakeCenteredBoldLabel(22); - if (_timeStyle == null) _timeStyle = MakeCenteredBoldLabel(28); - } - - private static GUIStyle MakeCenteredBoldLabel(int fontSize) - { - var style = new GUIStyle(GUI.skin.label) - { - alignment = TextAnchor.MiddleCenter, - fontSize = fontSize, - fontStyle = FontStyle.Bold, - }; - style.normal.textColor = Color.white; - return style; - } }