From 01c583ba9619c3bd09b34540603fc627901d4a58 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Thu, 30 Apr 2026 10:02:00 +0200 Subject: [PATCH] refactor: prepare main feature gameplay object and use GLB sky model --- docs/technical/architecture.md | 4 +- public/assets/UI/cassé.webm | 3 ++ public/assets/UI/centrale.webm | 3 ++ public/assets/UI/ebike.webm | 3 ++ public/assets/UI/interagir.webm | 3 ++ public/assets/UI/laferme.webm | 3 ++ public/assets/logo/logo.jpg | 3 ++ public/models/sky/model.glb | 3 ++ public/skybox/sky.exr | 3 -- public/sounds/effect/close-malette.mp3 | 3 ++ public/sounds/{ => effect}/fa.mp3 | 0 public/sounds/effect/open-malette.mp3 | 3 ++ public/sounds/musique/test.mp3 | 3 ++ src/components/three/MainFeatureObject.tsx | 42 +++++++++++++++++++ src/components/three/MainFeatureZone.tsx | 20 +++------ src/components/three/RepairCaseModel.tsx | 39 ++++++++++------- src/components/three/SkyModel.tsx | 29 +++++++++++++ src/components/three/index.ts | 1 + src/components/ui/HandTrackingProvider.tsx | 6 +-- src/data/debug/testSceneConfig.ts | 2 +- src/data/docs/docsTranslations.ts | 4 +- src/data/world/environmentConfig.ts | 2 +- src/managers/AudioManager.ts | 7 +++- src/world/Environment.tsx | 6 +-- src/world/World.tsx | 4 +- .../debug/{TestScene.tsx => TestMap.tsx} | 6 +-- 26 files changed, 152 insertions(+), 53 deletions(-) create mode 100644 public/assets/UI/cassé.webm create mode 100644 public/assets/UI/centrale.webm create mode 100644 public/assets/UI/ebike.webm create mode 100644 public/assets/UI/interagir.webm create mode 100644 public/assets/UI/laferme.webm create mode 100644 public/assets/logo/logo.jpg create mode 100644 public/models/sky/model.glb delete mode 100644 public/skybox/sky.exr create mode 100644 public/sounds/effect/close-malette.mp3 rename public/sounds/{ => effect}/fa.mp3 (100%) create mode 100644 public/sounds/effect/open-malette.mp3 create mode 100644 public/sounds/musique/test.mp3 create mode 100644 src/components/three/MainFeatureObject.tsx create mode 100644 src/components/three/SkyModel.tsx rename src/world/debug/{TestScene.tsx => TestMap.tsx} (96%) diff --git a/docs/technical/architecture.md b/docs/technical/architecture.md index f502ff1..f25359b 100644 --- a/docs/technical/architecture.md +++ b/docs/technical/architecture.md @@ -14,7 +14,7 @@ This document describes the code that exists today in the repository. - either the map scene or the debug physics test scene - the player rig when the active camera mode is `player` - `src/world/GameMap.tsx` loads map nodes from `public/map.json`, resolves available models, and builds the collision octree. -- `src/world/debug/TestScene.tsx` provides a debug-oriented interaction and physics scene. +- `src/world/debug/TestMap.tsx` provides a debug-oriented interaction and physics map. - `src/world/player/PlayerComponent.tsx` mounts the camera and controller. - `src/world/player/PlayerController.tsx` owns pointer lock movement, jump handling, and interaction input. @@ -64,7 +64,7 @@ This document describes the code that exists today in the repository. ## Current Limitations - The repository is a prototype, not the full intended game runtime. -- `src/world/debug/TestScene.tsx` is part of the active scene composition. +- `src/world/debug/TestMap.tsx` is part of the active scene composition. - There is no central gameplay orchestrator such as `GameManager`. - Missions, zones, cinematics, and dialogue systems are not implemented. - The player uses octree collision and simple movement rules, not a complete gameplay physics stack. diff --git a/public/assets/UI/cassé.webm b/public/assets/UI/cassé.webm new file mode 100644 index 0000000..bbbe317 --- /dev/null +++ b/public/assets/UI/cassé.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56f019508cbcb5c0c4770bdad0816c7b1d332fc809d582ee4e0d90904415c745 +size 252779 diff --git a/public/assets/UI/centrale.webm b/public/assets/UI/centrale.webm new file mode 100644 index 0000000..8287a94 --- /dev/null +++ b/public/assets/UI/centrale.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd2b2be96baaab171a5f68e23c785a0ea3583c7ba71b5a9b5745213fc3b0f5f8 +size 225885 diff --git a/public/assets/UI/ebike.webm b/public/assets/UI/ebike.webm new file mode 100644 index 0000000..003fbd1 --- /dev/null +++ b/public/assets/UI/ebike.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07684c556587213b7deae75e153594d8299acde4d3a28bfe88cbd49abdcda880 +size 197560 diff --git a/public/assets/UI/interagir.webm b/public/assets/UI/interagir.webm new file mode 100644 index 0000000..d38a4c4 --- /dev/null +++ b/public/assets/UI/interagir.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dd42a107d015aa0028823e248a3a3e32e6b8cb90173b547675145fefdba4581 +size 272167 diff --git a/public/assets/UI/laferme.webm b/public/assets/UI/laferme.webm new file mode 100644 index 0000000..4b16f79 --- /dev/null +++ b/public/assets/UI/laferme.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5872877c56253cb364aceb0f39551143e142150807c7e837759677b16d22741 +size 206993 diff --git a/public/assets/logo/logo.jpg b/public/assets/logo/logo.jpg new file mode 100644 index 0000000..3617df6 --- /dev/null +++ b/public/assets/logo/logo.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:814db18091a1a822dc2ebdef9f00400c4ff943e9aa1e43151e85b6ea1c4e98cc +size 149572 diff --git a/public/models/sky/model.glb b/public/models/sky/model.glb new file mode 100644 index 0000000..0f3feb7 --- /dev/null +++ b/public/models/sky/model.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f2a39ec378538c2b28064437f794963fad4f3091dc960e5feca63dff7c60d85 +size 329420 diff --git a/public/skybox/sky.exr b/public/skybox/sky.exr deleted file mode 100644 index c6ff8e1..0000000 --- a/public/skybox/sky.exr +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:904b303c98f865526b9524b955f440e630f29d8e18a57bb7bf443fcd9715add1 -size 83079911 diff --git a/public/sounds/effect/close-malette.mp3 b/public/sounds/effect/close-malette.mp3 new file mode 100644 index 0000000..1f3f1e8 --- /dev/null +++ b/public/sounds/effect/close-malette.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bb923d3b44ca6ebecdfcc45a6cba4a5666d7f7ecb66250fc427d7dcb6b5b3b1 +size 25077 diff --git a/public/sounds/fa.mp3 b/public/sounds/effect/fa.mp3 similarity index 100% rename from public/sounds/fa.mp3 rename to public/sounds/effect/fa.mp3 diff --git a/public/sounds/effect/open-malette.mp3 b/public/sounds/effect/open-malette.mp3 new file mode 100644 index 0000000..44433f6 --- /dev/null +++ b/public/sounds/effect/open-malette.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93c7a033aa7eef8ce9fa012b5ad3ef1fa961e1f842e21641fc02025ee4d6f4fd +size 12288 diff --git a/public/sounds/musique/test.mp3 b/public/sounds/musique/test.mp3 new file mode 100644 index 0000000..d4095f6 --- /dev/null +++ b/public/sounds/musique/test.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de4036838a022ff91e10dabf4db2c0e9e41f49c5fc6e59b571100d6aaba29cd2 +size 48225313 diff --git a/src/components/three/MainFeatureObject.tsx b/src/components/three/MainFeatureObject.tsx new file mode 100644 index 0000000..4832966 --- /dev/null +++ b/src/components/three/MainFeatureObject.tsx @@ -0,0 +1,42 @@ +import { TriggerObject } from "@/components/three/TriggerObject"; +import { RepairCaseModel } from "@/components/three/RepairCaseModel"; +import { AudioManager } from "@/managers/AudioManager"; +import type { Vector3Tuple } from "@/types/three"; + +interface MainFeatureObjectProps { + position: Vector3Tuple; + open: boolean; + onToggle: () => void; +} + +const CASE_MODEL_PATH = "/models/packderelance/model.gltf"; +const CASE_SOUND_PATH = "/sounds/effect/fa.mp3"; +const CASE_OPEN_SOUND_RATE = 1.08; +const CASE_CLOSE_SOUND_RATE = 0.82; + +export function MainFeatureObject({ + position, + open, + onToggle, +}: MainFeatureObjectProps): React.JSX.Element { + return ( + { + AudioManager.getInstance().playSound(CASE_SOUND_PATH, 1, { + playbackRate: open ? CASE_CLOSE_SOUND_RATE : CASE_OPEN_SOUND_RATE, + }); + onToggle(); + }} + > + + + ); +} diff --git a/src/components/three/MainFeatureZone.tsx b/src/components/three/MainFeatureZone.tsx index 31fd500..a0a3388 100644 --- a/src/components/three/MainFeatureZone.tsx +++ b/src/components/three/MainFeatureZone.tsx @@ -1,11 +1,9 @@ import { useState } from "react"; import { Text } from "@react-three/drei"; -import { TriggerObject } from "@/components/three/TriggerObject"; +import { MainFeatureObject } from "@/components/three/MainFeatureObject"; import { ModelSelectorPlaceholder } from "@/components/three/ModelSelectorPlaceholder"; -import { RepairCaseModel } from "@/components/three/RepairCaseModel"; const ZONE_ORIGIN = [10, 0.4, -8] as const; -const CASE_MODEL_PATH = "/models/packderelance/model.gltf"; const ZONE_RADIUS = 4.2; export function MainFeatureZone(): React.JSX.Element { @@ -44,19 +42,11 @@ export function MainFeatureZone(): React.JSX.Element { Pack de Relance Feature - setCaseOpen((value) => !value)} - > - - + open={caseOpen} + onToggle={() => setCaseOpen((value) => !value)} + /> scene.clone(true), [scene]); const lidRef = useRef(null); - const closedRotationX = useRef(0); + const initialOpen = useRef(open); + const openedRotationZ = useRef(0); const parsedScale = typeof scale === "number" ? ([scale, scale, scale] as Vector3Tuple) : scale; useEffect(() => { const lid = model.getObjectByName(CASE_LID_NODE_NAME); lidRef.current = lid ?? null; - closedRotationX.current = lid?.rotation.x ?? 0; + openedRotationZ.current = lid?.rotation.z ?? 0; + + if (lid && !initialOpen.current) { + lid.rotation.z = openedRotationZ.current + CASE_CLOSED_ROTATION_OFFSET_Z; + } }, [model]); - useFrame((_, delta) => { + useEffect(() => { const lid = lidRef.current; if (!lid) return; const targetRotation = - closedRotationX.current - (open ? CASE_OPEN_ANGLE : 0); - lid.rotation.x = THREE.MathUtils.damp( - lid.rotation.x, - targetRotation, - CASE_OPEN_SPEED, - delta, - ); - }); + openedRotationZ.current + + (open ? CASE_OPEN_ROTATION_OFFSET_Z : CASE_CLOSED_ROTATION_OFFSET_Z); + gsap.to(lid.rotation, { + z: targetRotation, + duration: CASE_ANIMATION_DURATION, + ease: "power2.inOut", + overwrite: true, + }); + + return () => { + gsap.killTweensOf(lid.rotation); + }; + }, [open]); return ( diff --git a/src/components/three/SkyModel.tsx b/src/components/three/SkyModel.tsx new file mode 100644 index 0000000..c0b1a8e --- /dev/null +++ b/src/components/three/SkyModel.tsx @@ -0,0 +1,29 @@ +import { useFrame, useThree } from "@react-three/fiber"; +import { useGLTF } from "@react-three/drei"; +import { useMemo, useRef } from "react"; +import * as THREE from "three"; + +interface SkyModelProps { + modelPath: string; +} + +const SKY_MODEL_SCALE = 1; + +export function SkyModel({ modelPath }: SkyModelProps): React.JSX.Element { + const camera = useThree((state) => state.camera); + const groupRef = useRef(null); + const { scene } = useGLTF(modelPath); + const model = useMemo(() => scene.clone(true), [scene]); + + useFrame(() => { + groupRef.current?.position.copy(camera.position); + }); + + return ( + + + + ); +} + +useGLTF.preload("/models/sky/model.glb"); diff --git a/src/components/three/index.ts b/src/components/three/index.ts index 31fe607..16040d2 100644 --- a/src/components/three/index.ts +++ b/src/components/three/index.ts @@ -6,6 +6,7 @@ export type { SimpleModelConfig } from "./SimpleModel"; export { ExplodableModel } from "./ExplodableModel"; export { MainFeatureZone } from "./MainFeatureZone"; +export { MainFeatureObject } from "./MainFeatureObject"; export { ModelSelectorPlaceholder } from "./ModelSelectorPlaceholder"; export { RepairCaseModel } from "./RepairCaseModel"; diff --git a/src/components/ui/HandTrackingProvider.tsx b/src/components/ui/HandTrackingProvider.tsx index 36e9a36..f404c24 100644 --- a/src/components/ui/HandTrackingProvider.tsx +++ b/src/components/ui/HandTrackingProvider.tsx @@ -6,7 +6,6 @@ import { HandTrackingContext, } from "@/hooks/useHandTrackingSnapshot"; import { useRemoteHandTracking } from "@/hooks/useRemoteHandTracking"; -import { isDebugEnabled } from "@/utils/debug/isDebugEnabled"; export function HandTrackingProvider({ children, @@ -15,10 +14,7 @@ export function HandTrackingProvider({ }): React.JSX.Element { const sceneMode = useSceneMode(); const { nearby, holding, handHolding } = useInteraction(); - const enabled = - isDebugEnabled() && - sceneMode === "physics" && - (nearby || holding || handHolding); + const enabled = sceneMode === "physics" && (nearby || holding || handHolding); const snapshot = useRemoteHandTracking({ enabled }); return ( diff --git a/src/data/debug/testSceneConfig.ts b/src/data/debug/testSceneConfig.ts index da1edc2..545db0b 100644 --- a/src/data/debug/testSceneConfig.ts +++ b/src/data/debug/testSceneConfig.ts @@ -13,7 +13,7 @@ export const TEST_SCENE_GRABBABLE_ROUGHNESS = 0.6; export const TEST_SCENE_GRABBABLE_METALNESS = 0.1; export const TEST_SCENE_TRIGGER_POSITION: Vector3Tuple = [3, 2, -3]; -export const TEST_SCENE_TRIGGER_SOUND_PATH = "/sounds/fa.mp3"; +export const TEST_SCENE_TRIGGER_SOUND_PATH = "/sounds/effect/fa.mp3"; export const TEST_SCENE_TRIGGER_RADIUS = 0.4; export const TEST_SCENE_TRIGGER_SEGMENTS = 32; export const TEST_SCENE_TRIGGER_COLOR = "#3b82f6"; diff --git a/src/data/docs/docsTranslations.ts b/src/data/docs/docsTranslations.ts index 91a3f4f..24f16a0 100644 --- a/src/data/docs/docsTranslations.ts +++ b/src/data/docs/docsTranslations.ts @@ -99,7 +99,7 @@ Ce document décrit le code réellement présent aujourd'hui dans le dépôt. - soit la carte principale, soit la scène de test physique debug - le rig joueur quand le mode caméra actif est \`player\` - \`src/world/GameMap.tsx\` charge les modèles de carte disponibles et construit l'octree de collision. -- \`src/world/debug/TestScene.tsx\` fournit une scène orientée debug pour les interactions et la physique. +- \`src/world/debug/TestMap.tsx\` fournit une carte orientée debug pour les interactions et la physique. - \`src/world/player/Player.tsx\` monte la caméra et le contrôleur. - \`src/world/player/PlayerController.tsx\` gère le mouvement pointer lock, le saut et les inputs d'interaction. @@ -129,7 +129,7 @@ Ce document décrit le code réellement présent aujourd'hui dans le dépôt. ## Limites actuelles - Le dépôt est encore un prototype, pas le runtime complet du jeu. -- \`src/world/debug/TestScene.tsx\` fait encore partie de la composition active. +- \`src/world/debug/TestMap.tsx\` fait encore partie de la composition active. - Il n'existe pas encore d'orchestrateur gameplay central comme \`GameManager\`. - Les systèmes de missions, zones, cinématiques et dialogues ne sont pas implémentés. - Le joueur utilise une collision octree et des règles simples, pas une pile physique gameplay complète. diff --git a/src/data/world/environmentConfig.ts b/src/data/world/environmentConfig.ts index fe277fa..f5f1222 100644 --- a/src/data/world/environmentConfig.ts +++ b/src/data/world/environmentConfig.ts @@ -1,2 +1,2 @@ -export const GAME_SCENE_SKYBOX_PATH = "/skybox/sky.exr"; +export const GAME_SCENE_SKY_MODEL_PATH = "/models/sky/model.glb"; export const PHYSICS_SCENE_BACKGROUND_COLOR = "#0b1018"; diff --git a/src/managers/AudioManager.ts b/src/managers/AudioManager.ts index 1fcc256..013c33f 100644 --- a/src/managers/AudioManager.ts +++ b/src/managers/AudioManager.ts @@ -1,5 +1,9 @@ import { logger } from "@/utils/logger"; +interface PlaySoundOptions { + playbackRate?: number; +} + export class AudioManager { private static _instance: AudioManager | null = null; private readonly _audioPools = new Map(); @@ -20,9 +24,10 @@ export class AudioManager { private constructor() {} - playSound(path: string, volume = 1): void { + playSound(path: string, volume = 1, options: PlaySoundOptions = {}): void { const audio = this._acquireAudio(path); audio.volume = Math.max(0, Math.min(1, volume)); + audio.playbackRate = options.playbackRate ?? 1; audio.currentTime = 0; void audio.play().catch((error: unknown) => { diff --git a/src/world/Environment.tsx b/src/world/Environment.tsx index b81a4ef..bd71cfe 100644 --- a/src/world/Environment.tsx +++ b/src/world/Environment.tsx @@ -1,9 +1,9 @@ -import { Environment as DreiEnvironment } from "@react-three/drei"; import { - GAME_SCENE_SKYBOX_PATH, + GAME_SCENE_SKY_MODEL_PATH, PHYSICS_SCENE_BACKGROUND_COLOR, } from "@/data/world/environmentConfig"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; +import { SkyModel } from "@/components/three/SkyModel"; export function Environment(): React.JSX.Element { const sceneMode = useSceneMode(); @@ -14,5 +14,5 @@ export function Environment(): React.JSX.Element { ); } - return ; + return ; } diff --git a/src/world/World.tsx b/src/world/World.tsx index b945c38..98f9e51 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -12,7 +12,7 @@ import { Environment } from "@/world/Environment"; import { Lighting } from "@/world/Lighting"; import { GameMap } from "@/world/GameMap"; import { Player } from "@/world/player/Player"; -import { TestScene } from "@/world/debug/TestScene"; +import { TestMap } from "@/world/debug/TestMap"; export function World(): React.JSX.Element { const cameraMode = useCameraMode(); @@ -33,7 +33,7 @@ export function World(): React.JSX.Element { {sceneMode === "game" ? ( ) : ( - + )} {cameraMode !== "debug" ? ( diff --git a/src/world/debug/TestScene.tsx b/src/world/debug/TestMap.tsx similarity index 96% rename from src/world/debug/TestScene.tsx rename to src/world/debug/TestMap.tsx index 6b71af1..ee1a3ff 100644 --- a/src/world/debug/TestScene.tsx +++ b/src/world/debug/TestMap.tsx @@ -24,13 +24,11 @@ import { import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; import type { OctreeReadyHandler } from "@/types/three"; -interface TestSceneProps { +interface TestMapProps { onOctreeReady: OctreeReadyHandler; } -export function TestScene({ - onOctreeReady, -}: TestSceneProps): React.JSX.Element { +export function TestMap({ onOctreeReady }: TestMapProps): React.JSX.Element { const floorRef = useRef(null); useOctreeGraphNode(floorRef, onOctreeReady);