first move with bike
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
import { useFrame } from "@react-three/fiber";
|
||||||
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
||||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||||
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
||||||
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
||||||
import { animateCameraTransition } from "@/world/GameCinematics";
|
import { animateCameraTransition } from "@/world/GameCinematics";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
|
import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig";
|
||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
const EBIKE_MODEL_PATH = "/models/ebike/model.gltf";
|
const EBIKE_MODEL_PATH = "/models/ebike/model.gltf";
|
||||||
@@ -38,6 +40,24 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
|||||||
const model = useClonedObject(scene);
|
const model = useClonedObject(scene);
|
||||||
const movementMode = useGameStore((state) => state.player.movementMode);
|
const movementMode = useGameStore((state) => state.player.movementMode);
|
||||||
|
|
||||||
|
useFrame((state) => {
|
||||||
|
if (groupRef.current) {
|
||||||
|
if (movementMode === "ebike") {
|
||||||
|
// Follow player physical position (capsule end)
|
||||||
|
const playerPos = (window as any).playerPos || [0, 10, 0];
|
||||||
|
groupRef.current.position.set(playerPos[0], playerPos[1] - PLAYER_EYE_HEIGHT, playerPos[2]);
|
||||||
|
|
||||||
|
// Match the e-bike's actual physical steering heading
|
||||||
|
const angle = (window as any).ebikeAngle || 0;
|
||||||
|
groupRef.current.rotation.set(0, angle, 0);
|
||||||
|
} else {
|
||||||
|
// Reset to original position
|
||||||
|
groupRef.current.position.set(...position);
|
||||||
|
groupRef.current.rotation.set(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const debugRef = useRef({ showCameraPoints: true });
|
const debugRef = useRef({ showCameraPoints: true });
|
||||||
useDebugFolder("Ebike", (folder) => {
|
useDebugFolder("Ebike", (folder) => {
|
||||||
folder
|
folder
|
||||||
@@ -76,15 +96,22 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
|||||||
useGameStore.getState().setPlayerMovementMode("ebike");
|
useGameStore.getState().setPlayerMovementMode("ebike");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const currentPos = new THREE.Vector3();
|
||||||
|
if (groupRef.current) {
|
||||||
|
groupRef.current.getWorldPosition(currentPos);
|
||||||
|
} else {
|
||||||
|
currentPos.set(...position);
|
||||||
|
}
|
||||||
|
|
||||||
const targetCamPos: Vector3Tuple = [
|
const targetCamPos: Vector3Tuple = [
|
||||||
position[0] + EBIKE_DROP_PLAYER_TRANSFORM.position[0],
|
currentPos.x + EBIKE_DROP_PLAYER_TRANSFORM.position[0],
|
||||||
position[1] + EBIKE_DROP_PLAYER_TRANSFORM.position[1],
|
currentPos.y + EBIKE_DROP_PLAYER_TRANSFORM.position[1],
|
||||||
position[2] + EBIKE_DROP_PLAYER_TRANSFORM.position[2],
|
currentPos.z + EBIKE_DROP_PLAYER_TRANSFORM.position[2],
|
||||||
];
|
];
|
||||||
const targetLookAt: Vector3Tuple = [
|
const targetLookAt: Vector3Tuple = [
|
||||||
position[0],
|
currentPos.x,
|
||||||
position[1] + 1,
|
currentPos.y + 1,
|
||||||
position[2],
|
currentPos.z,
|
||||||
];
|
];
|
||||||
|
|
||||||
animateCameraTransition(targetCamPos, targetLookAt, 1, () => {
|
animateCameraTransition(targetCamPos, targetLookAt, 1, () => {
|
||||||
@@ -103,7 +130,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
|||||||
movementMode === "walk" ? "Monter sur le bike" : "Descendre du bike"
|
movementMode === "walk" ? "Monter sur le bike" : "Descendre du bike"
|
||||||
}
|
}
|
||||||
position={position}
|
position={position}
|
||||||
radius={10}
|
radius={15}
|
||||||
onPress={handleInteract}
|
onPress={handleInteract}
|
||||||
>
|
>
|
||||||
<mesh>
|
<mesh>
|
||||||
|
|||||||
@@ -110,11 +110,51 @@ export function PlayerController({
|
|||||||
const currentSpeed = useGameStore((state) => state.player.currentSpeed);
|
const currentSpeed = useGameStore((state) => state.player.currentSpeed);
|
||||||
const movementMode = useGameStore((state) => state.player.movementMode);
|
const movementMode = useGameStore((state) => state.player.movementMode);
|
||||||
const movementModeRef = useRef(movementMode);
|
const movementModeRef = useRef(movementMode);
|
||||||
|
const prevMovementModeRef = useRef(movementMode);
|
||||||
|
const ebikeAngle = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
movementModeRef.current = movementMode;
|
movementModeRef.current = movementMode;
|
||||||
}, [movementMode]);
|
}, [movementMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (movementMode === "ebike") {
|
||||||
|
// Teleport player capsule to the bike's spawning position [0, 10, 0]
|
||||||
|
const targetPos: Vector3Tuple = [0, 10, 0];
|
||||||
|
capsule.current.start.set(
|
||||||
|
targetPos[0],
|
||||||
|
targetPos[1] - PLAYER_EYE_HEIGHT + PLAYER_CAPSULE_RADIUS,
|
||||||
|
targetPos[2],
|
||||||
|
);
|
||||||
|
capsule.current.end.set(...targetPos);
|
||||||
|
velocity.current.set(0, 0, 0);
|
||||||
|
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()
|
||||||
|
.copy(capsule.current.end)
|
||||||
|
.add(cameraOffset);
|
||||||
|
camera.position.copy(camPos);
|
||||||
|
camera.lookAt(capsule.current.end.x, capsule.current.end.y + 1, capsule.current.end.z);
|
||||||
|
} else if (movementMode === "walk" && prevMovementModeRef.current === "ebike") {
|
||||||
|
// Dismount! Teleport player capsule 3 units to the right
|
||||||
|
const rightDir = new THREE.Vector3();
|
||||||
|
camera.getWorldDirection(_forward);
|
||||||
|
_forward.setY(0).normalize();
|
||||||
|
rightDir.crossVectors(_forward, _up).normalize();
|
||||||
|
|
||||||
|
const shift = rightDir.multiplyScalar(3);
|
||||||
|
capsule.current.translate(shift);
|
||||||
|
camera.position.copy(capsule.current.end);
|
||||||
|
}
|
||||||
|
prevMovementModeRef.current = movementMode;
|
||||||
|
}, [movementMode, camera]);
|
||||||
|
|
||||||
const capsule = useRef(createSpawnCapsule(spawnPosition));
|
const capsule = useRef(createSpawnCapsule(spawnPosition));
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -226,20 +266,40 @@ export function PlayerController({
|
|||||||
|
|
||||||
const dt = Math.min(delta, PLAYER_MAX_DELTA);
|
const dt = Math.min(delta, PLAYER_MAX_DELTA);
|
||||||
|
|
||||||
|
// Rotate camera on Y-axis for ebike steering
|
||||||
|
if (movementModeRef.current === "ebike") {
|
||||||
|
const turnSpeed = 1.8; // radians per second
|
||||||
|
if (keys.current.left) {
|
||||||
|
ebikeAngle.current += turnSpeed * dt;
|
||||||
|
camera.rotateOnWorldAxis(_up, turnSpeed * dt);
|
||||||
|
}
|
||||||
|
if (keys.current.right) {
|
||||||
|
ebikeAngle.current -= turnSpeed * dt;
|
||||||
|
camera.rotateOnWorldAxis(_up, -turnSpeed * dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
camera.getWorldDirection(_forward);
|
||||||
_forward.setY(0);
|
_forward.setY(0);
|
||||||
if (_forward.lengthSq() > 0) {
|
if (_forward.lengthSq() > 0) {
|
||||||
_forward.normalize();
|
_forward.normalize();
|
||||||
_right.crossVectors(_forward, _up).normalize();
|
_right.crossVectors(_forward, _up).normalize();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_wishDir.set(0, 0, 0);
|
_wishDir.set(0, 0, 0);
|
||||||
if (!movementLocked) {
|
if (!movementLocked) {
|
||||||
if (keys.current.forward) _wishDir.add(_forward);
|
if (keys.current.forward) _wishDir.add(_forward);
|
||||||
if (keys.current.backward) _wishDir.sub(_forward);
|
if (keys.current.backward) _wishDir.sub(_forward);
|
||||||
|
if (movementModeRef.current !== "ebike") {
|
||||||
if (keys.current.left) _wishDir.sub(_right);
|
if (keys.current.left) _wishDir.sub(_right);
|
||||||
if (keys.current.right) _wishDir.add(_right);
|
if (keys.current.right) _wishDir.add(_right);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (_wishDir.lengthSq() > 0) _wishDir.normalize();
|
if (_wishDir.lengthSq() > 0) _wishDir.normalize();
|
||||||
|
|
||||||
const accel = onFloor.current
|
const accel = onFloor.current
|
||||||
@@ -288,9 +348,22 @@ export function PlayerController({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movementModeRef.current !== "ebike") {
|
if (movementModeRef.current === "ebike") {
|
||||||
|
// Offset of [-3, 8, 0] rotated by e-bike angle
|
||||||
|
const cameraOffset = new THREE.Vector3(-3, 8, 0);
|
||||||
|
cameraOffset.applyAxisAngle(_up, ebikeAngle.current);
|
||||||
|
|
||||||
|
const camPos = new THREE.Vector3()
|
||||||
|
.copy(capsule.current.end)
|
||||||
|
.add(cameraOffset);
|
||||||
|
camera.position.copy(camPos);
|
||||||
|
} else {
|
||||||
camera.position.copy(capsule.current.end);
|
camera.position.copy(capsule.current.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save player capsule end position and e-bike angle 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 = ebikeAngle.current;
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user