From 6434e1c4fccbcc869d371da8d1f955acb105d738 Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Fri, 3 Jul 2026 13:34:41 +0200 Subject: [PATCH] Show live hunter/hider counts in match HUD Count spawned role variants from a client-side avatar registry in NetworkPlayerSetup (network objects exist on every machine), so the counts work without server-only match state. MatchHud binds the two count texts and rewrites them only on change. Co-Authored-By: Claude Opus 4.8 --- Assets/Scripts/Network/NetworkPlayerSetup.cs | 22 ++++++++++++++++++++ Assets/Scripts/Network/UI/MatchHud.cs | 18 ++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/Network/NetworkPlayerSetup.cs b/Assets/Scripts/Network/NetworkPlayerSetup.cs index d9c305d..4550fa3 100644 --- a/Assets/Scripts/Network/NetworkPlayerSetup.cs +++ b/Assets/Scripts/Network/NetworkPlayerSetup.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Unity.Netcode; using UnityEngine; @@ -116,6 +117,7 @@ public class NetworkPlayerSetup : NetworkBehaviour public override void OnNetworkSpawn() { + _active.Add(this); // scoreboard registry (paired with OnNetworkDespawn) _bodyRenderers = GetComponentsInChildren(true); _painter = GetComponentInChildren(true); _concealment = GetComponentInChildren(true); @@ -177,6 +179,7 @@ public class NetworkPlayerSetup : NetworkBehaviour public override void OnNetworkDespawn() { + _active.Remove(this); _isFound.OnValueChanged -= OnFoundChanged; } @@ -377,6 +380,25 @@ public class NetworkPlayerSetup : NetworkBehaviour return _localViewerIsHunter; } + // Live scoreboard source. Every match avatar is a network object present on ALL machines, so counting the + // spawned role variants is client-safe - no server-only match state (e.g. _pendingHiders) is needed. + // Hunters = hunter variants; hiders = un-found hider variants (a Classic-found hider leaves the count, a + // Conversion hit despawns the hider object and spawns a hunter one, so both sides update from the registry). + private static readonly List _active = new List(); + + internal static void CountRoles(out int hunters, out int hiders) + { + hunters = 0; + hiders = 0; + for (int i = 0; i < _active.Count; i++) + { + var p = _active[i]; + if (!p || !p.scopedModes) continue; // ignore the neutral lobby avatar + if (p.role == PlayerRole.Hunter) hunters++; + else if (!p._isFound.Value) hiders++; + } + } + /// /// Places the freshly-spawned owner: role-specific spawn area first, then the shared area (a random /// point inside the collider), falling back to the deterministic client-id ring. Owner-authoritative - diff --git a/Assets/Scripts/Network/UI/MatchHud.cs b/Assets/Scripts/Network/UI/MatchHud.cs index 4a8d909..a9d7142 100644 --- a/Assets/Scripts/Network/UI/MatchHud.cs +++ b/Assets/Scripts/Network/UI/MatchHud.cs @@ -31,14 +31,16 @@ public class MatchHud : MonoBehaviour [SerializeField] private GameObject _crosshair; private int _lastSecond = -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 || !_crosshair) + if (!_canvas || !_phaseText || !_timeText || !_crosshair || !_hunterCount || !_hiderCount) { - Debug.LogError("[MatchHud] Unwired reference(s) - assign Canvas/Phase/Time/Crosshair in the Inspector.", this); + Debug.LogError("[MatchHud] Unwired reference(s) - assign Canvas/Phase/Time/Crosshair/HunterCount/HiderCount in the Inspector.", this); enabled = false; return; } @@ -68,6 +70,18 @@ public class MatchHud : MonoBehaviour _timeText.text = $"{s / 60:0}:{s % 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); }