update step
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { ZONES } from "@/data/zones";
|
||||
import { GameStepManager } from "@/stateManager/GameStepManager";
|
||||
import { Debug } from "@/utils/debug/Debug";
|
||||
|
||||
const _playerPos = new THREE.Vector3();
|
||||
const _zonePos = new THREE.Vector3();
|
||||
|
||||
export function ZoneDetection(): null {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const manager = GameStepManager.getInstance();
|
||||
const triggeredZones = useRef<Set<string>>(new Set());
|
||||
const debug = Debug.getInstance();
|
||||
|
||||
useEffect(() => {
|
||||
if (!debug.active) return;
|
||||
|
||||
const folder = debug.createFolder("Game");
|
||||
if (!folder) return;
|
||||
|
||||
const gameState = { step: manager.getStep() };
|
||||
const playerPos = { x: 0, y: 0, z: 0 };
|
||||
|
||||
folder
|
||||
.add(gameState, "step", ["intro", "outOfFabrik"])
|
||||
.name("Game Step")
|
||||
.disable();
|
||||
|
||||
folder.add(playerPos, "x").name("Player X").listen().disable();
|
||||
folder.add(playerPos, "y").name("Player Y").listen().disable();
|
||||
folder.add(playerPos, "z").name("Player Z").listen().disable();
|
||||
|
||||
const unsubManager = manager.subscribe(() => {
|
||||
gameState.step = manager.getStep();
|
||||
folder.controllersRecursive().forEach((c) => c.updateDisplay());
|
||||
});
|
||||
|
||||
let frameId: number;
|
||||
const updatePlayerPos = (): void => {
|
||||
camera.getWorldPosition(_playerPos);
|
||||
playerPos.x = Math.round(_playerPos.x * 100) / 100;
|
||||
playerPos.y = Math.round(_playerPos.y * 100) / 100;
|
||||
playerPos.z = Math.round(_playerPos.z * 100) / 100;
|
||||
folder.controllersRecursive().forEach((c) => c.updateDisplay());
|
||||
frameId = requestAnimationFrame(updatePlayerPos);
|
||||
};
|
||||
updatePlayerPos();
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frameId);
|
||||
debug.destroyFolder("Game");
|
||||
unsubManager();
|
||||
};
|
||||
}, [debug, manager, camera]);
|
||||
|
||||
useFrame(() => {
|
||||
camera.getWorldPosition(_playerPos);
|
||||
|
||||
for (const zone of ZONES) {
|
||||
if (triggeredZones.current.has(zone.id)) continue;
|
||||
|
||||
_zonePos.set(...zone.position);
|
||||
|
||||
const distanceSq = _playerPos.distanceToSquared(_zonePos);
|
||||
|
||||
if (distanceSq <= zone.radius * zone.radius) {
|
||||
manager.transitionTo(zone.targetStep);
|
||||
triggeredZones.current.add(zone.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
interface ZoneVisualProps {
|
||||
position: [number, number, number];
|
||||
radius: number;
|
||||
height: number;
|
||||
triggered: boolean;
|
||||
}
|
||||
|
||||
function ZoneVisual({
|
||||
position,
|
||||
radius,
|
||||
height,
|
||||
triggered,
|
||||
}: ZoneVisualProps): React.JSX.Element {
|
||||
const color = triggered ? "#00ff00" : "#ff0000";
|
||||
|
||||
return (
|
||||
<group position={position}>
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[radius - 0.3, radius, 32]} />
|
||||
<meshBasicMaterial color={color} side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
<mesh position={[0, height / 2, 0]}>
|
||||
<cylinderGeometry args={[radius, radius, height, 32, 1, true]} />
|
||||
<meshBasicMaterial
|
||||
color={color}
|
||||
transparent
|
||||
opacity={0.15}
|
||||
side={THREE.DoubleSide}
|
||||
depthWrite={false}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export function ZoneDebugVisuals(): React.JSX.Element | null {
|
||||
const debug = Debug.getInstance();
|
||||
const camera = useThree((state) => state.camera);
|
||||
const [triggeredZones, setTriggeredZones] = useState<Set<string>>(new Set());
|
||||
|
||||
useFrame(() => {
|
||||
camera.getWorldPosition(_playerPos);
|
||||
|
||||
for (const zone of ZONES) {
|
||||
if (triggeredZones.has(zone.id)) continue;
|
||||
|
||||
_zonePos.set(...zone.position);
|
||||
|
||||
const distanceSq = _playerPos.distanceToSquared(_zonePos);
|
||||
|
||||
if (distanceSq <= zone.radius * zone.radius) {
|
||||
setTriggeredZones((prev) => new Set(prev).add(zone.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!debug.active) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ZONES.map((zone) => (
|
||||
<ZoneVisual
|
||||
key={zone.id}
|
||||
position={[zone.position[0], 0.1, zone.position[2]]}
|
||||
radius={zone.radius}
|
||||
height={zone.height}
|
||||
triggered={triggeredZones.has(zone.id)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Zone } from "@/types/game";
|
||||
import type { Vector3Tuple } from "@/types/3d";
|
||||
|
||||
export const ZONES: Zone[] = [
|
||||
{
|
||||
id: "fabrikExit",
|
||||
position: [-5, 25, -15] as Vector3Tuple,
|
||||
radius: 10,
|
||||
height: 20,
|
||||
targetStep: "outOfFabrik",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { GameStepManager } from "@/stateManager/GameStepManager";
|
||||
import type { GameStepSnapshot } from "@/types/game";
|
||||
|
||||
const manager = GameStepManager.getInstance();
|
||||
|
||||
export function useGameStep(): GameStepSnapshot {
|
||||
return useSyncExternalStore(manager.subscribe.bind(manager), () => ({
|
||||
step: manager.getStep(),
|
||||
transitionTo: manager.transitionTo.bind(manager),
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { GameStep } from "@/types/game";
|
||||
|
||||
export class GameStepManager {
|
||||
private static _instance: GameStepManager | null = null;
|
||||
|
||||
private _currentStep: GameStep = "intro";
|
||||
private readonly _listeners = new Set<() => void>();
|
||||
|
||||
static getInstance(): GameStepManager {
|
||||
if (!GameStepManager._instance) {
|
||||
GameStepManager._instance = new GameStepManager();
|
||||
}
|
||||
|
||||
return GameStepManager._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
getStep(): GameStep {
|
||||
return this._currentStep;
|
||||
}
|
||||
|
||||
transitionTo(step: GameStep): void {
|
||||
if (this._currentStep === step) return;
|
||||
|
||||
this._currentStep = step;
|
||||
this._emit();
|
||||
}
|
||||
|
||||
subscribe(listener: () => void): () => void {
|
||||
this._listeners.add(listener);
|
||||
|
||||
return () => {
|
||||
this._listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this._currentStep = "intro";
|
||||
this._listeners.clear();
|
||||
GameStepManager._instance = null;
|
||||
}
|
||||
|
||||
private _emit(): void {
|
||||
this._listeners.forEach((cb) => cb());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Vector3Tuple } from "@/types/3d";
|
||||
|
||||
export type GameStep = "intro" | "outOfFabrik";
|
||||
|
||||
export interface Zone {
|
||||
id: string;
|
||||
position: Vector3Tuple;
|
||||
radius: number;
|
||||
height: number;
|
||||
targetStep: GameStep;
|
||||
}
|
||||
|
||||
export interface GameState {
|
||||
step: GameStep;
|
||||
}
|
||||
|
||||
export interface GameStepSnapshot {
|
||||
step: GameStep;
|
||||
transitionTo: (step: GameStep) => void;
|
||||
}
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
} from "@/data/playerConfig";
|
||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import {
|
||||
ZoneDebugVisuals,
|
||||
ZoneDetection,
|
||||
} from "@/components/zone/ZoneDetection";
|
||||
import { DebugCameraControls } from "@/utils/debug/scene/DebugCameraControls";
|
||||
import { DebugHelpers } from "@/utils/debug/scene/DebugHelpers";
|
||||
import { Environment } from "@/world/Environment";
|
||||
@@ -28,6 +32,8 @@ export function World(): React.JSX.Element {
|
||||
<Environment />
|
||||
<Lighting />
|
||||
<DebugHelpers />
|
||||
<ZoneDetection />
|
||||
<ZoneDebugVisuals />
|
||||
{cameraMode === "debug" ? <DebugCameraControls /> : null}
|
||||
|
||||
{sceneMode === "game" ? (
|
||||
|
||||
Reference in New Issue
Block a user