Merge e_bike + gps into develop #7

Merged
math-pixel merged 23 commits from feat/gps into develop 2026-05-28 05:55:19 +00:00
5 changed files with 77 additions and 15 deletions
Showing only changes of commit fbedb90bca - Show all commits
+65 -10
View File
@@ -3,13 +3,27 @@ import * as THREE from "three";
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 { animateCameraTransition } from "@/world/GameCinematics"; import { animateCameraTransition } from "@/world/GameCinematics";
import { useGameStore } from "@/managers/stores/useGameStore"; import { useGameStore } from "@/managers/stores/useGameStore";
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";
const EBIKE_CAMERA_POSITION: Vector3Tuple = [0, 1.5, -2];
const EBIKE_DROP_PLAYER_POSITION: Vector3Tuple = [2, 0, 0]; interface CameraTransform {
position: Vector3Tuple;
rotation: Vector3Tuple;
}
const EBIKE_CAMERA_TRANSFORM: CameraTransform = {
position: [-3, 8, 0],
rotation: [0, 90, 0],
};
const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = {
position: [3, 1.5, 0],
rotation: [0, 0, 0],
};
interface EbikeProps { interface EbikeProps {
position: Vector3Tuple; position: Vector3Tuple;
@@ -19,17 +33,38 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
const groupRef = useRef<THREE.Group>(null); const groupRef = useRef<THREE.Group>(null);
const { scene } = useLoggedGLTF(EBIKE_MODEL_PATH, { const { scene } = useLoggedGLTF(EBIKE_MODEL_PATH, {
scope: "Ebike", scope: "Ebike",
position, position: [0, 0, 0],
}); });
const model = useClonedObject(scene); const model = useClonedObject(scene);
const movementMode = useGameStore((state) => state.player.movementMode); const movementMode = useGameStore((state) => state.player.movementMode);
const debugRef = useRef({ showCameraPoints: true });
useDebugFolder("Ebike", (folder) => {
folder
.add(debugRef.current, "showCameraPoints")
.name("Show Camera Points")
.onChange((value: boolean) => {
debugRef.current.showCameraPoints = value;
});
});
const camPointPos: Vector3Tuple = [
position[0] + EBIKE_CAMERA_TRANSFORM.position[0],
position[1] + EBIKE_CAMERA_TRANSFORM.position[1],
position[2] + EBIKE_CAMERA_TRANSFORM.position[2],
];
const dropPointPos: Vector3Tuple = [
position[0] + EBIKE_DROP_PLAYER_TRANSFORM.position[0],
position[1] + EBIKE_DROP_PLAYER_TRANSFORM.position[1],
position[2] + EBIKE_DROP_PLAYER_TRANSFORM.position[2],
];
const handleInteract = (): void => { const handleInteract = (): void => {
if (movementMode === "walk") { if (movementMode === "walk") {
const targetCamPos: Vector3Tuple = [ const targetCamPos: Vector3Tuple = [
position[0] + EBIKE_CAMERA_POSITION[0], position[0] + EBIKE_CAMERA_TRANSFORM.position[0],
position[1] + EBIKE_CAMERA_POSITION[1], position[1] + EBIKE_CAMERA_TRANSFORM.position[1],
position[2] + EBIKE_CAMERA_POSITION[2], position[2] + EBIKE_CAMERA_TRANSFORM.position[2],
]; ];
const targetLookAt: Vector3Tuple = [ const targetLookAt: Vector3Tuple = [
position[0], position[0],
@@ -42,9 +77,9 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
}); });
} else { } else {
const targetCamPos: Vector3Tuple = [ const targetCamPos: Vector3Tuple = [
position[0] + EBIKE_DROP_PLAYER_POSITION[0], position[0] + EBIKE_DROP_PLAYER_TRANSFORM.position[0],
position[1] + EBIKE_DROP_PLAYER_POSITION[1], position[1] + EBIKE_DROP_PLAYER_TRANSFORM.position[1],
position[2] + EBIKE_DROP_PLAYER_POSITION[2], position[2] + EBIKE_DROP_PLAYER_TRANSFORM.position[2],
]; ];
const targetLookAt: Vector3Tuple = [ const targetLookAt: Vector3Tuple = [
position[0], position[0],
@@ -59,7 +94,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
}; };
return ( return (
<group ref={groupRef}> <group ref={groupRef} position={position}>
<primitive object={model} /> <primitive object={model} />
<InteractableObject <InteractableObject
kind="trigger" kind="trigger"
@@ -75,6 +110,26 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
<meshStandardMaterial color="red" opacity={0.5} transparent /> <meshStandardMaterial color="red" opacity={0.5} transparent />
</mesh> </mesh>
</InteractableObject> </InteractableObject>
{debugRef.current.showCameraPoints && (
<>
<mesh position={camPointPos}>
<sphereGeometry args={[0.3, 16, 16]} />
<meshStandardMaterial
color="yellow"
emissive="yellow"
emissiveIntensity={0.5}
/>
</mesh>
<mesh position={dropPointPos}>
<sphereGeometry args={[0.3, 16, 16]} />
<meshStandardMaterial
color="cyan"
emissive="cyan"
emissiveIntensity={0.5}
/>
</mesh>
</>
)}
</group> </group>
); );
} }
+1 -1
View File
@@ -181,7 +181,7 @@ function playCinematic(
let cameraTransitionTimeline: gsap.core.Timeline | null = null; let cameraTransitionTimeline: gsap.core.Timeline | null = null;
let globalCamera: THREE.Camera | null = null; let globalCamera: THREE.Camera | null = null;
export function setGlobalCamera(camera: THREE.Camera): void { export function setGlobalCamera(camera: THREE.Camera | null): void {
globalCamera = camera; globalCamera = camera;
} }
-2
View File
@@ -51,14 +51,12 @@ function StageAnchor({
export function GameStageContent(): React.JSX.Element { export function GameStageContent(): React.JSX.Element {
const mainState = useGameStore((state) => state.mainState); const mainState = useGameStore((state) => state.mainState);
const isBikeUnlocked = useGameStore((state) => state.intro.isBikeUnlocked);
return ( return (
<> <>
{mainState === "intro" ? ( {mainState === "intro" ? (
<StageAnchor color="#7dd3fc" position={[0, 4, 0]} /> <StageAnchor color="#7dd3fc" position={[0, 4, 0]} />
) : null} ) : null}
{/* {isBikeUnlocked ? <Ebike position={[0, 15, 0]} /> : null} */}
<Ebike position={[0, 5, 0]} /> <Ebike position={[0, 5, 0]} />
{GAME_REPAIR_ZONES.map((zone) => ( {GAME_REPAIR_ZONES.map((zone) => (
<RepairGame <RepairGame
+7 -1
View File
@@ -1,12 +1,18 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import { PointerLockControls } from "@react-three/drei"; import { PointerLockControls } from "@react-three/drei";
import { setGlobalCamera } from "@/world/GameCinematics";
export function PlayerCamera(): React.JSX.Element { export function PlayerCamera(): React.JSX.Element {
const camera = useThree((state) => state.camera);
useEffect(() => { useEffect(() => {
setGlobalCamera(camera);
return () => { return () => {
setGlobalCamera(null);
document.exitPointerLock(); document.exitPointerLock();
}; };
}, []); }, [camera]);
return <PointerLockControls />; return <PointerLockControls />;
} }
+4 -1
View File
@@ -108,6 +108,7 @@ export function PlayerController({
const initializedRef = useRef(false); const initializedRef = useRef(false);
const canMove = useGameStore((state) => state.missionFlow.canMove); const canMove = useGameStore((state) => state.missionFlow.canMove);
const currentSpeed = useGameStore((state) => state.player.currentSpeed); const currentSpeed = useGameStore((state) => state.player.currentSpeed);
const movementMode = useGameStore((state) => state.player.movementMode);
const capsule = useRef(createSpawnCapsule(spawnPosition)); const capsule = useRef(createSpawnCapsule(spawnPosition));
@@ -282,7 +283,9 @@ export function PlayerController({
} }
} }
camera.position.copy(capsule.current.end); if (movementMode !== "ebike") {
camera.position.copy(capsule.current.end);
}
}); });
return null; return null;