Feat/repair game #2
@@ -181,4 +181,4 @@ Current overlays:
|
|||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
The next natural step is to replace the simple install trigger with deeper repair interactions, such as selecting parts from the case, grabbing them, and validating the right replacement on a broken module.
|
The next natural step is to expand the repair interaction with case-based part selection and stricter validation of the correct replacement for each broken module.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ This document lists features that are implemented in the current codebase.
|
|||||||
|
|
||||||
- Reusable production `RepairGame` mounted for `bike`, `pylone`, and `ferme` mission states
|
- Reusable production `RepairGame` mounted for `bike`, `pylone`, and `ferme` mission states
|
||||||
- Repair mission config shared through `src/data/gameplay/repairMissions.ts`
|
- Repair mission config shared through `src/data/gameplay/repairMissions.ts`
|
||||||
- Repair-game flow supports `waiting -> inspected -> fragmented -> scanning -> repairing -> done` with `.webm` prompts, repair case spawn/opening, `E`, two-fists hold input, exploded model transition, scan visuals, replacement part display, and install trigger
|
- Repair-game flow supports `waiting -> inspected -> fragmented -> scanning -> repairing -> done` with `.webm` prompts, repair case spawn/opening, `E`, two-fists hold input, exploded model transition, scan visuals, grabbable replacement part placement, and install validation
|
||||||
|
|
||||||
## Audio
|
## Audio
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ The current user flow is:
|
|||||||
6. Press `E` or hold both fists closed for one second to move from `inspected` to `fragmented`.
|
6. Press `E` or hold both fists closed for one second to move from `inspected` to `fragmented`.
|
||||||
7. The mission object uses an exploded-model transition, then moves to `scanning`.
|
7. The mission object uses an exploded-model transition, then moves to `scanning`.
|
||||||
8. The scan visual highlights the broken area and shows the `cassé.webm` prompt.
|
8. The scan visual highlights the broken area and shows the `cassé.webm` prompt.
|
||||||
9. In `repairing`, the case opens and a replacement part appears near the case.
|
9. In `repairing`, the case opens and a grabbable replacement part appears near the case.
|
||||||
10. Press `E` on the green install target to move to `done` and show the reassembled object.
|
10. Move the replacement part close to the install target.
|
||||||
|
11. Press `E` on the green install target to move to `done` and show the reassembled object.
|
||||||
|
|
||||||
The older debug repair sandbox still exists in the physics test scene, but the production path now starts from the reusable `RepairGame` component.
|
The older debug repair sandbox still exists in the physics test scene, but the production path now starts from the reusable `RepairGame` component.
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ When the player inspects the object, `RepairGame` writes `inspected` through the
|
|||||||
|
|
||||||
In `inspected`, `RepairGame` can also move to `fragmented`. The player can use the interaction key or hold both fists closed for one second. The hand-tracking path is state-based, so it does not depend on being inside a local object interaction radius.
|
In `inspected`, `RepairGame` can also move to `fragmented`. The player can use the interaction key or hold both fists closed for one second. The hand-tracking path is state-based, so it does not depend on being inside a local object interaction radius.
|
||||||
|
|
||||||
In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, a blue scan visual and the `cassé.webm` prompt are shown before the flow advances to `repairing`. In `repairing`, the case opens, a replacement part appears, and the player can press `E` on the install target to finish the repair and move to `done`.
|
In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, a blue scan visual and the `cassé.webm` prompt are shown before the flow advances to `repairing`. In `repairing`, the case opens, a grabbable replacement part appears, and the install target only validates once that part is close enough.
|
||||||
|
|
||||||
Repair module slots and model-selection behavior still exist in the debug prototype. They can be migrated into the reusable repair flow in later steps if the repair interaction needs more depth.
|
Repair module slots and model-selection behavior still exist in the debug prototype. They can be migrated into the reusable repair flow in later steps if the repair interaction needs more depth.
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ Repair module slots and model-selection behavior still exist in the debug protot
|
|||||||
- `src/components/three/gameplay/RepairGame.tsx` composes the reusable production repair flow.
|
- `src/components/three/gameplay/RepairGame.tsx` composes the reusable production repair flow.
|
||||||
- `src/components/three/gameplay/RepairInspectionObject.tsx` handles the `waiting` inspection interaction.
|
- `src/components/three/gameplay/RepairInspectionObject.tsx` handles the `waiting` inspection interaction.
|
||||||
- `src/components/three/gameplay/RepairMissionCase.tsx` renders the mission repair case after inspection.
|
- `src/components/three/gameplay/RepairMissionCase.tsx` renders the mission repair case after inspection.
|
||||||
- `src/components/three/gameplay/RepairRepairingStep.tsx` renders the replacement part and install trigger in `repairing`.
|
- `src/components/three/gameplay/RepairRepairingStep.tsx` renders the grabbable replacement part, placement validation, and install trigger in `repairing`.
|
||||||
- `src/components/three/gameplay/RepairPromptVideo.tsx` renders `.webm` prompts inside the 3D scene.
|
- `src/components/three/gameplay/RepairPromptVideo.tsx` renders `.webm` prompts inside the 3D scene.
|
||||||
- `src/components/three/gameplay/RepairScanVisual.tsx` renders the scan halo, scan line, and broken prompt.
|
- `src/components/three/gameplay/RepairScanVisual.tsx` renders the scan halo, scan line, and broken prompt.
|
||||||
- `src/hooks/gameplay/useRepairFragmentationInput.ts` handles the `inspected -> fragmented` keyboard and hand-tracking input.
|
- `src/hooks/gameplay/useRepairFragmentationInput.ts` handles the `inspected -> fragmented` keyboard and hand-tracking input.
|
||||||
@@ -98,5 +99,5 @@ python -m backend.main
|
|||||||
- The reusable production `RepairGame` currently covers `waiting -> inspected -> fragmented -> scanning -> repairing -> done`.
|
- The reusable production `RepairGame` currently covers `waiting -> inspected -> fragmented -> scanning -> repairing -> done`.
|
||||||
- Mission progression exists in Zustand, but the full repair mission flow is still being integrated.
|
- Mission progression exists in Zustand, but the full repair mission flow is still being integrated.
|
||||||
- There is no central `GameManager` in this branch.
|
- There is no central `GameManager` in this branch.
|
||||||
- Hand tracking is available for the repair-step input, but the install interaction currently uses the shared `E` trigger path.
|
- Hand tracking is available for the two-fists input and the grabbable replacement part; final installation still uses the shared `E` trigger path.
|
||||||
- The repair-game content is configured statically in `src/data/gameplay/`.
|
- The repair-game content is configured statically in `src/data/gameplay/`.
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
import { RepairObjectModel } from "@/components/three/gameplay/RepairObjectModel";
|
import { RepairObjectModel } from "@/components/three/gameplay/RepairObjectModel";
|
||||||
import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo";
|
import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo";
|
||||||
|
import { GrabbableObject } from "@/components/three/interaction/GrabbableObject";
|
||||||
import { TriggerObject } from "@/components/three/interaction/TriggerObject";
|
import { TriggerObject } from "@/components/three/interaction/TriggerObject";
|
||||||
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
||||||
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
|
const INSTALL_TARGET_POSITION: Vector3Tuple = [0, 0.8, 0];
|
||||||
|
const INSTALL_TARGET_VECTOR = new THREE.Vector3(...INSTALL_TARGET_POSITION);
|
||||||
|
const REPLACEMENT_START_POSITION: Vector3Tuple = [0, 1.35, 1.8];
|
||||||
|
const REPAIR_INSTALL_RADIUS = 1.1;
|
||||||
|
|
||||||
interface RepairRepairingStepProps {
|
interface RepairRepairingStepProps {
|
||||||
config: RepairMissionConfig;
|
config: RepairMissionConfig;
|
||||||
@@ -12,41 +21,68 @@ export function RepairRepairingStep({
|
|||||||
config,
|
config,
|
||||||
onRepair,
|
onRepair,
|
||||||
}: RepairRepairingStepProps): React.JSX.Element {
|
}: RepairRepairingStepProps): React.JSX.Element {
|
||||||
|
const [isReplacementPlaced, setIsReplacementPlaced] = useState(false);
|
||||||
const replacementPart = config.replacementParts[0];
|
const replacementPart = config.replacementParts[0];
|
||||||
const replacementModelPath = replacementPart?.modelPath ?? config.modelPath;
|
const replacementModelPath = replacementPart?.modelPath ?? config.modelPath;
|
||||||
const replacementLabel = replacementPart?.label ?? config.label;
|
const replacementLabel = replacementPart?.label ?? config.label;
|
||||||
|
const installColor = isReplacementPlaced ? "#22c55e" : "#f97316";
|
||||||
|
const installFillColor = isReplacementPlaced ? "#86efac" : "#fed7aa";
|
||||||
|
|
||||||
|
const handleReplacementPosition = useCallback((position: THREE.Vector3) => {
|
||||||
|
const isPlaced =
|
||||||
|
position.distanceTo(INSTALL_TARGET_VECTOR) <= REPAIR_INSTALL_RADIUS;
|
||||||
|
setIsReplacementPlaced((current) =>
|
||||||
|
current === isPlaced ? current : isPlaced,
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<TriggerObject
|
<TriggerObject
|
||||||
position={[0, 0.8, 0]}
|
position={INSTALL_TARGET_POSITION}
|
||||||
colliders="ball"
|
colliders="ball"
|
||||||
label={`Installer ${replacementLabel}`}
|
label={
|
||||||
onTrigger={onRepair}
|
isReplacementPlaced
|
||||||
|
? `Installer ${replacementLabel}`
|
||||||
|
: `Approcher ${replacementLabel}`
|
||||||
|
}
|
||||||
|
onTrigger={() => {
|
||||||
|
if (!isReplacementPlaced) return;
|
||||||
|
|
||||||
|
onRepair();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<mesh>
|
<mesh>
|
||||||
<torusGeometry args={[0.95, 0.045, 12, 96]} />
|
<torusGeometry args={[0.95, 0.045, 12, 96]} />
|
||||||
<meshBasicMaterial color="#22c55e" transparent opacity={0.85} />
|
<meshBasicMaterial color={installColor} transparent opacity={0.85} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<mesh position={[0, 0.02, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
<mesh position={[0, 0.02, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
||||||
<ringGeometry args={[0.15, 0.9, 96]} />
|
<ringGeometry args={[0.15, 0.9, 96]} />
|
||||||
<meshBasicMaterial color="#86efac" transparent opacity={0.35} />
|
<meshBasicMaterial
|
||||||
|
color={installFillColor}
|
||||||
|
transparent
|
||||||
|
opacity={0.35}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
</TriggerObject>
|
</TriggerObject>
|
||||||
|
|
||||||
<group
|
<GrabbableObject
|
||||||
position={[
|
position={[
|
||||||
config.case.position[0],
|
config.case.position[0] + REPLACEMENT_START_POSITION[0],
|
||||||
config.case.position[1] + 1.2,
|
config.case.position[1] + REPLACEMENT_START_POSITION[1],
|
||||||
config.case.position[2],
|
config.case.position[2] + REPLACEMENT_START_POSITION[2],
|
||||||
]}
|
]}
|
||||||
|
colliders="ball"
|
||||||
|
handControlled
|
||||||
|
label={`Prendre ${replacementLabel}`}
|
||||||
|
onPositionChange={handleReplacementPosition}
|
||||||
>
|
>
|
||||||
<RepairObjectModel
|
<RepairObjectModel
|
||||||
label={replacementLabel}
|
label={replacementLabel}
|
||||||
modelPath={replacementModelPath}
|
modelPath={replacementModelPath}
|
||||||
scale={0.35}
|
scale={0.35}
|
||||||
/>
|
/>
|
||||||
</group>
|
</GrabbableObject>
|
||||||
|
|
||||||
<RepairPromptVideo src={config.interactUiPath} position={[0, 2.3, 0]} />
|
<RepairPromptVideo src={config.interactUiPath} position={[0, 2.3, 0]} />
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface GrabbableObjectProps {
|
|||||||
colliders?: ColliderShape;
|
colliders?: ColliderShape;
|
||||||
label?: string;
|
label?: string;
|
||||||
handControlled?: boolean;
|
handControlled?: boolean;
|
||||||
|
onPositionChange?: (position: THREE.Vector3) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const grabDebugParams = {
|
const grabDebugParams = {
|
||||||
@@ -123,6 +124,7 @@ export function GrabbableObject({
|
|||||||
colliders = GRAB_DEFAULT_COLLIDERS,
|
colliders = GRAB_DEFAULT_COLLIDERS,
|
||||||
label = GRAB_DEFAULT_LABEL,
|
label = GRAB_DEFAULT_LABEL,
|
||||||
handControlled = false,
|
handControlled = false,
|
||||||
|
onPositionChange,
|
||||||
}: GrabbableObjectProps): React.JSX.Element {
|
}: GrabbableObjectProps): React.JSX.Element {
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
const { hands } = useHandTrackingSnapshot();
|
const { hands } = useHandTrackingSnapshot();
|
||||||
@@ -172,6 +174,7 @@ export function GrabbableObject({
|
|||||||
|
|
||||||
const t = rbRef.current.translation();
|
const t = rbRef.current.translation();
|
||||||
_currentPos.set(t.x, t.y, t.z);
|
_currentPos.set(t.x, t.y, t.z);
|
||||||
|
onPositionChange?.(_currentPos);
|
||||||
|
|
||||||
if (fistHand) {
|
if (fistHand) {
|
||||||
const handCenter = getHandCenterPoint(fistHand);
|
const handCenter = getHandCenterPoint(fistHand);
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ Overlays actuels :
|
|||||||
|
|
||||||
## Prochaines étapes
|
## Prochaines étapes
|
||||||
|
|
||||||
La prochaine étape naturelle est de remplacer le trigger simple d'installation par des interactions de réparation plus profondes, comme choisir des pièces dans la mallette, les saisir et valider le bon remplacement sur un module cassé.
|
La prochaine étape naturelle est d'étendre l'interaction de réparation avec une sélection de pièces depuis la mallette et une validation plus stricte du bon remplacement pour chaque module cassé.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const featuresFr = `# Fonctionnalités implémentées
|
export const featuresFr = `# Fonctionnalités implémentées
|
||||||
@@ -442,7 +442,7 @@ Ce document liste les fonctionnalités présentes dans le code actuel.
|
|||||||
|
|
||||||
- \`RepairGame\` de production réutilisable monté pour les états de mission \`bike\`, \`pylone\` et \`ferme\`
|
- \`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\`
|
- Configuration de mission partagée via \`src/data/gameplay/repairMissions.ts\`
|
||||||
- Flow repair-game avec \`waiting -> inspected -> fragmented -> scanning -> repairing -> done\`, prompts \`.webm\`, apparition/ouverture de la mallette, touche \`E\`, hold deux poings, transition de modèle explosé, visuels de scan, pièce de remplacement et trigger d'installation
|
- Flow repair-game avec \`waiting -> inspected -> fragmented -> scanning -> repairing -> done\`, prompts \`.webm\`, apparition/ouverture de la mallette, touche \`E\`, hold deux poings, transition de modèle explosé, visuels de scan, placement d'une pièce de remplacement grabbable et validation d'installation
|
||||||
|
|
||||||
## Audio
|
## Audio
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user