import { useFrame, useThree } from "@react-three/fiber"; import { useEffect, useRef } from "react"; import { FEAR_SETTINGS, fearState } from "../state"; import { PointerLockControls } from "@react-three/drei"; import * as THREE from "three"; const forward = new THREE.Vector3(); const side = new THREE.Vector3(); const direction = new THREE.Vector3(); const viewDirection = new THREE.Vector3(); function usePlayerControls() { const keys = useRef({ Forward: false, Backward: false, Left: false, Right: false }); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = true; if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = true; if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = true; if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = true; }; const handleKeyUp = (e: KeyboardEvent) => { if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = false; if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = false; if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = false; if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = false; }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, []); return keys.current; } export default function Player() { const { camera } = useThree(); const controls = usePlayerControls(); const flashlightRef = useRef(null); const movementCounter = useRef(0); const confirmedSegment = useRef(0); const hasTriggeredThisSegment = useRef(false); useFrame((state, delta) => { camera.getWorldDirection(forward); forward.y = 0; forward.normalize(); side.crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize(); const moveForward = Number(controls.Forward) - Number(controls.Backward); const moveSide = Number(controls.Right) - Number(controls.Left); direction.set(0, 0, 0); if (moveForward !== 0) direction.addScaledVector(forward, moveForward); if (moveSide !== 0) direction.addScaledVector(side, moveSide); if (direction.lengthSq() > 0) direction.normalize().multiplyScalar(FEAR_SETTINGS.PLAYER_SPEED * delta); camera.position.x += direction.x; camera.position.z += direction.z; const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right; if (isMoving) { movementCounter.current += delta * 10; camera.position.y = FEAR_SETTINGS.PLAYER_HEIGHT + Math.sin(movementCounter.current) * 0.08; camera.position.x += Math.cos(movementCounter.current / 2) * 0.006; } else { const breatheTime = state.clock.elapsedTime * 1.5; const breatheY = FEAR_SETTINGS.PLAYER_HEIGHT + Math.sin(breatheTime) * 0.1; camera.position.y = THREE.MathUtils.lerp(camera.position.y, breatheY, 4 * delta); } if (flashlightRef.current) { flashlightRef.current.position.copy(camera.position); camera.getWorldDirection(viewDirection); const targetDest = new THREE.Vector3() .copy(camera.position) .addScaledVector(viewDirection, 10); flashlightRef.current.target.position.lerp(targetDest, 10 * delta); flashlightRef.current.target.updateMatrixWorld(); flashlightRef.current.intensity = FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE + Math.sin(state.clock.elapsedTime * 30) * 0.3; } const minX = -fearState.currentWidth / 2 + FEAR_SETTINGS.WALL_BUFFER; const maxX = fearState.currentWidth / 2 - FEAR_SETTINGS.WALL_BUFFER; if (camera.position.x < minX) camera.position.x = minX; if (camera.position.x > maxX) camera.position.x = maxX; const length = FEAR_SETTINGS.HALLWAY_LENGTH; const absoluteZ = -camera.position.z; const rawSegmentIndex = Math.floor(absoluteZ / length); const progressZ = ((absoluteZ % length) + length) % length / length; if (rawSegmentIndex > confirmedSegment.current && progressZ > 0.25) { if (!hasTriggeredThisSegment.current) { fearState.registerLoop('forward'); hasTriggeredThisSegment.current = true; } confirmedSegment.current = rawSegmentIndex; } else if (rawSegmentIndex < confirmedSegment.current && progressZ < 0.75) { if (!hasTriggeredThisSegment.current) { fearState.registerLoop('backward'); hasTriggeredThisSegment.current = true; } confirmedSegment.current = rawSegmentIndex; } if (rawSegmentIndex === confirmedSegment.current && progressZ > 0.35 && progressZ < 0.65) hasTriggeredThisSegment.current = false; }); return ( <> ); }