refactor: clean map gameplay architecture
This commit is contained in:
@@ -8,6 +8,7 @@ export function GameFlow(): null {
|
||||
const setStep = useGameStore((state) => state.setIntroStep);
|
||||
const setActivityCity = useGameStore((state) => state.setActivityCity);
|
||||
const setCanMove = useGameStore((state) => state.setCanMove);
|
||||
const completeIntro = useGameStore((state) => state.completeIntro);
|
||||
const hasInitialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -55,10 +56,17 @@ export function GameFlow(): null {
|
||||
|
||||
if (step === "manipulation") {
|
||||
setCanMove(false);
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
completeIntro();
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [step, setStep, setActivityCity, setCanMove]);
|
||||
}, [completeIntro, step, setStep, setActivityCity, setCanMove]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -41,8 +41,27 @@ type InteractableObjectProps =
|
||||
const _cameraPos = new THREE.Vector3();
|
||||
const _cameraDir = new THREE.Vector3();
|
||||
const _objectPos = new THREE.Vector3();
|
||||
const _objectBounds = new THREE.Box3();
|
||||
const _raycaster = new THREE.Raycaster();
|
||||
|
||||
function getInteractableWorldPosition(
|
||||
group: THREE.Group,
|
||||
debugSphere: THREE.Mesh | null,
|
||||
): THREE.Vector3 {
|
||||
_objectBounds.makeEmpty();
|
||||
|
||||
for (const child of group.children) {
|
||||
if (child === debugSphere) continue;
|
||||
_objectBounds.expandByObject(child);
|
||||
}
|
||||
|
||||
if (!_objectBounds.isEmpty()) {
|
||||
return _objectBounds.getCenter(_objectPos);
|
||||
}
|
||||
|
||||
return group.getWorldPosition(_objectPos);
|
||||
}
|
||||
|
||||
function createInteractableHandle(
|
||||
props: InteractableObjectProps,
|
||||
): InteractableHandle {
|
||||
@@ -158,7 +177,7 @@ export function InteractableObject(
|
||||
const t = bodyRef.current.translation();
|
||||
_objectPos.set(t.x, t.y, t.z);
|
||||
} else if (group) {
|
||||
group.getWorldPosition(_objectPos);
|
||||
getInteractableWorldPosition(group, debugSphereRef.current);
|
||||
} else {
|
||||
_objectPos.set(...position);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useEffect, useMemo } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||
import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three";
|
||||
import { disposeObject3D } from "@/utils/three/dispose";
|
||||
|
||||
function applyShadowSettings(
|
||||
object: THREE.Object3D,
|
||||
@@ -48,12 +47,6 @@ export function SimpleModel({
|
||||
applyShadowSettings(model, castShadow, receiveShadow);
|
||||
}, [castShadow, model, receiveShadow]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
disposeObject3D(model);
|
||||
};
|
||||
}, [model]);
|
||||
|
||||
const parsedScale =
|
||||
typeof scale === "number" ? ([scale, scale, scale] as Vector3Tuple) : scale;
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { RotateCcw, StepBack, StepForward } from "lucide-react";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import { isMissionStep, MISSION_STEPS } from "@/types/gameplay/repairMission";
|
||||
import {
|
||||
GAME_STEPS,
|
||||
isGameStep,
|
||||
MAIN_GAME_STATES,
|
||||
type MainGameState,
|
||||
} from "@/types/game";
|
||||
} from "@/data/game/gameStateConfig";
|
||||
import {
|
||||
isMissionStep,
|
||||
MISSION_STEPS,
|
||||
} from "@/data/gameplay/repairMissionState";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type { MainGameState } from "@/types/game";
|
||||
|
||||
function toPascalCase(value: string): string {
|
||||
return value
|
||||
@@ -19,18 +22,18 @@ function toPascalCase(value: string): string {
|
||||
export function GameStateDebugPanel(): React.JSX.Element {
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const ebikeStep = useGameStore((state) => state.ebike.currentStep);
|
||||
const pyloneStep = useGameStore((state) => state.pylone.currentStep);
|
||||
const fermeStep = useGameStore((state) => state.ferme.currentStep);
|
||||
const pylonStep = useGameStore((state) => state.pylon.currentStep);
|
||||
const farmStep = useGameStore((state) => state.farm.currentStep);
|
||||
const detail = useGameStore((state) => {
|
||||
switch (state.mainState) {
|
||||
case "intro":
|
||||
return state.intro.currentStep;
|
||||
case "ebike":
|
||||
return state.ebike.currentStep;
|
||||
case "pylone":
|
||||
return state.pylone.currentStep;
|
||||
case "ferme":
|
||||
return state.ferme.currentStep;
|
||||
case "pylon":
|
||||
return state.pylon.currentStep;
|
||||
case "farm":
|
||||
return state.farm.currentStep;
|
||||
case "outro":
|
||||
return state.outro.hasStarted ? "started" : "waiting";
|
||||
}
|
||||
@@ -38,8 +41,8 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
const setMainState = useGameStore((state) => state.setMainState);
|
||||
const setIntroStep = useGameStore((state) => state.setIntroStep);
|
||||
const setEbikeState = useGameStore((state) => state.setEbikeState);
|
||||
const setPyloneState = useGameStore((state) => state.setPyloneState);
|
||||
const setFermeState = useGameStore((state) => state.setFermeState);
|
||||
const setPylonState = useGameStore((state) => state.setPylonState);
|
||||
const setFarmState = useGameStore((state) => state.setFarmState);
|
||||
const setOutroState = useGameStore((state) => state.setOutroState);
|
||||
const advanceGameState = useGameStore((state) => state.advanceGameState);
|
||||
const rewindGameState = useGameStore((state) => state.rewindGameState);
|
||||
@@ -72,13 +75,13 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "pylone") {
|
||||
setPyloneState({ currentStep: nextSubState });
|
||||
if (mainState === "pylon") {
|
||||
setPylonState({ currentStep: nextSubState });
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "ferme") {
|
||||
setFermeState({ currentStep: nextSubState });
|
||||
if (mainState === "farm") {
|
||||
setFarmState({ currentStep: nextSubState });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -86,18 +89,34 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
function setDebugMainState(nextMainState: MainGameState): void {
|
||||
setMainState(nextMainState);
|
||||
|
||||
if (
|
||||
nextMainState === "pylon" ||
|
||||
nextMainState === "farm" ||
|
||||
nextMainState === "outro"
|
||||
) {
|
||||
setEbikeState({ currentStep: "done", isRepaired: true });
|
||||
}
|
||||
|
||||
if (nextMainState === "farm" || nextMainState === "outro") {
|
||||
setPylonState({ currentStep: "done", isPowered: true });
|
||||
}
|
||||
|
||||
if (nextMainState === "outro") {
|
||||
setFarmState({ currentStep: "done", irrigationFixed: true });
|
||||
}
|
||||
|
||||
if (nextMainState === "ebike" && ebikeStep === "locked") {
|
||||
setEbikeState({ currentStep: "waiting" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextMainState === "pylone" && pyloneStep === "locked") {
|
||||
setPyloneState({ currentStep: "waiting" });
|
||||
if (nextMainState === "pylon" && pylonStep === "locked") {
|
||||
setPylonState({ currentStep: "waiting" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextMainState === "ferme" && fermeStep === "locked") {
|
||||
setFermeState({ currentStep: "waiting" });
|
||||
if (nextMainState === "farm" && farmStep === "locked") {
|
||||
setFarmState({ currentStep: "waiting" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as THREE from "three";
|
||||
import { ZONES } from "@/data/zones";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import { Debug } from "@/utils/debug/Debug";
|
||||
import { GAME_STEPS } from "@/types/game";
|
||||
import { GAME_STEPS } from "@/data/game/gameStateConfig";
|
||||
|
||||
const _playerPos = new THREE.Vector3();
|
||||
const _zonePos = new THREE.Vector3();
|
||||
|
||||
Reference in New Issue
Block a user