Ramp wall/ceiling climb movement for precise placement

Ramp the along-surface climb velocity via a gentle acceleration instead of
snapping to full speed, so a brief tap nudges only a little. Sprint scales
the accel so it stays snappy; releasing decelerates at the ground decel rate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-07-01 22:11:28 +02:00
1 parent 9b9cc8fab8
commit 5c36ddc9e6
1 file changed
+24 -6
+24 -6
View File
@@ -8,7 +8,7 @@ using UnityEngine.InputSystem;
/// and jumping, sliding off too-steep slopes/edges, and a wall-climb mode.
///
/// Wall-climb (entered by pushing the move input into a wall): W/S climb straight up/down the
/// wall at a constant speed, A/D strafe left/right relative to the camera, and the body always
/// wall (velocity ramps in via a gentle acceleration for precise placement), A/D strafe left/right relative to the camera, and the body always
/// faces the wall. Holding the left mouse button and dragging up/down leans the visual mesh out
/// of or into the wall (only the model + its paint collider move; the capsule stays put, so the
/// lean can sink into the wall); holding the right mouse button and dragging left/right turns the
@@ -111,6 +111,11 @@ public class PlayerCharacterController : MonoBehaviour
[Range(0f, 1f)]
public float wallClimbSpeedMultiplier = 0.5f;
[Tooltip("How quickly wall/ceiling climb movement ramps up to speed (units/second²). Kept low so brief " +
"taps nudge only a little for precise placement; scaled up while sprinting so sprint stays snappy.")]
[Min(0.01f)]
public float wallClimbAcceleration = 5f;
[Tooltip("Speed multiplier for the mantle (pulling up over the top edge), independent of the climb speed. Multiplies moveSpeed; 1 = full walk speed, >1 = snappier pull-up.")]
[Min(0f)]
public float wallMantleSpeedMultiplier = 1.5f;
@@ -229,6 +234,7 @@ public class PlayerCharacterController : MonoBehaviour
private NetworkPlayerSetup _setup; // sibling role source; null on non-networked test instances
private float _verticalVelocity;
private Vector3 _horizontalVelocity; // smoothed horizontal move velocity (accel/decel ramp)
private Vector3 _climbVelocity; // smoothed along-surface climb velocity (accel ramp for precise placement)
private bool _sprinting; // sprint key state this frame (exposed via IsSprinting)
private bool _isIdle; // snapshot: player essentially still this frame (exposed via IsIdle)
private bool _movingOnGround; // snapshot: grounded & moving above the run threshold (exposed via IsMovingOnGround)
@@ -514,6 +520,7 @@ public class PlayerCharacterController : MonoBehaviour
_verticalVelocity = 0f;
_horizontalVelocity = Vector3.zero;
_climbVelocity = Vector3.zero;
_slideSpeed = 0f;
}
@@ -577,6 +584,7 @@ public class PlayerCharacterController : MonoBehaviour
_wallStrafeSign = 0;
_wallClimbSign = 0;
_climbLeftGround = false;
_climbVelocity = Vector3.zero; // start the climb ramp from rest
}
}
@@ -710,8 +718,8 @@ public class PlayerCharacterController : MonoBehaviour
}
else
{
// W/S climb straight up/down the wall at a constant speed (camera pitch never tilts
// them). Strafe along the wall's own horizontal tangent, not the live camera-right
// W/S climb straight up/down the wall (camera pitch never tilts them; speed ramps in
// below). Strafe along the wall's own horizontal tangent, not the live camera-right
// vector: feeding camera-right in here coupled movement to the orbit camera's
// per-frame re-aim and jittered sideways motion. The camera only picks the sign.
Vector3 wallUp = Vector3.ProjectOnPlane(Vector3.up, _wallNormal);
@@ -791,9 +799,18 @@ public class PlayerCharacterController : MonoBehaviour
float climbSpeed = moveSpeed * wallClimbSpeedMultiplier;
if (sprinting) climbSpeed *= sprintSpeedMultiplier;
Vector3 velocity = Vector3.zero;
if (alongSurface.sqrMagnitude > 1e-6f)
velocity += alongSurface.normalized * climbSpeed;
// Ramp the along-surface velocity instead of snapping to full speed: a gentle acceleration
// lets a brief tap move you only a little, so you can place yourself precisely (especially
// without sprint). Sprint scales the accel too, so it still reaches its higher speed quickly;
// releasing decelerates at the ground decel rate for a prompt, controllable stop.
Vector3 targetAlong = alongSurface.sqrMagnitude > 1e-6f
? alongSurface.normalized * climbSpeed
: Vector3.zero;
float climbAccel = targetAlong.sqrMagnitude > 1e-6f
? wallClimbAcceleration * (sprinting ? sprintSpeedMultiplier : 1f)
: deceleration;
_climbVelocity = Vector3.MoveTowards(_climbVelocity, targetAlong, climbAccel * dt);
Vector3 velocity = _climbVelocity;
if (found)
{
// Smoothly hold the capsule one skin-width off the surface (flush): the lean offset no
@@ -1211,6 +1228,7 @@ public class PlayerCharacterController : MonoBehaviour
_onCeiling = false;
_lostContactTimer = 0f;
_verticalVelocity = 0f;
_climbVelocity = Vector3.zero;
// Hand the current lean off to the residual so it eases home (EaseMeshOffsetToRest)
// instead of snapping; latch the normal since the live one may shift after detaching.
_meshOffsetResidual = _wallOffset;