Add player controller telemetry functionality

This commit is contained in:
2026-04-11 19:04:52 +02:00
parent 1a0bc64234
commit 44070f06c6

View File

@@ -0,0 +1,152 @@
import type {
PlayerControllerTelemetry,
RuntimeLocomotionState,
RuntimeMovementTransitionSignals,
RuntimePlayerMovementHooks
} from "./navigation-controller";
const MOVING_SPEED_EPSILON = 0.05;
function hasWallContact(locomotionState: RuntimeLocomotionState | null): boolean {
if (locomotionState === null) {
return false;
}
return (
locomotionState.contact.collidedAxes.x ||
locomotionState.contact.collidedAxes.z
);
}
export function createEmptyRuntimeMovementTransitionSignals(): RuntimeMovementTransitionSignals {
return {
jumpStarted: false,
leftGround: false,
startedFalling: false,
landed: false,
enteredWater: false,
exitedWater: false,
wallContactStarted: false,
headBump: false
};
}
export function resolveRuntimeMovementTransitionSignals(options: {
previousLocomotionState: RuntimeLocomotionState | null;
previousInWaterVolume: boolean;
locomotionState: RuntimeLocomotionState;
inWaterVolume: boolean;
jumpStarted: boolean;
headBump: boolean;
}): RuntimeMovementTransitionSignals {
const previousGrounded = options.previousLocomotionState?.grounded ?? false;
const previousAirborneKind = options.previousLocomotionState?.airborneKind;
const previousWallContact = hasWallContact(options.previousLocomotionState);
const wallContact = hasWallContact(options.locomotionState);
return {
jumpStarted: options.jumpStarted,
leftGround: previousGrounded && !options.locomotionState.grounded,
startedFalling:
options.locomotionState.airborneKind === "falling" &&
previousAirborneKind !== "falling",
landed: !previousGrounded && options.locomotionState.grounded,
enteredWater: !options.previousInWaterVolume && options.inWaterVolume,
exitedWater: options.previousInWaterVolume && !options.inWaterVolume,
wallContactStarted: !previousWallContact && wallContact,
headBump: options.headBump
};
}
export function resolveRuntimePlayerMovementHooks(options: {
locomotionState: RuntimeLocomotionState;
inWaterVolume: boolean;
cameraSubmerged: boolean;
signals: RuntimeMovementTransitionSignals;
}): RuntimePlayerMovementHooks {
const underwaterAmount = options.cameraSubmerged
? 1
: options.inWaterVolume
? 0.55
: 0;
return {
camera: {
jumping:
options.signals.jumpStarted ||
options.locomotionState.airborneKind === "jumping",
falling:
options.signals.startedFalling ||
options.locomotionState.airborneKind === "falling",
landing: options.signals.landed,
swimming: options.locomotionState.locomotionMode === "swimming",
underwaterAmount
},
audio: {
underwaterAmount,
enteredWater: options.signals.enteredWater,
exitedWater: options.signals.exitedWater
},
animation: {
locomotionMode: options.locomotionState.locomotionMode,
airborneKind: options.locomotionState.airborneKind,
gait: options.locomotionState.gait,
moving: options.locomotionState.planarSpeed > MOVING_SPEED_EPSILON,
movementAmount: Math.max(
options.locomotionState.inputMagnitude,
Math.min(1, options.locomotionState.planarSpeed)
),
grounded: options.locomotionState.grounded,
crouched: options.locomotionState.crouched,
sprinting: options.locomotionState.sprinting,
inWater: options.inWaterVolume,
signals: options.signals
}
};
}
export function createPlayerControllerTelemetry(options: {
feetPosition: PlayerControllerTelemetry["feetPosition"];
eyePosition: PlayerControllerTelemetry["eyePosition"];
grounded: boolean;
locomotionState: RuntimeLocomotionState;
movement: PlayerControllerTelemetry["movement"];
inWaterVolume: boolean;
cameraSubmerged: boolean;
inFogVolume: boolean;
pointerLocked: boolean;
spawn: PlayerControllerTelemetry["spawn"];
previousLocomotionState: RuntimeLocomotionState | null;
previousInWaterVolume: boolean;
jumpStarted: boolean;
headBump: boolean;
}): PlayerControllerTelemetry {
const signals = resolveRuntimeMovementTransitionSignals({
previousLocomotionState: options.previousLocomotionState,
previousInWaterVolume: options.previousInWaterVolume,
locomotionState: options.locomotionState,
inWaterVolume: options.inWaterVolume,
jumpStarted: options.jumpStarted,
headBump: options.headBump
});
return {
feetPosition: options.feetPosition,
eyePosition: options.eyePosition,
grounded: options.grounded,
locomotionState: options.locomotionState,
movement: options.movement,
inWaterVolume: options.inWaterVolume,
cameraSubmerged: options.cameraSubmerged,
inFogVolume: options.inFogVolume,
pointerLocked: options.pointerLocked,
spawn: options.spawn,
signals,
hooks: resolveRuntimePlayerMovementHooks({
locomotionState: options.locomotionState,
inWaterVolume: options.inWaterVolume,
cameraSubmerged: options.cameraSubmerged,
signals
})
};
}