From 3fe5b32de2ecb0d152d2aa66cd795397084faf56 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Tue, 2 Jun 2026 16:33:54 +0200 Subject: [PATCH] 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) --- src/world/player/PlayerCamera.tsx | 15 ++++++++++++-- src/world/player/PlayerController.tsx | 28 +++++++++++++++------------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/world/player/PlayerCamera.tsx b/src/world/player/PlayerCamera.tsx index 3a120a2..ca60af0 100644 --- a/src/world/player/PlayerCamera.tsx +++ b/src/world/player/PlayerCamera.tsx @@ -1,18 +1,29 @@ import { useEffect } from "react"; import { useThree } from "@react-three/fiber"; import { PointerLockControls } from "@react-three/drei"; +import { useSettingsStore } from "@/managers/stores/useSettingsStore"; import { setGlobalCamera } from "@/world/GameCinematics"; export function PlayerCamera(): React.JSX.Element { const camera = useThree((state) => state.camera); + const isSettingsMenuOpen = useSettingsStore( + (state) => state.isSettingsMenuOpen, + ); useEffect(() => { setGlobalCamera(camera); return () => { setGlobalCamera(null); - document.exitPointerLock(); + if (document.pointerLockElement) { + document.exitPointerLock(); + } }; }, [camera]); - return ; + return ( + + ); } diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 563b25f..8b27a1a 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -34,6 +34,7 @@ import { EBIKE_CAMERA_TRANSFORM, EBIKE_DECELERATION_DURATION_MS, } from "@/data/ebike/ebikeConfig"; +import { useSceneMode } from "@/hooks/debug/useSceneMode"; /** Global window properties used for ebike communication */ interface EbikeGlobalState { @@ -152,6 +153,7 @@ export function PlayerController({ spawnPosition, }: PlayerControllerProps): null { const camera = useThree((state) => state.camera); + const sceneMode = useSceneMode(); const movementLocked = useRepairMovementLocked(); const terrainHeight = useTerrainHeightSampler(); const movementLockedRef = useRef(movementLocked); @@ -483,19 +485,21 @@ export function PlayerController({ } } - const groundHeight = terrainHeight.getHeight( - capsule.current.end.x, - capsule.current.end.z, - ); - if (groundHeight !== null && velocity.current.y <= 0) { - const groundOffset = getCapsuleFootY(capsule.current) - groundHeight; + if (sceneMode === "game") { + const groundHeight = terrainHeight.getHeight( + capsule.current.end.x, + capsule.current.end.z, + ); + if (groundHeight !== null && velocity.current.y <= 0) { + const groundOffset = getCapsuleFootY(capsule.current) - groundHeight; - if (groundOffset <= PLAYER_GROUND_SNAP_DISTANCE) { - capsule.current.translate( - _collisionCorrection.set(0, -groundOffset, 0), - ); - velocity.current.y = 0; - onFloor.current = true; + if (groundOffset <= PLAYER_GROUND_SNAP_DISTANCE) { + capsule.current.translate( + _collisionCorrection.set(0, -groundOffset, 0), + ); + velocity.current.y = 0; + onFloor.current = true; + } } }