Add see-through X-ray reveal overlay for players

Add a second, additive HDR emission color (_XRayColor) that reveals a
player through walls as a pulsing energy glow, driven during matches:

- Shader: XRayOverlay pass (ZTest Greater, additive) for occluded parts
  plus a matching additive term in the forward fragment for visible
  parts, so the glow shows everywhere and never double-blends. Exposes
  _XRayColor, _XRayPulseSpeed and _XRayPulseStrength for a breathing pulse.
- Render it after opaques via a RenderObjects feature (custom XRayReveal
  light-mode tag) on both PC and Mobile renderers, so the through-wall
  test is depth-sort independent and no longer drops out at some camera
  angles/distances.
- MeshPainter drives _XRayColor alpha through a framerate-independent
  fade (xrayFadeDuration) rather than a hard toggle.
- Reveal is driven per avatar on every client: all remaining un-found
  hiders glow during the Resolution phase, and a hider whose
  ConcealmentMeter EmbeddedPercent exceeds MatchManager.embeddedRevealPercent
  glows via an owner-authoritative NetworkVariable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-07-01 12:22:17 +02:00
1 parent d7104f9314
commit c54e0cf502
8 files changed
+338 -13

No files matched your search

+68 -5
View File
@@ -81,6 +81,13 @@ public class NetworkPlayerSetup : NetworkBehaviour
/// target that is already out of play.</summary>
public bool IsFound => _isFound.Value;
// Owner-authoritative "this hider is buried too deep" flag. Only the OWNER runs the ConcealmentMeter
// (it is disabled on remotes), so the owner compares its EmbeddedPercent against MatchManager's
// threshold and writes this; every client reads it to switch the through-walls X-ray glow on/off for
// this avatar. Stays false on hunters / the lobby avatar and whenever no match is running.
private readonly NetworkVariable<bool> _embeddedReveal =
new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
// Conversion-mode in-place respawn position. The owner holds authority over the NetworkTransform, so the
// server's Instantiate position is NOT honoured on the owner (it would otherwise snap to the prefab origin,
// 0,0,0). The server writes the tagged world position here in its OnNetworkSpawn so it rides the initial
@@ -104,9 +111,16 @@ public class NetworkPlayerSetup : NetworkBehaviour
private bool _lastVisible = true;
private bool _renderGate; // conversion: body force-hidden until the fresh hunter has settled into place
private MeshPainter _painter; // owns the per-avatar material instance carrying _XRayColor
private ConcealmentMeter _concealment; // owner-only embedded-percent source (disabled on remotes)
private bool _xrayApplied;
private bool _lastXRay;
public override void OnNetworkSpawn()
{
_bodyRenderers = GetComponentsInChildren<SkinnedMeshRenderer>(true);
_painter = GetComponentInChildren<MeshPainter>(true);
_concealment = GetComponentInChildren<ConcealmentMeter>(true);
_isFound.OnValueChanged += OnFoundChanged;
if (_isFound.Value) ApplyFound(); // defensive: an avatar is never normally spawned already-found
@@ -142,10 +156,10 @@ public class NetworkPlayerSetup : NetworkBehaviour
if (cc) cc.enabled = false;
// Owner cleanup: the concealment meter is a per-frame skinned-mesh bake + voxelization +
// physics overlap whose result only feeds the LOCAL player (HUD / gameplay). A remote avatar's
// concealment is computed on its own owner's machine, so running it here is pure wasted CPU.
var concealment = GetComponentInChildren<ConcealmentMeter>(true);
if (concealment) concealment.enabled = false;
// physics overlap whose result only feeds the LOCAL player (HUD / gameplay + the embedded-reveal
// flag). A remote avatar's concealment is computed on its own owner's machine, so running it here
// is pure wasted CPU.
if (_concealment) _concealment.enabled = false;
// NOTE: SkinnedColliderBaker deliberately stays ENABLED on remotes. MeshPainter's stamp replay
// (ApplyNetworkStamp -> DrawBrushGpu) rasterizes the baker's live, per-frame BakedMesh to map a
@@ -219,7 +233,12 @@ public class NetworkPlayerSetup : NetworkBehaviour
}
}
private void Update() => UpdateVisibility();
private void Update()
{
UpdateVisibility();
UpdateEmbeddedReveal();
UpdateXRayReveal();
}
// Per-client visibility: each machine decides whether to render THIS avatar's body from the LOCAL
// viewer's perspective (hunter bodies are always visible; only hider bodies are ever hidden). A found
@@ -286,6 +305,50 @@ public class NetworkPlayerSetup : NetworkBehaviour
return true;
}
// Owner-only. Publishes whether this hider is buried too deep to the replicated flag every client reads.
// Only a Hider avatar during a running match qualifies (the anti-burrow reveal targets hiders); the meter
// runs owner-side only, so this is where EmbeddedPercent is available. Cheap: a bool compare, one network
// write only on an actual edge (NetworkVariable dirties on assignment, so guard against no-op writes).
private void UpdateEmbeddedReveal()
{
if (!IsOwner) return;
bool over = false;
var match = MatchManager.Instance;
if (match && match.MatchActive && role == PlayerRole.Hider && !_isFound.Value &&
_concealment && _concealment.HasResult)
over = _concealment.EmbeddedPercent >= match.EmbeddedRevealPercent;
if (over != _embeddedReveal.Value) _embeddedReveal.Value = over;
}
// Per-client. Switches this avatar's see-through-walls X-ray glow on/off by driving the material's
// _XRayColor alpha (via MeshPainter). Two independent triggers, OR-combined: the replicated embedded
// flag (a buried hider, from its owner) and the local resolution phase (every un-found hider is revealed
// to everyone at the end). Change-guarded so the material is only touched on an actual transition.
private void UpdateXRayReveal()
{
if (!_painter) return;
bool reveal = ComputeXRay();
if (_xrayApplied && reveal == _lastXRay) return;
_painter.XRayRevealed = reveal;
_lastXRay = reveal;
_xrayApplied = true;
}
// The X-ray reveal predicate for this avatar on the local machine.
private bool ComputeXRay()
{
// Buried-hider reveal: already gated to hider + running match on the owner before it is published.
if (_embeddedReveal.Value) return true;
// Resolution reveals every remaining (un-found) hider through walls, on every client.
var match = MatchManager.Instance;
return match && match.MatchActive &&
match.LocalSubPhase == MatchManager.MatchSubPhase.Resolution &&
role == PlayerRole.Hider && !_isFound.Value;
}
// True if THIS machine's local player is a Hunter role variant (so it must not see hiders during hide).
private static bool LocalViewerIsHunter()
{