From 6d7651dec98f15d97ee694ad507f9c0b9509462c Mon Sep 17 00:00:00 2001 From: neru Date: Mon, 1 Jun 2026 15:50:46 -0300 Subject: [PATCH] feat: implement acceleration, prevent bob from offsetting root pos --- src/app/fear/scene-components/player.tsx | 90 ++++++++++++++---------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/src/app/fear/scene-components/player.tsx b/src/app/fear/scene-components/player.tsx index 54d6c7a..3958def 100644 --- a/src/app/fear/scene-components/player.tsx +++ b/src/app/fear/scene-components/player.tsx @@ -2,13 +2,16 @@ 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(); +const targetDest = new THREE.Vector3(); + +const playerRoot = new THREE.Vector3(0, FEAR_SETTINGS.PLAYER_HEIGHT, 0); +const targetVelocity = new THREE.Vector3(); +const currentVelocity = new THREE.Vector3(); function usePlayerControls() { const keys = useRef({ Forward: false, Backward: false, Left: false, Right: false }); @@ -46,69 +49,77 @@ export default function Player() { const flashlightRef = useRef(null); const movementCounter = useRef(0); + const bobIntensity = useRef(0); const confirmedSegment = useRef(0); const hasTriggeredThisSegment = useRef(false); + useEffect(() => { + playerRoot.set(camera.position.x, FEAR_SETTINGS.PLAYER_HEIGHT, camera.position.z); + }, []); + useFrame((state, delta) => { + const dt = Math.min(delta, 0.1); + camera.getWorldDirection(forward); forward.y = 0; forward.normalize(); - - side.crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize(); + side.crossVectors(forward, THREE.Object3D.DEFAULT_UP).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); + targetVelocity.set(0, 0, 0); + if (moveForward !== 0) targetVelocity.addScaledVector(forward, moveForward); + if (moveSide !== 0) targetVelocity.addScaledVector(side, moveSide); - if (direction.lengthSq() > 0) - direction.normalize().multiplyScalar(FEAR_SETTINGS.PLAYER_SPEED * delta); + if (targetVelocity.lengthSq() > 0) + targetVelocity.normalize().multiplyScalar(FEAR_SETTINGS.PLAYER_SPEED); - camera.position.x += direction.x; - camera.position.z += direction.z; + currentVelocity.lerp(targetVelocity, 10 * dt); + + playerRoot.x += currentVelocity.x * dt; + playerRoot.z += currentVelocity.z * dt; 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; - - let bobX = 0; - let bobY = 0; + playerRoot.x = THREE.MathUtils.clamp(playerRoot.x, minX, maxX); const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right; - if (isMoving) { - movementCounter.current += delta * 10; - bobY = Math.sin(movementCounter.current) * 0.08; - bobX = Math.cos(movementCounter.current / 2) * 0.006; - - camera.position.y = FEAR_SETTINGS.PLAYER_HEIGHT + bobY; - camera.position.addScaledVector(side, bobX); - } 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); - } + + bobIntensity.current = THREE.MathUtils.lerp(bobIntensity.current, isMoving ? 1 : 0, 8 * dt); + + if (isMoving) + movementCounter.current += dt * 12; + + const moveBobY = Math.sin(movementCounter.current) * 0.06 * bobIntensity.current; + const moveBobX = Math.cos(movementCounter.current / 2) * 0.04 * bobIntensity.current; + + const breatheTime = state.clock.elapsedTime * 1.8; + const breatheBobY = Math.sin(breatheTime) * 0.03 * (1 - bobIntensity.current * 0.5); + + camera.position.copy(playerRoot); + camera.position.y += moveBobY + breatheBobY; + camera.position.addScaledVector(side, moveBobX); if (flashlightRef.current) { flashlightRef.current.position.copy(camera.position); camera.getWorldDirection(viewDirection); - const targetDest = new THREE.Vector3() + targetDest .copy(camera.position) .addScaledVector(viewDirection, 10); - flashlightRef.current.target.position.lerp(targetDest, 10 * delta); + flashlightRef.current.target.position.lerp(targetDest, 12 * dt); flashlightRef.current.target.updateMatrixWorld(); - flashlightRef.current.intensity = FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE + Math.sin(state.clock.elapsedTime * 30) * 0.3; + flashlightRef.current.intensity = + FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE + + Math.sin(state.clock.elapsedTime * 30) * 0.15 * Math.cos(state.clock.elapsedTime * 3); } const length = FEAR_SETTINGS.HALLWAY_LENGTH; - const absoluteZ = -camera.position.z; + const absoluteZ = -playerRoot.z; const rawSegmentIndex = Math.floor(absoluteZ / length); const progressZ = ((absoluteZ % length) + length) % length / length; @@ -127,8 +138,9 @@ export default function Player() { confirmedSegment.current = rawSegmentIndex; } - if (rawSegmentIndex === confirmedSegment.current && progressZ > 0.35 && progressZ < 0.65) + if (rawSegmentIndex === confirmedSegment.current && progressZ > 0.35 && progressZ < 0.65) { hasTriggeredThisSegment.current = false; + } }); return ( @@ -136,12 +148,14 @@ export default function Player() { );