Toggle session panel with Escape as in-game pause overlay
- Panel shows in Main while disconnected, auto-hides once a connection is established, and toggles open/closed with Escape in the Lobby or on a Map. - Escape now pauses gameplay (active mode exits, cursor freed, paint and movement disabled) instead of ending the match directly; closing restores the prior mode via SwitchTo. - Disconnect button is context-aware: leaves the session in the Lobby, acts as "Back to Lobby" (EndMatch) for the match authority on a Map, and stays a plain disconnect for non-authority clients on a Map. - Removed the Escape->EndMatch handler from MatchManager; match end now routes through RequestEndMatch with the same authority routing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
d27ee16004
commit
cdfec364b2
4 files changed
+248
-51
No files matched your search
+29
-11
@@ -678,6 +678,11 @@ MonoBehaviour:
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
CustomBlends: {fileID: 11400000, guid: 47b57a7fe51fd154e975311ad293878a, type: 2}
|
||||
--- !u!1 &499812162 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 534773943941885308, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
m_PrefabInstance: {fileID: 8599056809828915506}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!224 &499812163 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 4566662197198005404, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
@@ -819,6 +824,7 @@ MonoBehaviour:
|
||||
listContent: {fileID: 764069389}
|
||||
entryTemplate: {fileID: 1675823052}
|
||||
statusLabel: {fileID: 2102676159}
|
||||
panelRoot: {fileID: 499812162}
|
||||
selectedTint: {r: 0.26, g: 0.52, b: 0.96, a: 1}
|
||||
normalTint: {r: 1, g: 1, b: 1, a: 0.05}
|
||||
--- !u!114 &701418504 stripped
|
||||
@@ -1373,7 +1379,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: Idle
|
||||
m_text:
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 964ae5cd392ba08499b88de012ff8213, type: 2}
|
||||
m_sharedMaterial: {fileID: 1752619484}
|
||||
@@ -1409,8 +1415,8 @@ MonoBehaviour:
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 8192
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 2048
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
@@ -1421,7 +1427,7 @@ MonoBehaviour:
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_overflowMode: 1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
@@ -1441,7 +1447,7 @@ MonoBehaviour:
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_margin: {x: 0, y: 0, z: 0, w: -27.158808}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
@@ -1471,7 +1477,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: -4}
|
||||
m_AnchoredPosition: {x: 0, y: 96}
|
||||
m_SizeDelta: {x: -16, y: 24}
|
||||
m_Pivot: {x: 0.5, y: 1}
|
||||
--- !u!1001 &8599056809828915506
|
||||
@@ -1492,7 +1498,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 458161408064890808, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
value: 58.7
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 458161408064890808, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
@@ -1510,6 +1516,14 @@ PrefabInstance:
|
||||
propertyPath: m_Name
|
||||
value: Online Session Manager
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1117235872924133776, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: -134
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1117235872924133776, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 35
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1124549748001373863, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
@@ -1520,7 +1534,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1124549748001373863, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
value: 154.8
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1124549748001373863, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
@@ -1544,7 +1558,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2926165666280585152, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
value: 65.4
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2926165666280585152, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
@@ -1666,6 +1680,10 @@ PrefabInstance:
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8080141125004544176, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_ChildControlWidth
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8110497960478258844, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_fontMaterial
|
||||
value:
|
||||
@@ -1700,7 +1718,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8555406533395562537, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
value: 87.1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8555406533395562537, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
@@ -1718,7 +1736,7 @@ PrefabInstance:
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects:
|
||||
- targetCorrespondingSourceObject: {fileID: 4566662197198005404, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
insertIndex: -1
|
||||
insertIndex: 1
|
||||
addedObject: {fileID: 2102676161}
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 1040229641786897100, guid: 940219f29a5971947be1767af2ec5a71, type: 3}
|
||||
|
||||
@@ -63,6 +63,11 @@ public class GameModeManager : MonoBehaviour
|
||||
|
||||
private readonly List<GameMode> _modes = new List<GameMode>();
|
||||
private int _activeIndex = -1;
|
||||
private bool _paused;
|
||||
private int _resumeIndex = -1;
|
||||
|
||||
/// <summary>True while local gameplay is suspended for a modal overlay (see <see cref="SetPaused"/>).</summary>
|
||||
public bool IsPaused => _paused;
|
||||
|
||||
/// <summary>The currently active mode (null before startup).</summary>
|
||||
public GameMode ActiveMode =>
|
||||
@@ -145,6 +150,9 @@ public class GameModeManager : MonoBehaviour
|
||||
/// </summary>
|
||||
public void BindSpectator()
|
||||
{
|
||||
_paused = false;
|
||||
_resumeIndex = -1;
|
||||
|
||||
player = null;
|
||||
painter = null;
|
||||
PainterChanged?.Invoke(null);
|
||||
@@ -165,6 +173,10 @@ public class GameModeManager : MonoBehaviour
|
||||
{
|
||||
if (!localPlayer) return;
|
||||
|
||||
// A fresh bind is a definitive state reset - drop any modal pause so the new avatar starts live.
|
||||
_paused = false;
|
||||
_resumeIndex = -1;
|
||||
|
||||
var setup = localPlayer.GetComponent<NetworkPlayerSetup>();
|
||||
|
||||
player = localPlayer;
|
||||
@@ -216,6 +228,9 @@ public class GameModeManager : MonoBehaviour
|
||||
/// </summary>
|
||||
public void ReleaseToNoPlayer()
|
||||
{
|
||||
_paused = false;
|
||||
_resumeIndex = -1;
|
||||
|
||||
ActiveMode?.Exit();
|
||||
_activeIndex = -1;
|
||||
_modes.Clear();
|
||||
@@ -236,8 +251,54 @@ public class GameModeManager : MonoBehaviour
|
||||
Cursor.visible = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses or resumes local gameplay for a full-screen modal overlay (the online session panel, opened
|
||||
/// with Esc in the lobby/map). Pausing frees the OS cursor and suppresses ALL local input - movement,
|
||||
/// painting, camera look and the mode-switch key - WITHOUT tearing down the mode cycle, so resuming
|
||||
/// re-enters the exact mode that was active and restores its camera/cursor state. It mirrors the
|
||||
/// <see cref="ReleaseToNoPlayer"/> cursor handling (Exit the active mode, then park the orbit rig in
|
||||
/// Disabled so it stops re-grabbing the cursor every frame) but is reversible. No-op if unchanged.
|
||||
/// </summary>
|
||||
public void SetPaused(bool paused)
|
||||
{
|
||||
if (paused == _paused) return;
|
||||
_paused = paused;
|
||||
|
||||
if (paused)
|
||||
{
|
||||
_resumeIndex = _activeIndex;
|
||||
ActiveMode?.Exit(); // undo any FreeCam/First-Person takeover (also releases the cursor)
|
||||
_activeIndex = -1;
|
||||
|
||||
// Painting and movement run off their own flags/Update loops and the orbit rig re-asserts the
|
||||
// cursor each frame from its LookMode, so stop them all explicitly, then free the cursor. A
|
||||
// Disabled look-mode keeps the cursor free every frame (the same trick ReleaseToNoPlayer uses).
|
||||
if (player) player.ControlEnabled = false;
|
||||
if (painter) painter.PaintingEnabled = false;
|
||||
if (cameraInput)
|
||||
{
|
||||
cameraInput.enabled = true;
|
||||
cameraInput.LookMode = ThirdPersonOrbitInput.OrbitLookMode.Disabled;
|
||||
}
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
|
||||
ModeChanged?.Invoke(null); // hide mode-following UI (the paint HUD) beneath the overlay
|
||||
}
|
||||
else
|
||||
{
|
||||
int resume = _resumeIndex;
|
||||
_resumeIndex = -1;
|
||||
if (resume >= 0 && resume < _modes.Count)
|
||||
SwitchTo(resume); // re-Enter restores player/painter/look + cursor (+ re-shows the HUD)
|
||||
else
|
||||
ModeChanged?.Invoke(ActiveMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_paused) return;
|
||||
if (_modes.Count == 0) return;
|
||||
|
||||
if (WasSwitchPressed())
|
||||
|
||||
@@ -4,9 +4,6 @@ using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Server-authoritative match flow. Lives on the persistent NetworkManager object in the boot scene
|
||||
@@ -37,17 +34,19 @@ using UnityEngine.InputSystem;
|
||||
/// (strictly serialized - an NGO constraint, only one scene op in flight); then every player object is
|
||||
/// despawned and respawned as its role's prefab variant.
|
||||
///
|
||||
/// Match-control AUTHORITY: the client allowed to start (walk into the trigger) and end (Esc) the match.
|
||||
/// Match-control AUTHORITY: the client allowed to start (walk into the trigger) and end the match.
|
||||
/// A host has its own player, so the host is the authority. A DEDICATED server (StartServer, no local
|
||||
/// player) hands authority to the FIRST client that connects, so a server nobody plays on is still
|
||||
/// controllable. The id is server-local; the designated remote client is told via a named message so it
|
||||
/// reads Esc locally and asks the server (which validates the sender) to end. If the authority leaves,
|
||||
/// it is reassigned to another connected client.
|
||||
/// can ask the server (which validates the sender) to end. If the authority leaves, it is reassigned to
|
||||
/// another connected client.
|
||||
///
|
||||
/// End / join / teardown:
|
||||
/// - The match authority presses Esc -> <see cref="EndMatch"/>: Test Map is swapped back to Lobby and
|
||||
/// everyone is respawned as the neutral Player. The host/server ends directly; a remote authority
|
||||
/// client requests it over a named message that only the authority's id is honored for.
|
||||
/// - The match authority ends the match via <see cref="RequestEndMatch"/> -> <see cref="EndMatch"/>
|
||||
/// (driven by the online session panel's "Back to Lobby" button - Esc opens that panel rather than
|
||||
/// ending directly). Test Map is swapped back to Lobby and everyone is respawned as the neutral Player.
|
||||
/// The host/server ends directly; a remote authority client requests it over a named message that only
|
||||
/// the authority's id is honored for. A non-authority client's panel button is a plain disconnect.
|
||||
/// - A client that joins MID-MATCH spawns as a SPECTATOR: a dedicated EMPTY prefab (no body/collider)
|
||||
/// bound to free-cam-only, so it flies around the running map and watches but does not play. It
|
||||
/// respawns as a neutral lobby Player when the match ends.
|
||||
@@ -244,35 +243,6 @@ public class MatchManager : MonoBehaviour
|
||||
ReturnToMainOnly();
|
||||
}
|
||||
|
||||
// Esc ends the running match (sends everyone back to the lobby) - but only for the match AUTHORITY.
|
||||
// The host/server reads it locally and ends directly. A remote authority client (dedicated server)
|
||||
// can't end the match itself (the flow is server-authoritative), so it asks the server over a named
|
||||
// message; the server validates the sender is the authority and that a match is running.
|
||||
private void Update()
|
||||
{
|
||||
if (_net == null) return;
|
||||
|
||||
if (_net.IsServer)
|
||||
{
|
||||
if (Phase == MatchPhase.InMatch && IsMatchAuthority(_net.LocalClientId) && EscapePressed())
|
||||
EndMatch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pure client: Phase is server-local, so we don't gate on it here - the server checks it on receipt.
|
||||
if (_isLocalAuthority && EscapePressed())
|
||||
RequestEndMatchFromServer();
|
||||
}
|
||||
|
||||
private static bool EscapePressed()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame;
|
||||
#else
|
||||
return Input.GetKeyDown(KeyCode.Escape);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HookSceneManager()
|
||||
{
|
||||
if (_hooked || _net == null || _net.SceneManager == null) return;
|
||||
@@ -527,6 +497,35 @@ public class MatchManager : MonoBehaviour
|
||||
/// <summary>True if the given client currently holds match-control authority (may start and end the match).</summary>
|
||||
public bool IsMatchAuthority(ulong clientId) => _hasAuthorityClient && clientId == _authorityClientId;
|
||||
|
||||
/// <summary>True if THIS machine holds match authority - the host (whose own client id is the authority)
|
||||
/// or the granted authority client on a dedicated server. Drives the online panel's Disconnect button:
|
||||
/// on the map the authority's button means "Back to Lobby" (end the match); everyone else disconnects.</summary>
|
||||
public bool IsLocalMatchAuthority
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_net == null) return false;
|
||||
if (_net.IsServer) return IsMatchAuthority(_net.LocalClientId);
|
||||
return _isLocalAuthority;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ends the running match for everyone (back to the Lobby), routed through authority: the
|
||||
/// host/server ends directly; a remote authority client asks the server. No-op if this machine is not
|
||||
/// the authority or no match is running. Replaces the old Esc handler - now driven by the panel button.</summary>
|
||||
public void RequestEndMatch()
|
||||
{
|
||||
if (_net == null) return;
|
||||
if (_net.IsServer) { EndMatch(); return; } // EndMatch itself guards Phase == InMatch
|
||||
if (_isLocalAuthority) RequestEndMatchFromServer();
|
||||
}
|
||||
|
||||
// Local active-scene kind. Every peer sets its active scene to Lobby/Map as those load (see OnSceneEvent)
|
||||
// and back to Main on teardown, so a local UI can tell where it is without the server-local Phase.
|
||||
public bool IsLocalSceneMain => SceneManager.GetActiveScene().name == mainSceneName;
|
||||
public bool IsLocalSceneLobby => SceneManager.GetActiveScene().name == lobbySceneName;
|
||||
public bool IsLocalSceneMap => SceneManager.GetActiveScene().name == matchSceneName;
|
||||
|
||||
// Server -> client: tell the designated client it is the match authority (so it reads Esc locally).
|
||||
private void SendAuthorityGrant(ulong clientId)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// uGUI binding for <see cref="OnlineSessionManager"/>. Drives the server-browser canvas - Host / Refresh /
|
||||
@@ -13,6 +17,13 @@ using UnityEngine.UI;
|
||||
/// Selection model: Refresh fills the list with clones of <see cref="entryTemplate"/> (each a clickable row
|
||||
/// showing name + player count). Clicking a row selects it; Join connects to the selected session. While
|
||||
/// connected, only Disconnect is interactable.
|
||||
///
|
||||
/// Overlay behaviour: the panel is always shown in Main (not connected). Once a connection is up it hides;
|
||||
/// in the lobby/map Esc toggles it back as a modal pause overlay (cursor freed, local gameplay suspended via
|
||||
/// <see cref="GameModeManager.SetPaused"/>). On the map the Disconnect button becomes "Back to Lobby" for the
|
||||
/// match authority (ends the match), while everyone else (in the lobby, or a non-authority client on the map)
|
||||
/// gets a plain disconnect. Only the visual <see cref="panelRoot"/> is toggled - this binder stays alive on
|
||||
/// its (always-active) canvas so it keeps reading Esc while the panel is hidden.
|
||||
/// </summary>
|
||||
public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
{
|
||||
@@ -33,6 +44,11 @@ public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
[Header("Status")]
|
||||
[SerializeField] private TMP_Text statusLabel;
|
||||
|
||||
[Header("Overlay")]
|
||||
[Tooltip("Visual container toggled by Esc while connected (the panel root). Must be a CHILD of this " +
|
||||
"binder's object so the binder keeps running - and reading Esc - while the panel is hidden.")]
|
||||
[SerializeField] private GameObject panelRoot;
|
||||
|
||||
[Tooltip("Background tint of the selected row.")]
|
||||
[SerializeField] private Color selectedTint = new Color(0.26f, 0.52f, 0.96f, 1f);
|
||||
[Tooltip("Background tint of an unselected row.")]
|
||||
@@ -42,10 +58,22 @@ public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
private readonly List<Row> _rows = new List<Row>();
|
||||
private string _selectedId;
|
||||
|
||||
private MatchManager _match; // local match state (scene kind + authority); found at runtime
|
||||
private GameModeManager _gameMode; // pause hook for the modal overlay; found at runtime
|
||||
private TMP_Text _disconnectLabel; // disconnect button caption (relabelled to "Back to Lobby")
|
||||
|
||||
private bool _open; // Esc-toggled visibility while connected
|
||||
private bool _connected; // last-seen connection state (to detect transitions)
|
||||
private string _lastScene; // last-seen active scene name (to auto-close on a scene change)
|
||||
private string _disconnectText; // last applied caption (avoids per-frame TMP churn)
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// The template is the prototype, never shown itself - layout ignores it while disabled.
|
||||
if (entryTemplate) entryTemplate.SetActive(false);
|
||||
|
||||
_match = FindFirstObjectByType<MatchManager>();
|
||||
_gameMode = FindFirstObjectByType<GameModeManager>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -53,7 +81,11 @@ public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
if (hostButton) hostButton.onClick.AddListener(OnHost);
|
||||
if (refreshButton) refreshButton.onClick.AddListener(OnRefresh);
|
||||
if (joinButton) joinButton.onClick.AddListener(OnJoin);
|
||||
if (disconnectButton) disconnectButton.onClick.AddListener(OnDisconnect);
|
||||
if (disconnectButton)
|
||||
{
|
||||
disconnectButton.onClick.AddListener(OnDisconnect);
|
||||
_disconnectLabel = disconnectButton.GetComponentInChildren<TMP_Text>(true);
|
||||
}
|
||||
|
||||
if (manager)
|
||||
{
|
||||
@@ -61,8 +93,10 @@ public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
manager.SessionsChanged += RebuildList;
|
||||
}
|
||||
|
||||
_connected = manager && manager.IsConnected;
|
||||
RebuildList();
|
||||
OnManagerChanged();
|
||||
ApplyVisibility();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@@ -79,16 +113,92 @@ public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!manager) return;
|
||||
bool connected = manager.IsConnected;
|
||||
|
||||
// Auto-close the overlay if the scene changed underneath it (a remote ended/started the match, or
|
||||
// we walked into Start Match) so it never lingers as a stale pause across a transition.
|
||||
string scene = SceneManager.GetActiveScene().name;
|
||||
if (connected && _open && _lastScene != null && scene != _lastScene)
|
||||
SetOpen(false);
|
||||
_lastScene = scene;
|
||||
|
||||
if (!connected) return; // Main: panel always shown, Esc ignored
|
||||
|
||||
bool inLobbyOrMap = _match && (_match.IsLocalSceneLobby || _match.IsLocalSceneMap);
|
||||
if (inLobbyOrMap && EscapePressed())
|
||||
SetOpen(!_open);
|
||||
|
||||
RefreshDisconnectLabel();
|
||||
}
|
||||
|
||||
private void OnHost() { if (manager) manager.Host(); }
|
||||
private void OnRefresh() { if (manager) manager.Refresh(); }
|
||||
private void OnJoin() { if (manager && !string.IsNullOrEmpty(_selectedId)) manager.Join(_selectedId); }
|
||||
private void OnDisconnect() { if (manager) manager.Disconnect(); }
|
||||
|
||||
private void OnDisconnect()
|
||||
{
|
||||
if (!manager) return;
|
||||
|
||||
// On the map, the match AUTHORITY's button means "Back to Lobby" (end the match for everyone)
|
||||
// rather than leaving the session. Everyone else - in the lobby, or a non-authority client on the
|
||||
// map - does a plain disconnect.
|
||||
if (_match && _match.IsLocalSceneMap && _match.IsLocalMatchAuthority)
|
||||
{
|
||||
_match.RequestEndMatch();
|
||||
SetOpen(false); // heading back to the lobby; close the overlay + resume input
|
||||
return;
|
||||
}
|
||||
|
||||
manager.Disconnect();
|
||||
}
|
||||
|
||||
private void OnManagerChanged()
|
||||
{
|
||||
if (!manager) return;
|
||||
|
||||
bool connected = manager.IsConnected;
|
||||
if (connected != _connected)
|
||||
{
|
||||
_connected = connected;
|
||||
// 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;
|
||||
if (!connected && _gameMode) _gameMode.SetPaused(false);
|
||||
ApplyVisibility();
|
||||
}
|
||||
|
||||
if (statusLabel) statusLabel.text = manager.Status;
|
||||
UpdateButtons();
|
||||
RefreshDisconnectLabel();
|
||||
}
|
||||
|
||||
private void SetOpen(bool open)
|
||||
{
|
||||
_open = open;
|
||||
ApplyVisibility();
|
||||
if (_gameMode) _gameMode.SetPaused(open);
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
bool show = !(manager && manager.IsConnected) || _open;
|
||||
if (panelRoot && panelRoot.activeSelf != show)
|
||||
panelRoot.SetActive(show);
|
||||
}
|
||||
|
||||
private void RefreshDisconnectLabel()
|
||||
{
|
||||
if (!_disconnectLabel) return;
|
||||
bool backToLobby = _match && _match.IsLocalSceneMap && _match.IsLocalMatchAuthority;
|
||||
string text = backToLobby ? "Back to Lobby" : "Disconnect";
|
||||
if (text != _disconnectText)
|
||||
{
|
||||
_disconnectText = text;
|
||||
_disconnectLabel.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateButtons()
|
||||
@@ -165,4 +275,13 @@ public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
var t = root.Find(childName);
|
||||
return t ? t.GetComponent<TMP_Text>() : null;
|
||||
}
|
||||
|
||||
private static bool EscapePressed()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame;
|
||||
#else
|
||||
return Input.GetKeyDown(KeyCode.Escape);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user