From ce0eb903210a8a3864cef950905e99199418e11b Mon Sep 17 00:00:00 2001 From: math-pixel <59537610+math-pixel@users.noreply.github.com> Date: Tue, 19 May 2026 15:50:11 +0200 Subject: [PATCH] inhance move --- src/components/ebike/Ebike.tsx | 18 +++----- src/world/GameCinematics.tsx | 59 +++++++++++++++++++++++++++ src/world/player/PlayerController.tsx | 38 +++++++++++------ 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index 8f57224..b0773f0 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -5,26 +5,26 @@ import { InteractableObject } from "@/components/three/interaction/InteractableO import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; import { useClonedObject } from "@/hooks/three/useClonedObject"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; -import { animateCameraTransition } from "@/world/GameCinematics"; +import { animateCameraTransition, animateCameraTransformTransition } from "@/world/GameCinematics"; import { useGameStore } from "@/managers/stores/useGameStore"; import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig"; import type { Vector3Tuple } from "@/types/three/three"; const EBIKE_MODEL_PATH = "/models/ebike/model.gltf"; -interface CameraTransform { +export interface CameraTransform { position: Vector3Tuple; rotation: Vector3Tuple; } -const EBIKE_CAMERA_TRANSFORM: CameraTransform = { +export const EBIKE_CAMERA_TRANSFORM: CameraTransform = { position: [-3, 8, 0], - rotation: [90, 90, 90], + rotation: [0, 0, 0], }; const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = { position: [3, 1.5, 0], - rotation: [0, 0, 0], + rotation: [90, 90, 0], }; interface EbikeProps { @@ -86,13 +86,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { position[1] + EBIKE_CAMERA_TRANSFORM.position[1], position[2] + EBIKE_CAMERA_TRANSFORM.position[2], ]; - const targetLookAt: Vector3Tuple = [ - position[0], - position[1] + 1, - position[2], - ]; - - animateCameraTransition(targetCamPos, targetLookAt, 1, () => { + animateCameraTransformTransition(targetCamPos, EBIKE_CAMERA_TRANSFORM.rotation, 1, () => { useGameStore.getState().setPlayerMovementMode("ebike"); }); } else { diff --git a/src/world/GameCinematics.tsx b/src/world/GameCinematics.tsx index 2464589..4ba824d 100644 --- a/src/world/GameCinematics.tsx +++ b/src/world/GameCinematics.tsx @@ -233,3 +233,62 @@ export function animateCameraTransition( 0, ); } + +export function animateCameraTransformTransition( + targetPosition: Vector3Tuple, + targetRotation: Vector3Tuple, + duration: number = 1, + onComplete?: () => void, +): void { + if (!globalCamera) { + logger.warn("GameCinematics", "Camera not found for transition"); + onComplete?.(); + return; + } + + const camera = globalCamera; + + cameraTransitionTimeline?.kill(); + useGameStore.getState().setCinematicPlaying(true); + + // Convert target rotation in degrees to quaternion + const targetEuler = new THREE.Euler( + THREE.MathUtils.degToRad(targetRotation[0]), + THREE.MathUtils.degToRad(targetRotation[1]), + THREE.MathUtils.degToRad(targetRotation[2]), + "YXZ" + ); + const startQuaternion = camera.quaternion.clone(); + const endQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); + + const transitionObj = { progress: 0 }; + + cameraTransitionTimeline = gsap.timeline({ + onUpdate: () => { + camera.quaternion.copy(startQuaternion).slerp(endQuaternion, transitionObj.progress); + }, + onComplete: () => { + cameraTransitionTimeline = null; + useGameStore.getState().setCinematicPlaying(false); + onComplete?.(); + }, + }); + + cameraTransitionTimeline.to(camera.position, { + x: targetPosition[0], + y: targetPosition[1], + z: targetPosition[2], + duration, + ease: "power2.inOut", + }); + + cameraTransitionTimeline.to( + transitionObj, + { + progress: 1, + duration, + ease: "power2.inOut", + }, + 0, + ); +} diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index e6c6145..b625133 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -27,6 +27,7 @@ import { InteractionManager } from "@/managers/InteractionManager"; import { useGameStore } from "@/managers/stores/useGameStore"; import { useSettingsStore } from "@/managers/stores/useSettingsStore"; import type { Vector3Tuple } from "@/types/three/three"; +import { EBIKE_CAMERA_TRANSFORM } from "@/components/ebike/Ebike"; type Keys = { forward: boolean; @@ -111,6 +112,7 @@ export function PlayerController({ const movementMode = useGameStore((state) => state.player.movementMode); const movementModeRef = useRef(movementMode); const prevMovementModeRef = useRef(movementMode); + const ebikeAngle = useRef(0); useEffect(() => { movementModeRef.current = movementMode; @@ -130,6 +132,9 @@ export function PlayerController({ onFloor.current = false; wantsJump.current = false; + // Initialize ebikeAngle to the bike's visual orientation (0 by default) + ebikeAngle.current = 0; + // Position the camera exactly at the EBIKE_CAMERA_TRANSFORM offset [-3, 8, 0] const cameraOffset = new THREE.Vector3(-3, 8, 0); const camPos = new THREE.Vector3() @@ -266,18 +271,23 @@ export function PlayerController({ if (movementModeRef.current === "ebike") { const turnSpeed = 1.8; // radians per second if (keys.current.left) { - camera.rotateOnWorldAxis(_up, turnSpeed * dt); + ebikeAngle.current += turnSpeed * dt; } if (keys.current.right) { - camera.rotateOnWorldAxis(_up, -turnSpeed * dt); + ebikeAngle.current -= turnSpeed * dt; } } - camera.getWorldDirection(_forward); - _forward.setY(0); - if (_forward.lengthSq() > 0) { - _forward.normalize(); + if (movementModeRef.current === "ebike") { + _forward.set(Math.sin(ebikeAngle.current), 0, Math.cos(ebikeAngle.current)).normalize(); _right.crossVectors(_forward, _up).normalize(); + } else { + camera.getWorldDirection(_forward); + _forward.setY(0); + if (_forward.lengthSq() > 0) { + _forward.normalize(); + _right.crossVectors(_forward, _up).normalize(); + } } _wishDir.set(0, 0, 0); @@ -337,24 +347,28 @@ export function PlayerController({ } } - const euler = new THREE.Euler().setFromQuaternion(camera.quaternion, "YXZ"); - if (movementModeRef.current === "ebike") { - // Offset of [-3, 8, 0] rotated by the camera's actual yaw (euler.y) - const cameraOffset = new THREE.Vector3(-3, 8, 0); - cameraOffset.applyAxisAngle(_up, euler.y); + // Offset of position rotated by e-bike angle + const cameraOffset = new THREE.Vector3(...EBIKE_CAMERA_TRANSFORM.position); + cameraOffset.applyAxisAngle(_up, ebikeAngle.current); const camPos = new THREE.Vector3() .copy(capsule.current.end) .add(cameraOffset); camera.position.copy(camPos); + + // Set camera rotation strictly to EBIKE_CAMERA_TRANSFORM.rotation + ebikeAngle.current + const pitchRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[0]); + const yawRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[1]) + ebikeAngle.current; + const rollRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[2]); + camera.rotation.set(pitchRad, yawRad, rollRad, "YXZ"); } else { camera.position.copy(capsule.current.end); } // Save player capsule end position and camera yaw globally so other components (like Ebike) can access it (window as any).playerPos = [capsule.current.end.x, capsule.current.end.y, capsule.current.end.z]; - (window as any).ebikeAngle = euler.y; + (window as any).ebikeAngle = ebikeAngle.current; }); return null;