add: repair game inspection sub state
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { RepairInspectionObject } from "@/components/three/gameplay/RepairInspectionObject";
|
||||
import { RepairMissionCase } from "@/components/three/gameplay/RepairMissionCase";
|
||||
import { REPAIR_MISSIONS } from "@/data/gameplay/repairMissions";
|
||||
import { useRepairMissionStep } from "@/hooks/gameplay/useRepairMissionStep";
|
||||
import type { RepairMissionId } from "@/managers/stores/useGameStore";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three";
|
||||
import { toVector3Scale } from "@/utils/three/scale";
|
||||
|
||||
interface RepairGameProps extends Required<
|
||||
Pick<ModelTransformProps, "position">
|
||||
> {
|
||||
mission: RepairMissionId;
|
||||
rotation?: Vector3Tuple;
|
||||
scale?: ModelTransformProps["scale"];
|
||||
}
|
||||
|
||||
export function RepairGame({
|
||||
mission,
|
||||
position,
|
||||
rotation = [0, 0, 0],
|
||||
scale = 1,
|
||||
}: RepairGameProps): React.JSX.Element | null {
|
||||
const config = REPAIR_MISSIONS[mission];
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const setMissionStep = useGameStore((state) => state.setMissionStep);
|
||||
const step = useRepairMissionStep(mission);
|
||||
const parsedScale = toVector3Scale(scale);
|
||||
|
||||
if (mainState !== mission) return null;
|
||||
if (step === "locked") return null;
|
||||
|
||||
return (
|
||||
<group position={position} rotation={rotation} scale={parsedScale}>
|
||||
{step === "waiting" ? (
|
||||
<RepairInspectionObject
|
||||
config={config}
|
||||
worldPosition={position}
|
||||
onInspect={() => setMissionStep(mission, "inspected")}
|
||||
/>
|
||||
) : null}
|
||||
{step !== "waiting" ? <RepairMissionCase config={config} /> : null}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
||||
import { RepairObjectModel } from "@/components/three/gameplay/RepairObjectModel";
|
||||
import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo";
|
||||
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
|
||||
interface RepairInspectionObjectProps {
|
||||
config: RepairMissionConfig;
|
||||
worldPosition: Vector3Tuple;
|
||||
onInspect: () => void;
|
||||
}
|
||||
|
||||
export function RepairInspectionObject({
|
||||
config,
|
||||
worldPosition,
|
||||
onInspect,
|
||||
}: RepairInspectionObjectProps): React.JSX.Element {
|
||||
return (
|
||||
<InteractableObject
|
||||
kind="trigger"
|
||||
label={`Inspecter ${config.label}`}
|
||||
position={worldPosition}
|
||||
onPress={onInspect}
|
||||
>
|
||||
<RepairObjectModel
|
||||
label={config.label}
|
||||
modelPath={config.modelPath}
|
||||
scale={0.9}
|
||||
/>
|
||||
<RepairPromptVideo src={config.interactUiPath} />
|
||||
</InteractableObject>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { RepairCaseModel } from "@/components/three/gameplay/RepairCaseModel";
|
||||
import { REPAIR_CASE_MODEL_PATH } from "@/data/gameplay/repairCaseConfig";
|
||||
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
||||
|
||||
interface RepairMissionCaseProps {
|
||||
config: RepairMissionConfig;
|
||||
}
|
||||
|
||||
export function RepairMissionCase({
|
||||
config,
|
||||
}: RepairMissionCaseProps): React.JSX.Element {
|
||||
return (
|
||||
<RepairCaseModel
|
||||
modelPath={REPAIR_CASE_MODEL_PATH}
|
||||
open={false}
|
||||
position={config.case.position}
|
||||
rotation={config.case.rotation}
|
||||
scale={config.case.scale}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Component } from "react";
|
||||
import { SimpleModel } from "@/components/three/models/SimpleModel";
|
||||
import type { Vector3Scale, Vector3Tuple } from "@/types/three/three";
|
||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||
|
||||
interface RepairObjectModelProps {
|
||||
label: string;
|
||||
modelPath: string;
|
||||
position?: Vector3Tuple;
|
||||
rotation?: Vector3Tuple;
|
||||
scale?: Vector3Scale;
|
||||
}
|
||||
|
||||
interface RepairObjectModelBoundaryProps extends RepairObjectModelProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface RepairObjectModelBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
class RepairObjectModelBoundary extends Component<
|
||||
RepairObjectModelBoundaryProps,
|
||||
RepairObjectModelBoundaryState
|
||||
> {
|
||||
constructor(props: RepairObjectModelBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(): RepairObjectModelBoundaryState {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error): void {
|
||||
logModelLoadError(
|
||||
{
|
||||
modelPath: this.props.modelPath,
|
||||
position: this.props.position,
|
||||
rotation: this.props.rotation,
|
||||
scale: this.props.scale,
|
||||
scope: `RepairObjectModel.${this.props.label}`,
|
||||
},
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
if (this.state.hasError) {
|
||||
return <RepairObjectFallback label={this.props.label} />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export function RepairObjectModel({
|
||||
label,
|
||||
modelPath,
|
||||
position = [0, 0, 0],
|
||||
rotation = [0, 0, 0],
|
||||
scale = 1,
|
||||
}: RepairObjectModelProps): React.JSX.Element {
|
||||
return (
|
||||
<RepairObjectModelBoundary
|
||||
label={label}
|
||||
modelPath={modelPath}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
scale={scale}
|
||||
>
|
||||
<SimpleModel
|
||||
modelPath={modelPath}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
scale={scale}
|
||||
/>
|
||||
</RepairObjectModelBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
function RepairObjectFallback({ label }: { label: string }): React.JSX.Element {
|
||||
return (
|
||||
<group>
|
||||
<mesh castShadow receiveShadow>
|
||||
<boxGeometry args={[1.4, 1.4, 1.4]} />
|
||||
<meshStandardMaterial color="#facc15" roughness={0.6} wireframe />
|
||||
</mesh>
|
||||
<mesh position={[0, 1.05, 0]}>
|
||||
<sphereGeometry args={[0.08, 16, 16]} />
|
||||
<meshBasicMaterial color={label ? "#f8fafc" : "#facc15"} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Html } from "@react-three/drei";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
|
||||
interface RepairPromptVideoProps {
|
||||
src: string;
|
||||
position?: Vector3Tuple;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export function RepairPromptVideo({
|
||||
src,
|
||||
position = [0, 1.8, 0],
|
||||
size = 96,
|
||||
}: RepairPromptVideoProps): React.JSX.Element {
|
||||
return (
|
||||
<Html position={position} center transform occlude={false}>
|
||||
<video
|
||||
aria-hidden="true"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
src={src}
|
||||
style={{
|
||||
display: "block",
|
||||
height: size,
|
||||
objectFit: "contain",
|
||||
pointerEvents: "none",
|
||||
width: size,
|
||||
}}
|
||||
/>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
@@ -98,7 +98,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/GameStageContent.tsx\` est enveloppé dans le contexte Rapier \`Physics\` dans la scène de jeu de production afin que les objets gameplay de stage puissent utiliser la physique sans migrer la carte ou le joueur vers Rapier.
|
||||
- \`src/world/GameStageContent.tsx\` est enveloppé dans le contexte Rapier \`Physics\` dans la scène de jeu de production afin que les objets gameplay de stage puissent utiliser la physique sans migrer la carte ou le joueur vers Rapier. Il monte maintenant des instances réutilisables de \`RepairGame\` pour les états de mission \`bike\`, \`pylone\` et \`ferme\`.
|
||||
- \`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.
|
||||
@@ -140,12 +140,20 @@ Le joueur et l'octree de carte doivent rester hors du provider Rapier tant qu'il
|
||||
- \`src/components/debug/scene/DebugCameraControls.tsx\` monte la caméra libre debug.
|
||||
- Les contrôles globaux \`lil-gui\` incluent camera mode, scene mode, \`R3F Perf\` et \`Debug Overlay\`; les contrôles d'interaction vivent dans le dossier \`Interaction\`.
|
||||
|
||||
## Domaines de composants 3D
|
||||
|
||||
- \`src/components/three/models/\` contient les helpers de modèles réutilisables comme \`ExplodableModel\`.
|
||||
- \`src/components/three/interaction/\` contient les wrappers d'interaction réutilisables comme \`InteractableObject\`, \`TriggerObject\` et \`GrabbableObject\`.
|
||||
- \`src/components/three/handTracking/\` contient les modèles debug R3F liés au hand tracking, comme les gants.
|
||||
- \`src/components/three/gameplay/\` contient les composants de gameplay de réparation : le flow de production réutilisable \`RepairGame\`, la mallette de réparation, la zone debug repair et les slots de modules.
|
||||
- \`src/components/three/world/\` contient les objets world/environnement réutilisables comme \`SkyModel\`.
|
||||
|
||||
## Limites actuelles
|
||||
|
||||
- Le dépôt est encore un prototype, pas le runtime complet du jeu.
|
||||
- \`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.
|
||||
- L'état de mission existe dans Zustand, mais les zones, cinématiques, dialogues et le flow complet de réparation ne sont pas implémentés.
|
||||
- Le joueur utilise une collision octree et des règles simples, pas une pile physique gameplay complète.
|
||||
`;
|
||||
|
||||
@@ -347,6 +355,14 @@ Cela évite aux composants gameplay réutilisables, comme les flows de réparati
|
||||
|
||||
\`src/world/GameStageContent.tsx\` s'abonne à \`mainState\` et monte le contenu spécifique au state courant.
|
||||
|
||||
Pour les missions de réparation, il monte le composant réutilisable \`RepairGame\` avec un id de mission :
|
||||
|
||||
\`\`\`tsx
|
||||
<RepairGame mission="bike" position={[8, 0, -6]} />
|
||||
\`\`\`
|
||||
|
||||
\`RepairGame\` lit l'étape de mission active depuis le store et écrit les transitions via des actions génériques comme \`setMissionStep\`. Cela garde le composant de scène petit et évite les branches spécifiques à chaque mission dans le flow de réparation.
|
||||
|
||||
La scène peut donc évoluer progressivement vers ce pattern :
|
||||
|
||||
\`\`\`tsx
|
||||
@@ -390,7 +406,7 @@ Overlays actuels :
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
La prochaine étape naturelle est de remplacer les ancres temporaires de \`GameStageContent\` par de vrais composants de phase, par exemple \`IntroContent\`, \`BikeContent\`, \`PyloneContent\`, \`FermeContent\` et \`OutroContent\`.
|
||||
La prochaine étape naturelle est d'étendre \`RepairGame\` au-delà de \`waiting -> inspected\` avec la fragmentation, le scan, la réparation et la complétion.
|
||||
`;
|
||||
|
||||
export const featuresFr = `# Fonctionnalités implémentées
|
||||
@@ -422,6 +438,12 @@ Ce document liste les fonctionnalités présentes dans le code actuel.
|
||||
- Les objets gameplay avec physique peuvent être montés dans le contenu de stage sans remplacer la collision octree du joueur
|
||||
- Prompt d'interaction affiché pour les interactions trigger
|
||||
|
||||
## Gameplay de réparation
|
||||
|
||||
- \`RepairGame\` de production réutilisable monté pour les états de mission \`bike\`, \`pylone\` et \`ferme\`
|
||||
- Configuration de mission partagée via \`src/data/gameplay/repairMissions.ts\`
|
||||
- Première slice repair-game avec \`waiting -> inspected\`, prompts d'interaction \`.webm\` et apparition de la mallette
|
||||
|
||||
## Audio
|
||||
|
||||
- Lecture de sons one-shot pour les interactions trigger
|
||||
@@ -438,7 +460,7 @@ Ce document liste les fonctionnalités présentes dans le code actuel.
|
||||
|
||||
## Pas encore implémenté
|
||||
|
||||
- système de missions
|
||||
- système de missions complet
|
||||
- système de zones
|
||||
- système de cinématiques
|
||||
- système de dialogues
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type {
|
||||
MissionStep,
|
||||
RepairMissionId,
|
||||
} from "@/managers/stores/useGameStore";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
|
||||
export function useRepairMissionStep(mission: RepairMissionId): MissionStep {
|
||||
return useGameStore((state) => state[mission].currentStep);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RepairGame } from "@/components/three/gameplay/RepairGame";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
|
||||
@@ -33,11 +34,11 @@ export function GameStageContent(): React.JSX.Element {
|
||||
case "intro":
|
||||
return <StageAnchor color="#7dd3fc" position={[0, 4, 0]} />;
|
||||
case "bike":
|
||||
return <StageAnchor color="#facc15" position={[8, 3, -6]} />;
|
||||
return <RepairGame mission="bike" position={[8, 0, -6]} />;
|
||||
case "pylone":
|
||||
return <StageAnchor color="#a78bfa" position={[64, 6, -66]} />;
|
||||
return <RepairGame mission="pylone" position={[64, 0, -66]} />;
|
||||
case "ferme":
|
||||
return <StageAnchor color="#86efac" position={[-24, 5, 42]} />;
|
||||
return <RepairGame mission="ferme" position={[-24, 0, 42]} />;
|
||||
case "outro":
|
||||
return <StageAnchor color="#fb7185" position={[0, 6, 10]} scale={1.25} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user