feat: update ui and intro sequence
This commit is contained in:
Binary file not shown.
@@ -14,6 +14,7 @@ import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig";
|
|||||||
import {
|
import {
|
||||||
EBIKE_CAMERA_TRANSFORM,
|
EBIKE_CAMERA_TRANSFORM,
|
||||||
EBIKE_DROP_PLAYER_TRANSFORM,
|
EBIKE_DROP_PLAYER_TRANSFORM,
|
||||||
|
EBIKE_WORLD_SCALE,
|
||||||
EBIKE_WORLD_ROTATION_Y,
|
EBIKE_WORLD_ROTATION_Y,
|
||||||
} from "@/data/ebike/ebikeConfig";
|
} from "@/data/ebike/ebikeConfig";
|
||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
@@ -283,17 +284,18 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
|||||||
ref={groupRef}
|
ref={groupRef}
|
||||||
position={position}
|
position={position}
|
||||||
rotation={[0, EBIKE_WORLD_ROTATION_Y, 0]}
|
rotation={[0, EBIKE_WORLD_ROTATION_Y, 0]}
|
||||||
|
scale={EBIKE_WORLD_SCALE}
|
||||||
>
|
>
|
||||||
<primitive object={model} />
|
<primitive object={model} />
|
||||||
<InteractableObject
|
<InteractableObject
|
||||||
kind="trigger"
|
kind="trigger"
|
||||||
label={interactionLabel}
|
label={interactionLabel}
|
||||||
position={position}
|
position={position}
|
||||||
radius={15}
|
radius={5}
|
||||||
onPress={handleInteract}
|
onPress={handleInteract}
|
||||||
>
|
>
|
||||||
<mesh>
|
<mesh>
|
||||||
<boxGeometry args={[10, 13, 2]} />
|
<boxGeometry args={[8, 9, 2]} />
|
||||||
<meshBasicMaterial colorWrite={false} depthWrite={false} />
|
<meshBasicMaterial colorWrite={false} depthWrite={false} />
|
||||||
</mesh>
|
</mesh>
|
||||||
</InteractableObject>
|
</InteractableObject>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
import { MissionNotification } from "@/components/ui/MissionNotification";
|
import { MissionNotification } from "@/components/ui/MissionNotification";
|
||||||
import {
|
import {
|
||||||
EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS,
|
EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS,
|
||||||
EBIKE_BREAKDOWN_DIALOGUE_ID,
|
EBIKE_BREAKDOWN_DIALOGUE_ID,
|
||||||
EBIKE_INTRO_RIDE_DURATION_MS,
|
EBIKE_INTRO_BREAKDOWN_DISTANCE,
|
||||||
EBIKE_SOUNDS,
|
EBIKE_SOUNDS,
|
||||||
} from "@/data/ebike/ebikeConfig";
|
} from "@/data/ebike/ebikeConfig";
|
||||||
|
import { INTRO_MISSION_NOTIFICATION_IMAGE_PATH } from "@/data/gameplay/missionNotifications";
|
||||||
import { AudioManager } from "@/managers/AudioManager";
|
import { AudioManager } from "@/managers/AudioManager";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest";
|
import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest";
|
||||||
@@ -18,6 +20,9 @@ export function EbikeIntroSequence(): React.JSX.Element | null {
|
|||||||
const completeIntro = useGameStore((state) => state.completeIntro);
|
const completeIntro = useGameStore((state) => state.completeIntro);
|
||||||
const [breakdownDialogueDone, setBreakdownDialogueDone] = useState(false);
|
const [breakdownDialogueDone, setBreakdownDialogueDone] = useState(false);
|
||||||
const hasStartedBreakdown = useRef(false);
|
const hasStartedBreakdown = useRef(false);
|
||||||
|
const rideDistance = useRef(0);
|
||||||
|
const lastRidePosition = useRef<THREE.Vector3 | null>(null);
|
||||||
|
const currentRidePosition = useRef(new THREE.Vector3());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (introStep !== "await-ebike-mount" || movementMode !== "ebike") return;
|
if (introStep !== "await-ebike-mount" || movementMode !== "ebike") return;
|
||||||
@@ -26,16 +31,45 @@ export function EbikeIntroSequence(): React.JSX.Element | null {
|
|||||||
}, [introStep, movementMode, setIntroStep]);
|
}, [introStep, movementMode, setIntroStep]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (introStep !== "ebike-intro-ride") return undefined;
|
if (introStep !== "ebike-intro-ride") return;
|
||||||
|
|
||||||
const timeoutId = window.setTimeout(() => {
|
rideDistance.current = 0;
|
||||||
|
lastRidePosition.current = null;
|
||||||
|
}, [introStep]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (introStep !== "ebike-intro-ride" || movementMode !== "ebike") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationFrameId = 0;
|
||||||
|
const tick = () => {
|
||||||
|
const parkedPosition = window.ebikeParkedPosition;
|
||||||
|
if (parkedPosition) {
|
||||||
|
currentRidePosition.current.set(...parkedPosition);
|
||||||
|
if (!lastRidePosition.current) {
|
||||||
|
lastRidePosition.current = currentRidePosition.current.clone();
|
||||||
|
} else {
|
||||||
|
rideDistance.current += currentRidePosition.current.distanceTo(
|
||||||
|
lastRidePosition.current,
|
||||||
|
);
|
||||||
|
lastRidePosition.current.copy(currentRidePosition.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rideDistance.current >= EBIKE_INTRO_BREAKDOWN_DISTANCE) {
|
||||||
setIntroStep("ebike-breakdown");
|
setIntroStep("ebike-breakdown");
|
||||||
}, EBIKE_INTRO_RIDE_DURATION_MS);
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
animationFrameId = window.requestAnimationFrame(tick);
|
||||||
window.clearTimeout(timeoutId);
|
|
||||||
};
|
};
|
||||||
}, [introStep, setIntroStep]);
|
|
||||||
|
animationFrameId = window.requestAnimationFrame(tick);
|
||||||
|
return () => {
|
||||||
|
window.cancelAnimationFrame(animationFrameId);
|
||||||
|
};
|
||||||
|
}, [introStep, movementMode, setIntroStep]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (introStep !== "ebike-breakdown" || hasStartedBreakdown.current) {
|
if (introStep !== "ebike-breakdown" || hasStartedBreakdown.current) {
|
||||||
@@ -100,14 +134,27 @@ export function EbikeIntroSequence(): React.JSX.Element | null {
|
|||||||
}
|
}
|
||||||
}, [introStep]);
|
}, [introStep]);
|
||||||
|
|
||||||
if (introStep !== "await-ebike-mount" && introStep !== "ebike-intro-ride") {
|
if (
|
||||||
|
introStep !== "reveal" &&
|
||||||
|
introStep !== "await-ebike-mount" &&
|
||||||
|
introStep !== "ebike-intro-ride" &&
|
||||||
|
introStep !== "ebike-breakdown"
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (introStep === "ebike-breakdown") {
|
||||||
|
return <MissionNotification mission="ebike" />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MissionNotification
|
<MissionNotification
|
||||||
mission="ebike"
|
imagePath={INTRO_MISSION_NOTIFICATION_IMAGE_PATH}
|
||||||
visible={introStep === "await-ebike-mount"}
|
visible={
|
||||||
|
introStep === "reveal" ||
|
||||||
|
introStep === "await-ebike-mount" ||
|
||||||
|
introStep === "ebike-intro-ride"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ import { MISSION_NOTIFICATION_IMAGE_PATHS } from "@/data/gameplay/missionNotific
|
|||||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
interface MissionNotificationProps {
|
interface MissionNotificationProps {
|
||||||
mission: RepairMissionId;
|
mission?: RepairMissionId;
|
||||||
|
imagePath?: string;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MissionNotification({
|
export function MissionNotification({
|
||||||
mission,
|
mission,
|
||||||
|
imagePath,
|
||||||
visible = true,
|
visible = true,
|
||||||
}: MissionNotificationProps): React.JSX.Element {
|
}: MissionNotificationProps): React.JSX.Element {
|
||||||
|
const src =
|
||||||
|
imagePath ?? (mission ? MISSION_NOTIFICATION_IMAGE_PATHS[mission] : "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`mission-notification${visible ? "" : " mission-notification--hidden"}`}
|
className={`mission-notification${visible ? "" : " mission-notification--hidden"}`}
|
||||||
@@ -19,7 +24,7 @@ export function MissionNotification({
|
|||||||
<span className="mission-notification__image-wrap">
|
<span className="mission-notification__image-wrap">
|
||||||
<img
|
<img
|
||||||
className="mission-notification__image"
|
className="mission-notification__image"
|
||||||
src={MISSION_NOTIFICATION_IMAGE_PATHS[mission]}
|
src={src}
|
||||||
alt="Nouvel objectif de mission"
|
alt="Nouvel objectif de mission"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -6,19 +6,20 @@ export interface CameraTransform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const EBIKE_CAMERA_TRANSFORM: CameraTransform = {
|
export const EBIKE_CAMERA_TRANSFORM: CameraTransform = {
|
||||||
position: [-3.5, 6, 0],
|
position: [-2.6, 4.5, 0],
|
||||||
rotation: [-10, -90, 0],
|
rotation: [-10, -90, 0],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = {
|
export const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = {
|
||||||
position: [0, 1.5, -3],
|
position: [0, 1.3, -2.25],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EBIKE_WORLD_POSITION: Vector3Tuple = [61.5, 8.4, 62.4];
|
export const EBIKE_WORLD_POSITION: Vector3Tuple = [55.8, 1.75, 60.2];
|
||||||
export const EBIKE_WORLD_ROTATION_Y = 2.4107;
|
export const EBIKE_WORLD_ROTATION_Y = 2.4107;
|
||||||
|
export const EBIKE_WORLD_SCALE = 0.25;
|
||||||
|
|
||||||
export const EBIKE_INTRO_RIDE_DURATION_MS = 5000;
|
export const EBIKE_INTRO_BREAKDOWN_DISTANCE = 15;
|
||||||
export const EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS = 250;
|
export const EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS = 250;
|
||||||
|
|
||||||
export const EBIKE_MAX_SPEED = 3;
|
export const EBIKE_MAX_SPEED = 3;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
|
export const INTRO_MISSION_NOTIFICATION_IMAGE_PATH =
|
||||||
|
"/assets/world/UI/intro-mission-notification.png";
|
||||||
|
|
||||||
export const MISSION_NOTIFICATION_IMAGE_PATHS: Record<RepairMissionId, string> =
|
export const MISSION_NOTIFICATION_IMAGE_PATHS: Record<RepairMissionId, string> =
|
||||||
{
|
{
|
||||||
ebike: "/assets/world/UI/ebike-mission-notification.png",
|
ebike: "/assets/world/UI/ebike-mission-notification.png",
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export const CHARACTER_CONFIGS = {
|
|||||||
scale: [1.55, 1.55, 1.55],
|
scale: [1.55, 1.55, 1.55],
|
||||||
animations: ["idle", "walk"],
|
animations: ["idle", "walk"],
|
||||||
defaultAnimation: "idle",
|
defaultAnimation: "idle",
|
||||||
snapToTerrain: false,
|
|
||||||
},
|
},
|
||||||
fermier: {
|
fermier: {
|
||||||
id: "fermier",
|
id: "fermier",
|
||||||
|
|||||||
Reference in New Issue
Block a user