From 5c36ddc9e6509e0f991d85120d16f285e4469e4a Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Wed, 1 Jul 2026 22:11:28 +0200 Subject: [PATCH] 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 --- Assets/Scripts/PlayerCharacterController.cs | 30 ++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Assets/Scripts/PlayerCharacterController.cs b/Assets/Scripts/PlayerCharacterController.cs index 5b2da43..bc27939 100644 --- a/Assets/Scripts/PlayerCharacterController.cs +++ b/Assets/Scripts/PlayerCharacterController.cs @@ -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;