Turn MatchHud into a uGUI canvas binder
Replace the runtime IMGUI (OnGUI) HUD with a binder that drives the authored MatchHudCanvas widgets via [SerializeField] references (Canvas / Phase / Time / Crosshair). Visibility toggles Canvas.enabled instead of the GameObject, so Update keeps polling while hidden; an Awake guard disables the component on unwired references. Drop the AddComponent<MatchHud>() bootstrap in MatchManager - the component now lives on the scene canvas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
fbbb4f7419
commit
8f9b6cf1bc
2 files changed
+51
-65
No files matched your search
@@ -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<MatchHud>()) gameObject.AddComponent<MatchHud>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
|
||||
@@ -1,42 +1,72 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="MatchManager"/> (AddComponent), so it rides the persistent
|
||||
/// manager object and reads <see cref="MatchManager.Instance"/> on every machine.
|
||||
/// On-screen match HUD binder: drives the authored <c>MatchHudCanvas</c> 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 <see cref="UnityEngine.UI.GraphicRaycaster"/> 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.
|
||||
/// </summary>
|
||||
[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<Canvas>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user