Add taunt button, Tab as Escape alternative, free cam Q/E removal

- OverviewMenu: taunt button (manual ping + close, hider/seek-only), Tab triggers the Escape chain (TypingGuard-gated)
- GameModeManager: LocalPlayerIsHider helper, allow E-ping in free cam (Q/E no longer used for movement)
- FreeCamController: vertical movement only via Space/Ctrl
- Icons: Icon_taunt (wired), Icon_firstperson_paint (unwired, for later)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5 committed 2026-07-12 15:22:47 +02:00
1 parent 1054bcd3d6
commit c6a22c1643
7 files changed
+269 -12

No files matched your search

@@ -6,7 +6,7 @@ using UnityEngine.InputSystem;
/// <summary>
/// Free-flying spectator camera on its own dedicated <see cref="CinemachineCamera"/>: WASD flight relative
/// to the view, Q/E (or Space/Left&#160;Ctrl) for down/up, mouse look. A <see cref="CharacterController"/>
/// to the view, Space/Left&#160;Ctrl for up/down, mouse look. A <see cref="CharacterController"/>
/// capsule makes it slide along world geometry instead of clipping. Driven by <see cref="FreeCamMode"/>
/// via <see cref="SetActive"/>; while inactive this component does nothing.
/// </summary>
@@ -170,14 +170,15 @@ public class FreeCamController : MonoBehaviour
if (kb.sKey.isPressed) dir -= transform.forward;
if (kb.dKey.isPressed) dir += transform.right;
if (kb.aKey.isPressed) dir -= transform.right;
if (kb.eKey.isPressed || kb.spaceKey.isPressed) dir += Vector3.up;
if (kb.qKey.isPressed || kb.leftCtrlKey.isPressed) dir -= Vector3.up;
// Q/E intentionally unbound: E is the taunt key and must work in free cam.
if (kb.spaceKey.isPressed) dir += Vector3.up;
if (kb.leftCtrlKey.isPressed) dir -= Vector3.up;
}
#else
dir += transform.forward * Input.GetAxisRaw("Vertical");
dir += transform.right * Input.GetAxisRaw("Horizontal");
if (Input.GetKey(KeyCode.E) || Input.GetKey(KeyCode.Space)) dir += Vector3.up;
if (Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
if (Input.GetKey(KeyCode.Space)) dir += Vector3.up;
if (Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
#endif
return dir;
}
+23 -4
View File
@@ -6,7 +6,8 @@ using UnityEngine.InputSystem;
#endif
/// <summary>
/// The Esc overview menu (the "Overview UI" canvas in Main). Owns ALL Escape handling: Esc toggles
/// The Esc overview menu (the "Overview UI" canvas in Main). Owns ALL Escape handling (Tab acts as an
/// alternative to Esc): Esc toggles
/// this panel as a modal pause overlay (gameplay suspended via <see cref="GameModeManager.SetPaused"/>);
/// 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
@@ -41,6 +42,9 @@ public class OverviewMenu : MonoBehaviour
[SerializeField] private Button paintButton;
[SerializeField] private Button firstPersonButton;
[SerializeField] private Button freeCamButton;
[Tooltip("Triggers the hider's manual radar ping (same as the E key) and closes the menu. Only shown " +
"for a hider during Seek.")]
[SerializeField] private Button tauntButton;
private bool _open;
@@ -60,6 +64,7 @@ public class OverviewMenu : MonoBehaviour
if (paintButton) paintButton.onClick.AddListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.AddListener(OnFirstPerson);
if (freeCamButton) freeCamButton.onClick.AddListener(OnFreeCam);
if (tauntButton) tauntButton.onClick.AddListener(OnTaunt);
}
private void OnDisable()
@@ -70,6 +75,7 @@ public class OverviewMenu : MonoBehaviour
if (paintButton) paintButton.onClick.RemoveListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.RemoveListener(OnFirstPerson);
if (freeCamButton) freeCamButton.onClick.RemoveListener(OnFreeCam);
if (tauntButton) tauntButton.onClick.RemoveListener(OnTaunt);
}
private void Update()
@@ -147,6 +153,12 @@ public class OverviewMenu : MonoBehaviour
gameMode.SetPaused(false);
}
private void OnTaunt()
{
Close(resume: true);
if (MatchManager.Instance) MatchManager.Instance.RequestManualPing();
}
private void OnPlayerControl() => SwitchMode<PlayerControlMode>();
private void OnPaint() => SwitchMode<PaintMode>();
private void OnFirstPerson() => SwitchMode<FirstPersonMode>();
@@ -174,6 +186,12 @@ public class OverviewMenu : MonoBehaviour
if (matchResultButton)
matchResultButton.gameObject.SetActive(matchResultScreen && matchResultScreen.HasResults);
// Taunt/ping only does something for a hider during Seek (the server re-gates found hiders) -
// hide it otherwise, same pattern as the role-scoped mode buttons.
if (tauntButton)
tauntButton.gameObject.SetActive(gameMode.LocalPlayerIsHider && MatchManager.Instance &&
MatchManager.Instance.LocalSubPhase == MatchManager.MatchSubPhase.Seek);
SetModeButton<PlayerControlMode>(playerControlButton, modes, active);
SetModeButton<PaintMode>(paintButton, modes, active);
SetModeButton<FirstPersonMode>(firstPersonButton, modes, active);
@@ -191,13 +209,14 @@ public class OverviewMenu : MonoBehaviour
button.interactable = !(active is T);
}
// NOT TypingGuard-gated (GameplayKeyPressed): Esc must work even while a text field has focus.
// Esc is NOT TypingGuard-gated: it must work even while a text field has focus.
// Tab (alternative binding) IS gated so it stays usable for typing/field navigation.
private static bool EscapePressed()
{
#if ENABLE_INPUT_SYSTEM
return InputUtil.KeyPressedThisFrame(Key.Escape);
return InputUtil.KeyPressedThisFrame(Key.Escape) || InputUtil.GameplayKeyPressed(Key.Tab);
#else
return InputUtil.KeyPressedThisFrame(KeyCode.Escape);
return InputUtil.KeyPressedThisFrame(KeyCode.Escape) || InputUtil.GameplayKeyPressed(KeyCode.Tab);
#endif
}
}
+6 -3
View File
@@ -98,6 +98,9 @@ public class GameModeManager : MonoBehaviour
/// <summary>True while gameplay is suspended for a modal overlay (see <see cref="SetPaused"/>).</summary>
public bool IsPaused => _paused;
/// <summary>True while the bound local avatar is a hider (drives the overview menu's taunt button).</summary>
public bool LocalPlayerIsHider => _localRole == PlayerRole.Hider;
/// <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);
@@ -413,9 +416,9 @@ public class GameModeManager : MonoBehaviour
}
// 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.
if (_localRole == PlayerRole.Hider && !(ActiveMode is FreeCamMode) &&
// Works in every mode incl. Free Cam (its fly controls no longer use Q/E); the phase/role gate lives in
// MatchManager (RequestManualPing no-ops outside Seek / for a found hider), so a stray press is harmless.
if (_localRole == PlayerRole.Hider &&
InputUtil.GameplayKeyPressed(pingKey) && MatchManager.Instance)
MatchManager.Instance.RequestManualPing();
Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: e1ee44a22edb1ce47930b5ad143075a4
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 256
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

+117
View File
@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: f7a95cb24662ee042ae5bba5593ca55a
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 256
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant: