Add player-collision toggle and make conversion statues follow it
Add a per-machine "Players Collide" switch on GameModeManager: a self-healing reconciler routes the local capsule through other avatars' body colliders via Physics.IgnoreCollision when disabled, re-asserting every frame while off to survive the late-created runtime MeshCollider, conversion respawns and teleport resets. Expose Active/ActiveVersion/ BodyCollider on NetworkPlayerSetup for the reconciler. Give MatchStatue the same Active registry + BodyCollider pattern so the frozen conversion statues obey the same toggle (they carry their own paint MeshCollider but are deliberately not NetworkPlayerSetup avatars). Also drive the statue's steady X-ray reveal in Resolution, matching an un-found hider. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
8bafa2f10a
commit
414beaa403
3 files changed
+142
-1
No files matched your search
@@ -42,6 +42,19 @@ public class GameModeManager : MonoBehaviour
|
||||
[Tooltip("Mode active at startup (0 = Player Control).")]
|
||||
public int startModeIndex = 0;
|
||||
|
||||
[Header("Player collision")]
|
||||
[Tooltip("ON = players physically block each other (default). OFF = the local player passes through every " +
|
||||
"other player - their CharacterController stops colliding with other players' body colliders. " +
|
||||
"Only affects player-vs-player; environment collision is unchanged. Can be toggled live in Play mode.")]
|
||||
[SerializeField] private bool playersCollide = true;
|
||||
|
||||
/// <summary>Whether players physically block each other. Setting it re-applies on the next frame.</summary>
|
||||
public bool PlayersCollide
|
||||
{
|
||||
get => playersCollide;
|
||||
set { playersCollide = value; _playerCollisionDirty = true; }
|
||||
}
|
||||
|
||||
public PlayerCharacterController Player => player;
|
||||
public ThirdPersonOrbitInput CameraInput => cameraInput;
|
||||
public MeshPainter Painter => painter;
|
||||
@@ -64,6 +77,12 @@ public class GameModeManager : MonoBehaviour
|
||||
private int _resumeIndex = -1;
|
||||
private bool _loadFrozen; // true while the pre-match Loading barrier is holding the local player still
|
||||
|
||||
// Player-collision reconciler state (see ReconcilePlayerCollision). Non-serialized: reset each play session.
|
||||
private bool _playerCollisionDirty = true; // force a re-apply (startup, toggle flip, inspector edit)
|
||||
private CharacterController _localCapsule; // the local player's capsule, cached to detect a respawn swap
|
||||
|
||||
private void OnValidate() => _playerCollisionDirty = true; // an inspector toggle takes effect next frame
|
||||
|
||||
/// <summary>The currently active mode (null before startup).</summary>
|
||||
public GameMode ActiveMode =>
|
||||
(_activeIndex >= 0 && _activeIndex < _modes.Count) ? _modes[_activeIndex] : null;
|
||||
@@ -318,6 +337,9 @@ public class GameModeManager : MonoBehaviour
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Independent of mode/pause: keep player-vs-player collision matching the toggle every frame.
|
||||
ReconcilePlayerCollision();
|
||||
|
||||
if (_paused) return;
|
||||
if (_modes.Count == 0) return;
|
||||
// No active mode = the inert no-player state (pre-lobby, or after ReleaseToNoPlayer). Ignore the
|
||||
@@ -393,6 +415,61 @@ public class GameModeManager : MonoBehaviour
|
||||
match.LocalSubPhase == MatchManager.MatchSubPhase.Loading;
|
||||
}
|
||||
|
||||
// Keeps player-vs-player collision in sync with `playersCollide`. Only the LOCAL owner's CharacterController
|
||||
// actively depenetrates (remote replicas run with their controller disabled), and it collides with the other
|
||||
// avatars' body MeshColliders (and conversion statues, which carry the same runtime paint collider) - so
|
||||
// making the local capsule ignore those colliders is a complete, per-machine way to let the player pass
|
||||
// through both. Environment collision is untouched.
|
||||
//
|
||||
// While OFF this re-asserts every frame (n = a handful of avatars, so it is cheap) because Physics.IgnoreCollision
|
||||
// is fragile exactly here: its state resets whenever a collider is disabled/re-enabled (teleports toggle the
|
||||
// capsule) and a freshly-spawned avatar's MeshCollider only appears a frame after spawn. Re-applying each frame
|
||||
// sidesteps all of that. While ON it does one restoring pass on a change, then stays idle (collision is the default).
|
||||
private void ReconcilePlayerCollision()
|
||||
{
|
||||
var capsule = player ? player.GetComponent<CharacterController>() : null;
|
||||
|
||||
bool changed = _playerCollisionDirty || capsule != _localCapsule;
|
||||
_playerCollisionDirty = false;
|
||||
_localCapsule = capsule;
|
||||
|
||||
if (playersCollide)
|
||||
{
|
||||
// Collisions on: only act on a transition (toggle flip / new capsule) to restore the pairs we ignored.
|
||||
if (changed) ApplyPlayerCollisionIgnore(capsule, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Collisions off: re-assert the ignore against every current avatar (see the fragility note above).
|
||||
ApplyPlayerCollisionIgnore(capsule, true);
|
||||
}
|
||||
|
||||
// Ignores (or restores) collision between the local capsule and every OTHER avatar's body collider,
|
||||
// plus every conversion statue (its own paint MeshCollider - otherwise the local player still bumps into
|
||||
// statues while the toggle is off, since statues aren't NetworkPlayerSetup avatars).
|
||||
private void ApplyPlayerCollisionIgnore(CharacterController capsule, bool ignore)
|
||||
{
|
||||
if (!capsule) return; // no local player (pre-spawn / spectator / found) - nothing to gate
|
||||
|
||||
var avatars = NetworkPlayerSetup.Active;
|
||||
for (int i = 0; i < avatars.Count; i++)
|
||||
{
|
||||
var avatar = avatars[i];
|
||||
if (!avatar || avatar.IsOwner) continue; // skip the local player's own avatar
|
||||
var col = avatar.BodyCollider;
|
||||
if (col && col.enabled) Physics.IgnoreCollision(capsule, col, ignore);
|
||||
}
|
||||
|
||||
var statues = MatchStatue.Active;
|
||||
for (int i = 0; i < statues.Count; i++)
|
||||
{
|
||||
var statue = statues[i];
|
||||
if (!statue) continue;
|
||||
var col = statue.BodyCollider;
|
||||
if (col && col.enabled) Physics.IgnoreCollision(capsule, col, ignore);
|
||||
}
|
||||
}
|
||||
|
||||
// Cached body/fur renderers of the currently bound local avatar, re-resolved only when `player` changes
|
||||
// (a lobby->match respawn rebinds mid-load), so the per-frame Loading hide doesn't walk the hierarchy each tick.
|
||||
private PlayerCharacterController _bodyHideOwner;
|
||||
|
||||
Reference in new issue
Block a user