Camera-relative wall climb up/down keys to fix grab/release twitch
When running backwards toward the camera into a wall, the grab decided on the camera-relative world direction (S pushes into the wall -> grab) while the base release decided on the raw S axis (v < 0 -> immediately let go), causing a per-frame grab/release flicker. Mirror the existing _wallStrafeSign A/D latch with a new _wallClimbSign: the camera picks, per climb-key press, which of W/S climbs up (the one pushing into the wall) via Dot(camForward, wallNormal) and latches it. All up/down tests now run through the effective climb axis vEff - wall->ceiling handoff, top-out detach/hop, the alongSurface mapping and the base release - so grab and release can no longer contradict each other. Reset on grab and in ExitWallClimb. Facing the wall and grabbing with W is unchanged (vEff == v). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
cfb767f6cb
commit
0a1859cab1
2 files changed
+65
-10
No files matched your search
@@ -238,6 +238,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
private float _wallOffset; // signed mesh lean (mouse drag): + = out from the wall, - = into it; moves only the model, not the capsule
|
||||
private float _wallYaw; // body yaw offset from facing the wall (mouse drag); held until leaving
|
||||
private int _wallStrafeSign; // latched sideways direction (camera picks it once per strafe); 0 = unlatched
|
||||
private int _wallClimbSign; // latched up/down direction: whichever W/S key pushes into the wall climbs UP (camera picks it once per climb); 0 = unlatched
|
||||
private bool _offsetDragging; // true while the offset/rotation is being set (pauses camera orbit)
|
||||
private bool _climbLeftGround; // true once the capsule has left the ground while climbing (enables ground-detach)
|
||||
private Vector3 _meshBaseLocalPos; // meshOffsetTarget's authored local position; the lean offset is applied relative to this
|
||||
@@ -503,6 +504,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
_meshOffsetResidual = 0f; // fresh grab: the climb path owns the mesh, drop any leftover ease
|
||||
_wallYaw = 0f;
|
||||
_wallStrafeSign = 0;
|
||||
_wallClimbSign = 0;
|
||||
_climbLeftGround = false;
|
||||
}
|
||||
}
|
||||
@@ -533,6 +535,26 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
UpdateCeilingClass();
|
||||
bool onCeiling = _onCeiling;
|
||||
|
||||
// Map W/S to UP/DOWN by which key pushes INTO the wall, the same way A/D's _wallStrafeSign
|
||||
// picks sideways from the camera: whichever vertical key drives the player toward the wall
|
||||
// climbs up. Facing the wall that's W; with the camera on the wall's side (walking backward
|
||||
// into it) it's S. Without this remap the grab (decided on the camera-relative world
|
||||
// direction) and the base-release (decided on raw S) disagree, so the climb flickers on/off.
|
||||
// Latched once per press and held until released; only walls remap (the ceiling crawl is
|
||||
// already camera-relative). vEff is the effective climb axis used for every up/down test below.
|
||||
bool climbing = Mathf.Abs(v) > 1e-4f;
|
||||
if (!climbing)
|
||||
_wallClimbSign = 0;
|
||||
else if (_wallClimbSign == 0 && !onCeiling)
|
||||
{
|
||||
Vector3 camFwd = cameraTransform ? cameraTransform.forward : Vector3.zero;
|
||||
camFwd.y = 0f;
|
||||
// camFwd aligned with the outward wall normal = camera faces away from the wall, so
|
||||
// forward (W) moves away from it and S is the into-wall, climb-up key.
|
||||
_wallClimbSign = (cameraTransform && Vector3.Dot(camFwd, _wallNormal) > 0f) ? -1 : 1;
|
||||
}
|
||||
float vEff = _wallClimbSign != 0 ? v * _wallClimbSign : v;
|
||||
|
||||
// Jump detaches. Off a wall it pushes straight up; off a ceiling it just lets go and
|
||||
// drops. Re-grab stays blocked appropriately by UpdateWallClimbState's push-in test.
|
||||
if (jumpPressed)
|
||||
@@ -544,7 +566,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
|
||||
// Wall->ceiling hand-off: while climbing up a wall into an overhang, switch to the
|
||||
// ceiling right above the head instead of trying to mantle over a (non-existent) top.
|
||||
if (!onCeiling && v > 0f
|
||||
if (!onCeiling && vEff > 0f
|
||||
&& ProbeCeiling(out Vector3 ceilNormal, out float ceilHeadGap)
|
||||
&& ceilHeadGap <= ceilingGrabDistance)
|
||||
{
|
||||
@@ -603,7 +625,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
{
|
||||
// Hop over the lip only when actually moving up onto the flattened face (a real
|
||||
// top-out); if we reached it going down, just step off cleanly with no hop.
|
||||
if (v > 0f) TopOutDetach();
|
||||
if (vEff > 0f) TopOutDetach();
|
||||
else ExitWallClimb();
|
||||
return;
|
||||
}
|
||||
@@ -640,7 +662,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
if (_wallStrafeSign < 0)
|
||||
wallRight = -wallRight;
|
||||
|
||||
alongSurface = wallUp * v + wallRight * h;
|
||||
alongSurface = wallUp * vEff + wallRight * h;
|
||||
}
|
||||
if (alongSurface.sqrMagnitude > 1f) alongSurface.Normalize();
|
||||
bool hasMoveInput = (h * h + v * v) > 1e-4f;
|
||||
@@ -716,10 +738,10 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
_controller.Move(velocity * dt);
|
||||
|
||||
// Reaching walkable ground detaches back to normal grounded movement. The top-out hop
|
||||
// (carry over the lip) fires ONLY when topping out upward over an edge (v > 0). Arriving
|
||||
// at the floor by descending, OR pressing down/away (S, v < 0) at the wall's base while
|
||||
// grounded, just lets go cleanly with no hop -- the latter is the only detach path that
|
||||
// doesn't require having left the ground first, so a wall grabbed at its base can be
|
||||
// (carry over the lip) fires ONLY when topping out upward over an edge (vEff > 0). Arriving
|
||||
// at the floor by descending, OR pressing down/away (the effective down key, vEff < 0) at the
|
||||
// wall's base while grounded, just lets go cleanly with no hop -- the latter is the only detach
|
||||
// path that doesn't require having left the ground first, so a wall grabbed at its base can be
|
||||
// released by pressing away. We test walkable ground under the feet rather than trusting
|
||||
// isGrounded: the standoff correction can lift the capsule just off the surface, so at a
|
||||
// slope's top isGrounded stays false and the old check never fired.
|
||||
@@ -728,12 +750,12 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
{
|
||||
_climbLeftGround = true;
|
||||
}
|
||||
else if (_climbLeftGround && v > 0f)
|
||||
else if (_climbLeftGround && vEff > 0f)
|
||||
{
|
||||
TopOutDetach(); // topped out upward onto walkable ground: hop over the lip (holding W carries on)
|
||||
TopOutDetach(); // topped out upward onto walkable ground: hop over the lip (holding the up key carries on)
|
||||
return;
|
||||
}
|
||||
else if (_climbLeftGround || v < 0f)
|
||||
else if (_climbLeftGround || vEff < 0f)
|
||||
{
|
||||
ExitWallClimb(); // descended to the floor, or pulling down/away at the base: let go, no hop
|
||||
return;
|
||||
@@ -1108,6 +1130,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
_wallOffset = 0f;
|
||||
_wallYaw = 0f;
|
||||
_wallStrafeSign = 0;
|
||||
_wallClimbSign = 0;
|
||||
_offsetDragging = false;
|
||||
_climbLeftGround = false;
|
||||
_mantling = false;
|
||||
|
||||
Reference in new issue
Block a user