Add OverviewMenu escape menu, remove Tab mode cycling

- New OverviewMenu component: Esc toggles the pause overlay, buttons
  switch modes via GameModeManager.TrySwitchTo<T>() and close the panel;
  visibility follows the role-scoped mode set, active mode disabled.
- Esc in paint mode exits to camera mode instead (PlayerControlMode,
  hunter falls back to FirstPersonMode) rather than opening the menu.
- Centralized all Esc handling in OverviewMenu (browser close included)
  to avoid frame races; OnlineSessionBrowserUI gains IsOpen/Open/Close.
- GameModeManager: switchKey Tab-cycle removed, TrySwitchTo<T> added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5 committed 2026-07-12 12:34:12 +02:00
1 parent 367bc9cee0
commit 7ac6038121
4 files changed
+239 -53

No files matched your search

+31 -29
View File
@@ -7,10 +7,11 @@ using UnityEngine.InputSystem;
#endif
/// <summary>
/// Switches the game between high-level modes. The switch key (Tab) cycles to the next registered
/// mode. The mode set is scoped to the local avatar's <see cref="PlayerRole"/> when it spawns
/// (Hunter: First Person + Paint; Hider: Player Control + Paint + Free Cam); a non-networked scene
/// uses the full set. Attach to a single, persistent scene GameObject.
/// Switches the game between high-level modes. Modes are selected via the overview (Esc) menu
/// (see <see cref="OverviewMenu"/>, which calls <see cref="TrySwitchTo{T}"/>). The mode set is
/// scoped to the local avatar's <see cref="PlayerRole"/> when it spawns (Hunter: First Person +
/// Paint; Hider: Player Control + Paint + Free Cam); a non-networked scene uses the full set.
/// Attach to a single, persistent scene GameObject.
/// </summary>
[DisallowMultipleComponent]
public class GameModeManager : MonoBehaviour
@@ -32,13 +33,6 @@ public class GameModeManager : MonoBehaviour
public FirstPersonController firstPerson;
[Header("Switching")]
[Tooltip("Key that cycles to the next mode.")]
#if ENABLE_INPUT_SYSTEM
public Key switchKey = Key.Tab;
#else
public KeyCode switchKey = KeyCode.Tab;
#endif
[Tooltip("Mode active at startup (0 = Player Control).")]
public int startModeIndex = 0;
@@ -98,6 +92,25 @@ public class GameModeManager : MonoBehaviour
public GameMode ActiveMode =>
(_activeIndex >= 0 && _activeIndex < _modes.Count) ? _modes[_activeIndex] : null;
/// <summary>The registered role-scoped mode set (read-only; the overview menu builds its buttons from this).</summary>
public IReadOnlyList<GameMode> Modes => _modes;
/// <summary>True while gameplay is suspended for a modal overlay (see <see cref="SetPaused"/>).</summary>
public bool IsPaused => _paused;
/// <summary>The active mode, or - while paused - the mode that will resume. Null in the no-player state.</summary>
public GameMode EffectiveMode =>
ActiveMode ?? ((_resumeIndex >= 0 && _resumeIndex < _modes.Count) ? _modes[_resumeIndex] : null);
/// <summary>Switches to the registered mode of type <typeparamref name="T"/>; false if the
/// current role-scoped set has none.</summary>
public bool TrySwitchTo<T>() where T : GameMode
{
for (int i = 0; i < _modes.Count; i++)
if (_modes[i] is T) { SwitchTo(i); return true; }
return false;
}
private void Awake()
{
if (!player) player = FindFirstObjectByType<PlayerCharacterController>();
@@ -115,7 +128,7 @@ public class GameModeManager : MonoBehaviour
// Go live only when this scene already holds a player to control (a scene-placed avatar). In the
// networked Main scene the avatar spawns on connect, so the manager stays inert - no camera control,
// no mode switching - until BindLocalPlayer promotes it once the lobby avatar exists. Without this,
// the pre-spawn menu already lets you orbit the camera and Tab-cycle modes with no player.
// the pre-spawn menu already lets you orbit the camera and switch modes with no player.
if (player && _modes.Count > 0)
SwitchTo(Mathf.Clamp(startModeIndex, 0, _modes.Count - 1));
else
@@ -283,7 +296,8 @@ public class GameModeManager : MonoBehaviour
/// unlock would be reverted next frame) and the cursor freed. Entered at startup in a networked scene
/// before the lobby avatar spawns, and again after it is gone (see <see cref="ReleaseToNoPlayer"/>).
/// <see cref="BindLocalPlayer"/> / <see cref="BindSpectator"/> promote out of it. With no active mode
/// (_activeIndex &lt; 0) Update ignores the switch key, so Tab cannot bootstrap a mode from here.
/// (_activeIndex &lt; 0) Update is inert and the overview menu refuses to open, so nothing can
/// bootstrap a mode from here.
/// </summary>
private void EnterNoPlayerState()
{
@@ -358,8 +372,8 @@ public class GameModeManager : MonoBehaviour
if (_paused) return;
if (_modes.Count == 0) return;
// No active mode = the inert no-player state (pre-lobby, or after ReleaseToNoPlayer). Ignore the
// switch key and Tick so Tab can't bootstrap a mode and no camera rig is driven until a bind.
// No active mode = the inert no-player state (pre-lobby, or after ReleaseToNoPlayer). Skip the
// Tick so no camera rig is driven until a bind.
if (_activeIndex < 0) return;
// Pre-match loading barrier: hold the local player frozen (no movement, painting or mode switching)
@@ -389,27 +403,15 @@ public class GameModeManager : MonoBehaviour
ActiveMode?.Enter();
}
// Resolution (the reveal window) is watch-only: force out of Paint and keep the switch key from
// Resolution (the reveal window) is watch-only: force out of Paint and keep mode switching from
// re-entering. No restore needed - the end-of-match respawn rebuilds the cycle at index 0.
bool suppressPaint = PaintSuppressed();
if (suppressPaint && ActiveMode is PaintMode)
if (PaintSuppressed() && ActiveMode is PaintMode)
{
int fallback = FirstNonPaintFrom(0);
if (fallback >= 0) SwitchTo(fallback);
else if (painter) painter.PaintingEnabled = false; // degenerate paint-only set: at least stop painting
}
if (InputUtil.GameplayKeyPressed(switchKey))
{
int next = (_activeIndex + 1) % _modes.Count;
if (suppressPaint && _modes[next] is PaintMode)
{
int skip = FirstNonPaintFrom(next);
if (skip >= 0) next = skip;
}
SwitchTo(next);
}
// A hider manually pings itself onto every hunter's radar (same blip as the periodic sweep), on demand.
// Excluded in Free Cam whose fly controls already use this key; the phase/role gate lives in MatchManager
// (RequestManualPing no-ops outside Seek / for a found hider), so a stray press is harmless.