updated the collision for jumps and terrain
This commit is contained in:
1 parent
c00f3211ea
commit
e39aebf3a1
5 files changed
+138
-18
No files matched your search
@@ -1,13 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
PLAYER_GROUND_MIN_NORMAL_Y,
|
||||
PLAYER_AIRBORNE_PRESENTATION_DELAY_MS,
|
||||
PLAYER_JUMP_BUFFER_MS,
|
||||
PLAYER_JUMP_COYOTE_MS,
|
||||
bufferPlayerJump,
|
||||
cancelBufferedPlayerJump,
|
||||
characterVerticalMotion,
|
||||
createCharacterVerticalPresentationTiming,
|
||||
createPlayerJumpTiming,
|
||||
isWalkableGroundHit,
|
||||
updateCharacterVerticalPresentation,
|
||||
updatePlayerJumpTiming,
|
||||
} from "./playerJump";
|
||||
|
||||
@@ -63,4 +66,38 @@ describe("player jump timing", () => {
|
||||
expect(characterVerticalMotion(true, 0, 100, 150)).toBe("landing");
|
||||
expect(characterVerticalMotion(true, 0, 150, 150)).toBe("grounded");
|
||||
});
|
||||
|
||||
it("hides brief terrain gaps without playing a landing animation", () => {
|
||||
const timing = createCharacterVerticalPresentationTiming();
|
||||
expect(updateCharacterVerticalPresentation(timing, false, -0.1, 1_000, false))
|
||||
.toBe("grounded");
|
||||
expect(updateCharacterVerticalPresentation(
|
||||
timing,
|
||||
true,
|
||||
0,
|
||||
1_000 + PLAYER_AIRBORNE_PRESENTATION_DELAY_MS - 1,
|
||||
false,
|
||||
)).toBe("grounded");
|
||||
});
|
||||
|
||||
it("presents real falls and intentional jumps, then lands once", () => {
|
||||
const falling = createCharacterVerticalPresentationTiming();
|
||||
expect(updateCharacterVerticalPresentation(falling, false, -1, 2_000, false))
|
||||
.toBe("grounded");
|
||||
expect(updateCharacterVerticalPresentation(
|
||||
falling,
|
||||
false,
|
||||
-2,
|
||||
2_000 + PLAYER_AIRBORNE_PRESENTATION_DELAY_MS,
|
||||
false,
|
||||
)).toBe("falling");
|
||||
expect(updateCharacterVerticalPresentation(falling, true, 0, 2_200, false))
|
||||
.toBe("landing");
|
||||
expect(updateCharacterVerticalPresentation(falling, true, 0, 2_400, false))
|
||||
.toBe("grounded");
|
||||
|
||||
const jumping = createCharacterVerticalPresentationTiming();
|
||||
expect(updateCharacterVerticalPresentation(jumping, false, 7.2, 3_000, true))
|
||||
.toBe("rising");
|
||||
});
|
||||
});
|
||||
+51
-1
@@ -3,7 +3,8 @@ export const PLAYER_JUMP_COYOTE_MS = 100;
|
||||
export const PLAYER_JUMP_BUFFER_MS = 120;
|
||||
export const PLAYER_JUMP_LANDING_UNLOCK_MS = 80;
|
||||
export const PLAYER_LANDING_PRESENTATION_MS = 180;
|
||||
export const PLAYER_GROUND_PROBE_DISTANCE = 0.14;
|
||||
export const PLAYER_AIRBORNE_PRESENTATION_DELAY_MS = 100;
|
||||
export const PLAYER_GROUND_PROBE_DISTANCE = 0.22;
|
||||
export const PLAYER_GROUND_MIN_NORMAL_Y = 0.55;
|
||||
|
||||
export type CharacterVerticalMotion = "grounded" | "rising" | "falling" | "landing";
|
||||
@@ -15,6 +16,12 @@ export interface PlayerJumpTiming {
|
||||
lockedUntilLanding: boolean;
|
||||
}
|
||||
|
||||
export interface CharacterVerticalPresentationTiming {
|
||||
airborneStartedAtMs: number;
|
||||
airbornePresented: boolean;
|
||||
landingUntilMs: number;
|
||||
}
|
||||
|
||||
export interface GroundProbeHit {
|
||||
readonly timeOfImpact: number;
|
||||
readonly normal: { readonly y: number };
|
||||
@@ -29,6 +36,14 @@ export function createPlayerJumpTiming(nowMs = Number.NEGATIVE_INFINITY): Player
|
||||
};
|
||||
}
|
||||
|
||||
export function createCharacterVerticalPresentationTiming(): CharacterVerticalPresentationTiming {
|
||||
return {
|
||||
airborneStartedAtMs: Number.NEGATIVE_INFINITY,
|
||||
airbornePresented: false,
|
||||
landingUntilMs: Number.NEGATIVE_INFINITY,
|
||||
};
|
||||
}
|
||||
|
||||
export function bufferPlayerJump(timing: PlayerJumpTiming, requestedAtMs: number): void {
|
||||
if (!Number.isFinite(requestedAtMs)) return;
|
||||
timing.bufferedUntilMs = requestedAtMs + PLAYER_JUMP_BUFFER_MS;
|
||||
@@ -92,3 +107,38 @@ export function characterVerticalMotion(
|
||||
if (grounded) return nowMs < landingUntilMs ? "landing" : "grounded";
|
||||
return verticalVelocity > 0.15 ? "rising" : "falling";
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps tiny terrain gaps from interrupting locomotion with a fall/land pair.
|
||||
* An intentional jump is presented immediately; an incidental loss of ground
|
||||
* must persist before it becomes visible, and only visible airtime may land.
|
||||
*/
|
||||
export function updateCharacterVerticalPresentation(
|
||||
timing: CharacterVerticalPresentationTiming,
|
||||
grounded: boolean,
|
||||
verticalVelocity: number,
|
||||
nowMs: number,
|
||||
intentionalJump: boolean,
|
||||
): CharacterVerticalMotion {
|
||||
if (!Number.isFinite(nowMs)) {
|
||||
return characterVerticalMotion(grounded, verticalVelocity, 0, Number.NEGATIVE_INFINITY);
|
||||
}
|
||||
|
||||
if (!grounded) {
|
||||
if (!Number.isFinite(timing.airborneStartedAtMs)) timing.airborneStartedAtMs = nowMs;
|
||||
if (
|
||||
intentionalJump
|
||||
|| nowMs - timing.airborneStartedAtMs >= PLAYER_AIRBORNE_PRESENTATION_DELAY_MS
|
||||
) timing.airbornePresented = true;
|
||||
return timing.airbornePresented
|
||||
? characterVerticalMotion(false, verticalVelocity, nowMs, timing.landingUntilMs)
|
||||
: "grounded";
|
||||
}
|
||||
|
||||
if (timing.airbornePresented) {
|
||||
timing.landingUntilMs = nowMs + PLAYER_LANDING_PRESENTATION_MS;
|
||||
}
|
||||
timing.airborneStartedAtMs = Number.NEGATIVE_INFINITY;
|
||||
timing.airbornePresented = false;
|
||||
return characterVerticalMotion(true, verticalVelocity, nowMs, timing.landingUntilMs);
|
||||
}
|
||||
Reference in new issue
Block a user