diff --git a/Assets/Scripts/Components/UI/OverviewMenu.cs b/Assets/Scripts/Components/UI/OverviewMenu.cs
index fa76674..734313a 100644
--- a/Assets/Scripts/Components/UI/OverviewMenu.cs
+++ b/Assets/Scripts/Components/UI/OverviewMenu.cs
@@ -8,10 +8,10 @@ using UnityEngine.InputSystem;
///
/// The Esc overview menu (the "Overview UI" canvas in Main). Owns ALL Escape handling: Esc toggles
/// this panel as a modal pause overlay (gameplay suspended via );
-/// while the session browser overlay is open instead, Esc closes that, and while paint mode is active
-/// Esc exits to the camera mode (Player Control, or First Person for the hunter). Buttons switch game modes
-/// (via ) or open the session browser, and every click
-/// closes the panel. Mode buttons are only shown for modes present in the current role-scoped set
+/// while the session browser or match results overlay is open instead, Esc closes that, and while paint
+/// mode is active Esc exits to the camera mode (Player Control, or First Person for the hunter). Buttons
+/// switch game modes (via ) or open the session browser /
+/// match results overlay, and every click closes the panel. Mode buttons are only shown for modes present in the current role-scoped set
/// (); the active mode's button is disabled. Attach to the
/// always-active canvas root; is the toggled visual child.
///
@@ -24,6 +24,10 @@ public class OverviewMenu : MonoBehaviour
"stays closed. Empty = found automatically.")]
[SerializeField] private OnlineSessionBrowserUI sessionBrowser;
+ [Tooltip("Match results screen binder. While its overlay is open it owns Esc (close-only) and this " +
+ "menu stays closed. Empty = found automatically.")]
+ [SerializeField] private MatchResultScreen matchResultScreen;
+
[Header("Panel")]
[Tooltip("Visual container toggled by Esc. Must be a CHILD of this binder's object so the binder " +
"keeps reading Esc while the panel is hidden.")]
@@ -31,6 +35,7 @@ public class OverviewMenu : MonoBehaviour
[Header("Buttons")]
[SerializeField] private Button sessionButton;
+ [SerializeField] private Button matchResultButton;
[Tooltip("Enters Player Control (third-person orbit) mode.")]
[SerializeField] private Button playerControlButton;
[SerializeField] private Button paintButton;
@@ -43,12 +48,14 @@ public class OverviewMenu : MonoBehaviour
{
if (!gameMode) gameMode = FindFirstObjectByType();
if (!sessionBrowser) sessionBrowser = FindFirstObjectByType();
+ if (!matchResultScreen) matchResultScreen = FindFirstObjectByType();
if (panelRoot) panelRoot.SetActive(false);
}
private void OnEnable()
{
if (sessionButton) sessionButton.onClick.AddListener(OnSession);
+ if (matchResultButton) matchResultButton.onClick.AddListener(OnMatchResult);
if (playerControlButton) playerControlButton.onClick.AddListener(OnPlayerControl);
if (paintButton) paintButton.onClick.AddListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.AddListener(OnFirstPerson);
@@ -58,6 +65,7 @@ public class OverviewMenu : MonoBehaviour
private void OnDisable()
{
if (sessionButton) sessionButton.onClick.RemoveListener(OnSession);
+ if (matchResultButton) matchResultButton.onClick.RemoveListener(OnMatchResult);
if (playerControlButton) playerControlButton.onClick.RemoveListener(OnPlayerControl);
if (paintButton) paintButton.onClick.RemoveListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.RemoveListener(OnFirstPerson);
@@ -67,12 +75,13 @@ public class OverviewMenu : MonoBehaviour
private void Update()
{
bool browserOpen = sessionBrowser && sessionBrowser.IsOpen;
+ bool resultsOpen = matchResultScreen && matchResultScreen.IsOpen;
if (_open)
{
- // The browser overlay took over (its Resolution auto-open) -> hide this panel; the pause
- // is the browser's now, so don't release it.
- if (browserOpen)
+ // Another overlay took over (results Resolution auto-open) -> hide this panel; the pause
+ // is that overlay's now, so don't release it.
+ if (browserOpen || resultsOpen)
HidePanel();
// An external state reset (respawn/bind, disconnect) dropped the pause underneath the
// open panel -> don't linger as a stale overlay over live gameplay.
@@ -84,6 +93,8 @@ public class OverviewMenu : MonoBehaviour
if (browserOpen)
sessionBrowser.Close();
+ else if (resultsOpen)
+ matchResultScreen.Close();
else if (_open)
Close(resume: true);
// Paint mode: Esc exits back to the camera mode instead of opening the menu. Player Control
@@ -129,6 +140,13 @@ public class OverviewMenu : MonoBehaviour
gameMode.SetPaused(false);
}
+ private void OnMatchResult()
+ {
+ Close(resume: false);
+ if (!(matchResultScreen && matchResultScreen.Open()) && gameMode)
+ gameMode.SetPaused(false);
+ }
+
private void OnPlayerControl() => SwitchMode();
private void OnPaint() => SwitchMode();
private void OnFirstPerson() => SwitchMode();
@@ -151,6 +169,11 @@ public class OverviewMenu : MonoBehaviour
IReadOnlyList modes = gameMode.Modes;
GameMode active = gameMode.EffectiveMode;
+ // Match results only exist from the first Hide phase on - hide the button until then
+ // (same pattern as the role-scoped mode buttons).
+ if (matchResultButton)
+ matchResultButton.gameObject.SetActive(matchResultScreen && matchResultScreen.HasResults);
+
SetModeButton(playerControlButton, modes, active);
SetModeButton(paintButton, modes, active);
SetModeButton(firstPersonButton, modes, active);
diff --git a/Assets/Scripts/Network/UI/MatchResultScreen.cs b/Assets/Scripts/Network/UI/MatchResultScreen.cs
index 9fb7a26..22114eb 100644
--- a/Assets/Scripts/Network/UI/MatchResultScreen.cs
+++ b/Assets/Scripts/Network/UI/MatchResultScreen.cs
@@ -1,18 +1,28 @@
using System.Collections.Generic;
using TMPro;
using UnityEngine;
+using UnityEngine.SceneManagement;
///
-/// Binds the match scoreboard section inside the pause/session panel (Online Session Manager) to the
-/// result rows replicates: LIVE while a match runs (roster from Hide start,
-/// re-sent on every hit / mid-match leave; winner still None -> pending hiders read "Hiding") and frozen
-/// with the winner banner when Seek ends (the browser UI auto-opens the panel then). The section root is
-/// hidden while no result data exists (Main / lobby). Rows are pooled clones of the disabled row
-/// template, rebuilt only when a new result version arrives ().
+/// The standalone match results menu (modal pause overlay, same pattern as
+/// ): opened via the overview menu's Match Results button
+/// (), auto-opened when the Resolution sub-phase begins, closed by Esc through
+/// (all Esc handling lives there). Binds the scoreboard to the result rows
+/// replicates: LIVE while a match runs (roster from Hide start, re-sent on
+/// every hit / mid-match leave; winner still None -> pending hiders read "Hiding") and frozen with the
+/// winner banner when Seek ends. Rows are pooled clones of the disabled row template, rebuilt only when
+/// a new result version arrives (). Only the visual
+/// is toggled - this binder stays alive on its (always-active) object so it
+/// keeps tracking match state while the panel is hidden.
///
[DisallowMultipleComponent]
public class MatchResultScreen : MonoBehaviour
{
+ [Header("Overlay")]
+ [Tooltip("Visual container toggled via Open/Close. Must be a CHILD of this binder's object so the " +
+ "binder keeps tracking match state while the panel is hidden.")]
+ [SerializeField] private GameObject _panelRoot;
+
[Tooltip("Big winner banner (\"Hunters win!\" / \"Hiders win!\").")]
[SerializeField] private TMP_Text _winnerText;
@@ -38,6 +48,11 @@ public class MatchResultScreen : MonoBehaviour
private readonly List _rows = new List();
private int _boundVersion = -1;
+ private GameModeManager _gameMode; // pause hook for the modal overlay; found at runtime
+ private bool _open; // overlay visibility (toggled via Open/Close)
+ private Scene _lastScene; // last-seen active scene (to auto-close on a scene change)
+ private MatchManager.MatchSubPhase _lastSubPhase; // last-seen sub-phase (auto-open on Resolution)
+
private void Awake()
{
bool templateOk = _rowTemplate && FindText(_rowTemplate, "Name") && FindText(_rowTemplate, "Outcome") &&
@@ -49,16 +64,65 @@ public class MatchResultScreen : MonoBehaviour
enabled = false;
return;
}
+
+ _gameMode = FindFirstObjectByType();
+ if (_panelRoot) _panelRoot.SetActive(false);
+ }
+
+ /// True while the modal overlay is open. The overview menu yields Esc to this overlay
+ /// (close-only) while it is open.
+ public bool IsOpen => _open;
+
+ /// True once any result data exists (LIVE mid-match roster or the frozen final scoreboard) -
+ /// there is nothing to show before the first Hide phase.
+ public bool HasResults => MatchManager.Instance && MatchManager.Instance.LocalResults.Count > 0;
+
+ /// Opens the modal overlay (used by the overview menu's Match Results button); false while
+ /// no result data exists, so the caller can drop the pause instead of stranding it.
+ public bool Open()
+ {
+ if (!enabled || !HasResults) return false;
+ SetOpen(true);
+ return true;
+ }
+
+ /// Closes the modal overlay and resumes gameplay (Esc handling in ).
+ public void Close() => SetOpen(false);
+
+ private void SetOpen(bool open)
+ {
+ _open = open;
+ if (_panelRoot) _panelRoot.SetActive(open);
+ if (_gameMode) _gameMode.SetPaused(open);
}
private void Update()
{
+ // Auto-close if the scene changed underneath the open overlay (match ended -> lobby swap, or a
+ // remote started the match) so it never lingers as a stale pause across a transition.
+ Scene scene = SceneManager.GetActiveScene();
+ if (_open && _lastScene.IsValid() && scene != _lastScene)
+ SetOpen(false);
+ _lastScene = scene;
+
var match = MatchManager.Instance;
bool hasResults = match && match.LocalResults.Count > 0;
+
+ // Resolution just began -> auto-open so the final scoreboard is presented (this used to live on
+ // the session browser while the scoreboard was a section of its panel). Esc can still close it.
+ MatchManager.MatchSubPhase subPhase = match ? match.LocalSubPhase : MatchManager.MatchSubPhase.None;
+ if (subPhase != _lastSubPhase)
+ {
+ _lastSubPhase = subPhase;
+ if (subPhase == MatchManager.MatchSubPhase.Resolution && !_open && hasResults)
+ SetOpen(true);
+ }
+
if (_content && _content.activeSelf != hasResults) _content.SetActive(hasResults);
if (!hasResults)
{
_boundVersion = -1;
+ if (_open) SetOpen(false); // data vanished (disconnect) -> don't linger as an empty pause
return;
}
if (_boundVersion != match.ResultVersion) Bind(match);
diff --git a/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs b/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs
index 160d272..5c3d81d 100644
--- a/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs
+++ b/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs
@@ -13,9 +13,7 @@ using UnityEngine.UI;
/// lobby/map the overview (Esc) menu reopens it via as a modal pause overlay
/// (cursor freed, gameplay suspended via ) - all Esc key
/// handling lives in , which closes this overlay through .
-/// It also auto-opens when the Resolution sub-phase begins so
-/// the final match scoreboard (a section of this panel) is presented. The Disconnect button is
-/// context-sensitive: in Main it is an
+/// The Disconnect button is context-sensitive: in Main it is an
/// application "Exit"; on the map it becomes "Back to Lobby" for the match authority (ends the match);
/// everyone else gets a plain disconnect. Only the visual is toggled - this binder
/// stays alive on its (always-active) canvas so it keeps tracking state while the panel is hidden.
@@ -61,7 +59,6 @@ public class OnlineSessionBrowserUI : MonoBehaviour
private bool _connected; // last-seen connection state (to detect transitions)
private Scene _lastScene; // last-seen active scene (to auto-close on a scene change)
private string _disconnectText; // last applied caption (avoids per-frame TMP churn)
- private MatchManager.MatchSubPhase _lastSubPhase; // last-seen sub-phase (auto-open on Resolution)
private void Awake()
{
@@ -124,16 +121,6 @@ public class OnlineSessionBrowserUI : MonoBehaviour
if (!connected) return; // Main: panel always shown
- // Resolution just began -> auto-open the overlay so the final scoreboard is presented. Esc can
- // still close it (via the overview menu); the lobby scene swap afterwards auto-closes it (see above).
- MatchManager.MatchSubPhase subPhase = _match ? _match.LocalSubPhase : MatchManager.MatchSubPhase.None;
- if (subPhase != _lastSubPhase)
- {
- _lastSubPhase = subPhase;
- if (subPhase == MatchManager.MatchSubPhase.Resolution && !_open)
- SetOpen(true);
- }
-
RefreshDisconnectLabel();
}
@@ -202,7 +189,6 @@ public class OnlineSessionBrowserUI : MonoBehaviour
// Connection up (hosted/joined) -> hide the panel; Esc reopens it in the lobby/map.
// Back to Main (disconnect/shutdown) -> show it again and drop any pause.
_open = false;
- _lastSubPhase = MatchManager.MatchSubPhase.None; // rejoining into Resolution must re-trigger
if (!connected && _gameMode) _gameMode.SetPaused(false);
ApplyVisibility();
}