Improve third-person climbing logic and planar movement detection

This commit is contained in:
2026-04-30 00:35:42 +02:00
parent e88dd59a7a
commit f022dac380
2 changed files with 28 additions and 12 deletions

View File

@@ -15,6 +15,8 @@ import {
CLIMB_INPUT_ACTIVE_THRESHOLD, CLIMB_INPUT_ACTIVE_THRESHOLD,
CLIMB_SPEED_METERS_PER_SECOND, CLIMB_SPEED_METERS_PER_SECOND,
computeClimbPlaneMovement, computeClimbPlaneMovement,
isClimbMovementIntoSurface,
resolveClimbPlanarInputDirection,
shouldEnterClimbing, shouldEnterClimbing,
shouldExitClimbing, shouldExitClimbing,
type RuntimePlayerClimbSurface type RuntimePlayerClimbSurface
@@ -665,7 +667,10 @@ export class ThirdPersonNavigationController implements NavigationController {
this.publishTelemetry(); this.publishTelemetry();
} }
private resolveClimbProbeDirection(movementYawRadians: number): Vec3 { private resolveClimbProbeDirection(
movementYawRadians: number,
inputState: ReturnType<typeof resolvePlayerStartActionInputs>
): Vec3 {
if (this.climbSurface !== null) { if (this.climbSurface !== null) {
return { return {
x: -this.climbSurface.normal.x, x: -this.climbSurface.normal.x,
@@ -674,6 +679,15 @@ export class ThirdPersonNavigationController implements NavigationController {
}; };
} }
const inputDirection = resolveClimbPlanarInputDirection(
inputState,
movementYawRadians
);
if (inputDirection.direction !== null) {
return inputDirection.direction;
}
return { return {
x: Math.sin(movementYawRadians), x: Math.sin(movementYawRadians),
y: 0, y: 0,
@@ -731,22 +745,27 @@ export class ThirdPersonNavigationController implements NavigationController {
const climbPressed = inputState.climb > CLIMB_INPUT_ACTIVE_THRESHOLD; const climbPressed = inputState.climb > CLIMB_INPUT_ACTIVE_THRESHOLD;
const jumpPressed = inputState.jump > CLIMB_INPUT_ACTIVE_THRESHOLD; const jumpPressed = inputState.jump > CLIMB_INPUT_ACTIVE_THRESHOLD;
if (!climbPressed) {
this.climbLatchBlocked = false;
}
const climbSurface = const climbSurface =
this.context.resolvePlayerClimbSurface?.( this.context.resolvePlayerClimbSurface?.(
this.feetPosition, this.feetPosition,
this.resolveClimbProbeDirection(movementYawRadians), this.resolveClimbProbeDirection(movementYawRadians, inputState),
this.standingPlayerShape, this.standingPlayerShape,
this.climbSurface this.climbSurface
) ?? null; ) ?? null;
const movementIntoSurface = isClimbMovementIntoSurface({
input: inputState,
movementYawRadians,
surface: climbSurface
});
const climbIntentActive = climbPressed || movementIntoSurface;
if (!climbIntentActive) {
this.climbLatchBlocked = false;
}
if ( if (
this.climbSurface !== null && this.climbSurface !== null &&
shouldExitClimbing({ shouldExitClimbing({
climbInput: inputState.climb,
surface: climbSurface, surface: climbSurface,
jumpPressed jumpPressed
}) })
@@ -812,10 +831,6 @@ export class ThirdPersonNavigationController implements NavigationController {
return true; return true;
} }
if (climbSurface === null && climbPressed) {
this.climbLatchBlocked = true;
}
return false; return false;
} }
@@ -824,6 +839,7 @@ export class ThirdPersonNavigationController implements NavigationController {
(this.climbLatchBlocked || (this.climbLatchBlocked ||
!shouldEnterClimbing({ !shouldEnterClimbing({
climbInput: inputState.climb, climbInput: inputState.climb,
movementIntoSurface,
surface: climbSurface, surface: climbSurface,
jumpPressed jumpPressed
})) }))

View File

@@ -143,7 +143,7 @@ describe("player climbing helpers", () => {
isClimbMovementIntoSurface({ isClimbMovementIntoSurface({
input: createInputState({ moveForward: 1 }), input: createInputState({ moveForward: 1 }),
movementYawRadians: 0, movementYawRadians: 0,
surface, surface
}) })
).toBe(true); ).toBe(true);
expect( expect(