fix(player): scope pointer lock and ground snap to game scene

PointerLockControls now targets #game-canvas and respects the
settings menu, and document.exitPointerLock() only runs when a
pointer lock is actually active. The terrain ground snap in
PlayerController is gated on sceneMode === "game" so it doesn't
fight the physics test scene's flat floor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boullay
2026-06-02 16:33:54 +02:00
parent 72cb9f5be6
commit 3fe5b32de2
2 changed files with 29 additions and 14 deletions
+12 -1
View File
@@ -1,18 +1,29 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { PointerLockControls } from "@react-three/drei"; import { PointerLockControls } from "@react-three/drei";
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
import { setGlobalCamera } from "@/world/GameCinematics"; import { setGlobalCamera } from "@/world/GameCinematics";
export function PlayerCamera(): React.JSX.Element { export function PlayerCamera(): React.JSX.Element {
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
const isSettingsMenuOpen = useSettingsStore(
(state) => state.isSettingsMenuOpen,
);
useEffect(() => { useEffect(() => {
setGlobalCamera(camera); setGlobalCamera(camera);
return () => { return () => {
setGlobalCamera(null); setGlobalCamera(null);
if (document.pointerLockElement) {
document.exitPointerLock(); document.exitPointerLock();
}
}; };
}, [camera]); }, [camera]);
return <PointerLockControls />; return (
<PointerLockControls
enabled={!isSettingsMenuOpen}
selector="#game-canvas"
/>
);
} }
+4
View File
@@ -34,6 +34,7 @@ import {
EBIKE_CAMERA_TRANSFORM, EBIKE_CAMERA_TRANSFORM,
EBIKE_DECELERATION_DURATION_MS, EBIKE_DECELERATION_DURATION_MS,
} from "@/data/ebike/ebikeConfig"; } from "@/data/ebike/ebikeConfig";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
/** Global window properties used for ebike communication */ /** Global window properties used for ebike communication */
interface EbikeGlobalState { interface EbikeGlobalState {
@@ -152,6 +153,7 @@ export function PlayerController({
spawnPosition, spawnPosition,
}: PlayerControllerProps): null { }: PlayerControllerProps): null {
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
const sceneMode = useSceneMode();
const movementLocked = useRepairMovementLocked(); const movementLocked = useRepairMovementLocked();
const terrainHeight = useTerrainHeightSampler(); const terrainHeight = useTerrainHeightSampler();
const movementLockedRef = useRef(movementLocked); const movementLockedRef = useRef(movementLocked);
@@ -483,6 +485,7 @@ export function PlayerController({
} }
} }
if (sceneMode === "game") {
const groundHeight = terrainHeight.getHeight( const groundHeight = terrainHeight.getHeight(
capsule.current.end.x, capsule.current.end.x,
capsule.current.end.z, capsule.current.end.z,
@@ -498,6 +501,7 @@ export function PlayerController({
onFloor.current = true; onFloor.current = true;
} }
} }
}
if (movementModeRef.current === "ebike") { if (movementModeRef.current === "ebike") {
let targetSteer = 0; let targetSteer = 0;