Add NoClimb climb filter and kill-plane rescue to player controller
Two player-controller additions: NoClimb: colliders tagged "NoClimb" are now invisible to wall-climb and ceiling-grab. The grab detector (OnControllerColliderHit) and all three climb probes (ProbeWall, ProbeCeiling, WallInFront) skip them, so such a surface still blocks the capsule physically but can never be grabbed, re-probed onto, or treated as a ceiling. WalkableGroundBelow is left untouched (it only accepts walkable ground). The tag is read from a serialized field (default "NoClimb"); an empty tag disables the check. Rescue: a kill-plane fail-safe. The owner captures its spawn point on the first Update frame (after OnNetworkSpawn's ring offset has landed) and, if it falls below rescueMinY (-100), teleports back there with all momentum cleared, toggling the CharacterController around the write like PlaceOnSpawnRing. Owner-only; the authoritative NetworkTransform replicates the reset. Runs ahead of the control gate so it rescues in any mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
dadf9d88d4
commit
198db9a3ee
3 files changed
+60
-30
No files matched your search
@@ -75,6 +75,12 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
"animation. Only while airborne (not grounded, not wall-climbing). Scales with player size.")]
|
||||
public float fallAnimationSpeed = 4f;
|
||||
|
||||
[Header("Rescue")]
|
||||
[Tooltip("Kill-plane fail-safe: if the player falls below this world Y (e.g. off the map into the void), " +
|
||||
"they are teleported back to where this avatar spawned (spawn center + per-client ring offset) " +
|
||||
"with all momentum cleared. Owner-only; the authoritative NetworkTransform replicates the reset.")]
|
||||
public float rescueMinY = -100f;
|
||||
|
||||
[Header("Slope Sliding")]
|
||||
[Tooltip("Slide down surfaces steeper than the CharacterController's Slope Limit instead of balancing on them.")]
|
||||
public bool slideDownSteepSlopes = true;
|
||||
@@ -136,6 +142,9 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
[Tooltip("Upward hop when topping out over a wall/slope edge, so holding W carries you onto the ledge instead of dropping back onto the face and re-grabbing. In meters of jump height; 0 = no hop.")]
|
||||
public float wallTopOutBoost = 0.3f;
|
||||
|
||||
[Tooltip("Colliders with this tag are invisible to wall-climb and ceiling-grab: they still block the capsule physically, but can never be grabbed, re-probed onto, or used as a ceiling. Leave empty to disable.")]
|
||||
public string noClimbTag = "NoClimb";
|
||||
|
||||
[Tooltip("Camera orbit input; paused while dragging the wall-climb offset. Auto-found if empty.")]
|
||||
[SerializeField] private ThirdPersonOrbitInput cameraOrbit;
|
||||
|
||||
@@ -224,6 +233,8 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
private bool _movingOnGround; // snapshot: grounded & moving above the run threshold (exposed via IsMovingOnGround)
|
||||
private bool _isFalling; // snapshot: airborne & descending past the fall threshold (exposed via IsFalling)
|
||||
private float _groundOffset; // signed vertical mesh raise from the ground mouse pose-adjust (+ up / - down); folded into SetMeshLean
|
||||
private Vector3 _spawnPosition; // world position this avatar spawned at; the rescue (kill-plane) teleport target
|
||||
private bool _spawnCaptured; // false until the first Update captures the spawn position (after OnNetworkSpawn's ring offset has landed)
|
||||
|
||||
// Ground contact normal from the latest Move (for slope sliding).
|
||||
private Vector3 _groundNormal = Vector3.up;
|
||||
@@ -304,6 +315,21 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Kill-plane fail-safe (owner-only: the controller is disabled on remotes). Capture the spawn
|
||||
// point on the first frame -- by now OnNetworkSpawn's ring offset has landed -- then snap the
|
||||
// player back if they fall through the world. Runs ahead of the control gate so it rescues in
|
||||
// any mode (paint/free-cam included).
|
||||
if (!_spawnCaptured)
|
||||
{
|
||||
_spawnPosition = transform.position;
|
||||
_spawnCaptured = true;
|
||||
}
|
||||
else if (transform.position.y < rescueMinY)
|
||||
{
|
||||
RescueToSpawn();
|
||||
return;
|
||||
}
|
||||
|
||||
float h = 0f, v = 0f;
|
||||
bool jumpPressed = false;
|
||||
bool sprinting = false;
|
||||
@@ -471,6 +497,24 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
_isFalling = !_controller.isGrounded && _verticalVelocity < -fallAnimationSpeed * CapsuleScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kill-plane rescue: snap the avatar back to its captured spawn point and clear all momentum. The
|
||||
/// CharacterController overrides direct transform writes, so it is toggled around the move (same
|
||||
/// pattern as <see cref="NetworkPlayerSetup.PlaceOnSpawnRing"/>). Owner-only; the authoritative
|
||||
/// NetworkTransform replicates the new position to remotes.
|
||||
/// </summary>
|
||||
private void RescueToSpawn()
|
||||
{
|
||||
bool reenable = _controller && _controller.enabled;
|
||||
if (_controller) _controller.enabled = false;
|
||||
transform.position = _spawnPosition;
|
||||
if (_controller) _controller.enabled = reenable;
|
||||
|
||||
_verticalVelocity = 0f;
|
||||
_horizontalVelocity = Vector3.zero;
|
||||
_slideSpeed = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enters wall-climb mode only when the move input actively pushes into a wall
|
||||
/// (not on mere contact). Leaving while climbing is handled in
|
||||
@@ -950,6 +994,13 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
/// </summary>
|
||||
private static bool IsPlayerCollider(Collider c) => c.GetComponentInParent<PlayerCharacterController>();
|
||||
|
||||
/// <summary>
|
||||
/// True if the collider is tagged as non-climbable (<see cref="noClimbTag"/>): every climb probe
|
||||
/// and the grab detector skip it, so such a wall/ceiling blocks the capsule but is never grabbed,
|
||||
/// re-probed onto, or treated as a ceiling. Empty/unset tag disables the check (never throws).
|
||||
/// </summary>
|
||||
private bool IsNoClimb(Collider c) => !string.IsNullOrEmpty(noClimbTag) && c && c.CompareTag(noClimbTag);
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts from the character center toward the surface (along -_wallNormal) to find the
|
||||
/// current normal and the gap (surface clearance) to it. The capsule's support distance
|
||||
@@ -973,7 +1024,7 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
bool found = false;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (IsPlayerCollider(_wallProbe[i].collider)) // skip self and other player avatars
|
||||
if (IsPlayerCollider(_wallProbe[i].collider) || IsNoClimb(_wallProbe[i].collider)) // skip self, other player avatars, and no-climb surfaces
|
||||
continue;
|
||||
if (_wallProbe[i].distance < best)
|
||||
{
|
||||
@@ -1018,7 +1069,7 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
bool found = false;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (IsPlayerCollider(_wallProbe[i].collider))
|
||||
if (IsPlayerCollider(_wallProbe[i].collider) || IsNoClimb(_wallProbe[i].collider))
|
||||
continue;
|
||||
if (_wallProbe[i].normal.y >= CeilingNormalThreshold) // not a ceiling
|
||||
continue;
|
||||
@@ -1090,7 +1141,7 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
origin, -_wallNormal, _wallProbe, maxDist, ~0, QueryTriggerInteraction.Ignore);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (IsPlayerCollider(_wallProbe[i].collider))
|
||||
if (IsPlayerCollider(_wallProbe[i].collider) || IsNoClimb(_wallProbe[i].collider))
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
@@ -1198,7 +1249,7 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
/// </summary>
|
||||
private void OnControllerColliderHit(ControllerColliderHit hit)
|
||||
{
|
||||
if (enableWallClimb)
|
||||
if (enableWallClimb && !IsNoClimb(hit.collider))
|
||||
{
|
||||
float angle = Vector3.Angle(hit.normal, Vector3.up);
|
||||
if (angle >= wallMinAngle)
|
||||
|
||||
Reference in new issue
Block a user