fix: stabilize game scene loading and player spawn
This commit is contained in:
@@ -11,5 +11,5 @@ export const PLAYER_MAX_DELTA = 0.05;
|
||||
export const PLAYER_ACCELERATION_MULTIPLIER = 9;
|
||||
export const PLAYER_XZ_DAMPING_FACTOR = 8;
|
||||
|
||||
export const PLAYER_SPAWN_POSITION_GAME: Vector3Tuple = [0, 100, 0];
|
||||
export const PLAYER_SPAWN_POSITION_GAME: Vector3Tuple = [0, 50, 0];
|
||||
export const PLAYER_SPAWN_POSITION_PHYSICS: Vector3Tuple = [0, 3, 0];
|
||||
|
||||
@@ -10,7 +10,9 @@ interface UseWorldSceneLoadingOptions {
|
||||
|
||||
interface UseWorldSceneLoadingResult {
|
||||
octree: Octree | null;
|
||||
gameplayReady: boolean;
|
||||
showGameStage: boolean;
|
||||
handleGameStageLoaded: () => void;
|
||||
handleGameMapLoaded: () => void;
|
||||
handleOctreeReady: (octree: Octree) => void;
|
||||
}
|
||||
@@ -21,15 +23,26 @@ export function useWorldSceneLoading({
|
||||
}: UseWorldSceneLoadingOptions): UseWorldSceneLoadingResult {
|
||||
const [octree, setOctree] = useState<Octree | null>(null);
|
||||
const [gameMapLoaded, setGameMapLoaded] = useState(false);
|
||||
const [gameStageLoaded, setGameStageLoaded] = useState(false);
|
||||
const showGameStage = sceneMode === "game" && gameMapLoaded;
|
||||
const gameplayReady = showGameStage && gameStageLoaded && octree !== null;
|
||||
const sceneReady =
|
||||
(sceneMode === "game" && gameMapLoaded) ||
|
||||
(sceneMode === "game" && gameplayReady) ||
|
||||
(sceneMode === "physics" && octree !== null);
|
||||
|
||||
const handleGameMapLoaded = useCallback(() => {
|
||||
setGameMapLoaded(true);
|
||||
}, []);
|
||||
|
||||
const handleGameStageLoaded = useCallback(() => {
|
||||
setGameStageLoaded(true);
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Initialisation gameplay",
|
||||
progress: 0.96,
|
||||
status: "loading",
|
||||
});
|
||||
}, [onLoadingStateChange]);
|
||||
|
||||
const handleOctreeReady = useCallback(
|
||||
(nextOctree: Octree) => {
|
||||
setOctree(nextOctree);
|
||||
@@ -74,7 +87,9 @@ export function useWorldSceneLoading({
|
||||
|
||||
return {
|
||||
octree,
|
||||
gameplayReady,
|
||||
showGameStage,
|
||||
handleGameStageLoaded,
|
||||
handleGameMapLoaded,
|
||||
handleOctreeReady,
|
||||
};
|
||||
|
||||
+4
-4
@@ -18,13 +18,13 @@ export function HomePage(): React.JSX.Element {
|
||||
const handleSceneLoadingStateChange = useCallback(
|
||||
(nextState: SceneLoadingState) => {
|
||||
setSceneLoadingState((currentState) => {
|
||||
const shouldRestartProgress = currentState.status === "ready";
|
||||
if (currentState.status === "ready" && nextState.status === "loading") {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
return {
|
||||
...nextState,
|
||||
progress: shouldRestartProgress
|
||||
? nextState.progress
|
||||
: Math.max(currentState.progress, nextState.progress),
|
||||
progress: Math.max(currentState.progress, nextState.progress),
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ export function GameCinematics(): null {
|
||||
const playedCinematicsRef = useRef(new Set<string>());
|
||||
const timelineRef = useRef<gsap.core.Timeline | null>(null);
|
||||
const activeAudiosRef = useRef(new Set<HTMLAudioElement>());
|
||||
const startedAtRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
@@ -59,7 +60,9 @@ export function GameCinematics(): null {
|
||||
useFrame(({ clock }) => {
|
||||
if (!manifest) return;
|
||||
|
||||
const elapsedTime = clock.getElapsedTime();
|
||||
startedAtRef.current ??= clock.getElapsedTime();
|
||||
|
||||
const elapsedTime = clock.getElapsedTime() - startedAtRef.current;
|
||||
|
||||
manifest.cinematics.forEach((cinematic) => {
|
||||
if (cinematic.timecode === undefined) return;
|
||||
|
||||
@@ -12,6 +12,7 @@ export function GameDialogues(): null {
|
||||
const [manifest, setManifest] = useState<DialogueManifest | null>(null);
|
||||
const playedDialoguesRef = useRef(new Set<string>());
|
||||
const activeAudiosRef = useRef(new Set<HTMLAudioElement>());
|
||||
const startedAtRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
@@ -38,7 +39,9 @@ export function GameDialogues(): null {
|
||||
useFrame(({ clock }) => {
|
||||
if (!manifest) return;
|
||||
|
||||
const elapsedTime = clock.getElapsedTime();
|
||||
startedAtRef.current ??= clock.getElapsedTime();
|
||||
|
||||
const elapsedTime = clock.getElapsedTime() - startedAtRef.current;
|
||||
|
||||
manifest.dialogues.forEach((dialogue) => {
|
||||
if (dialogue.timecode === undefined) return;
|
||||
|
||||
+83
-47
@@ -1,5 +1,12 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Component, Suspense, useEffect, useState } from "react";
|
||||
import {
|
||||
Component,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||
import { GameMapCollision } from "@/world/GameMapCollision";
|
||||
@@ -20,6 +27,7 @@ interface ErrorBoundaryProps {
|
||||
fallback: ReactNode;
|
||||
modelUrl: string | null;
|
||||
node: MapNode;
|
||||
onSettled: () => void;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
@@ -50,6 +58,7 @@ class ModelErrorBoundary extends Component<
|
||||
},
|
||||
error,
|
||||
);
|
||||
this.props.onSettled();
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
@@ -68,19 +77,39 @@ interface GameMapProps {
|
||||
buildOctree?: boolean;
|
||||
}
|
||||
|
||||
const MAP_RENDER_BATCH_SIZE = 12;
|
||||
|
||||
export function GameMap({
|
||||
buildOctree = true,
|
||||
onLoaded,
|
||||
onLoadingStateChange,
|
||||
onOctreeReady,
|
||||
}: GameMapProps): React.JSX.Element {
|
||||
const settledMapNodesRef = useRef(new Set<number>());
|
||||
const [mapNodes, setMapNodes] = useState<LoadedMapNode[]>([]);
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
const [visibleNodeCount, setVisibleNodeCount] = useState(0);
|
||||
const visibleMapNodes = mapNodes.slice(0, visibleNodeCount);
|
||||
const mapReady = mapLoaded && visibleNodeCount >= mapNodes.length;
|
||||
const [settledMapNodeCount, setSettledMapNodeCount] = useState(0);
|
||||
const mapReady = mapLoaded && settledMapNodeCount >= mapNodes.length;
|
||||
|
||||
const handleMapNodeSettled = useCallback((index: number) => {
|
||||
if (settledMapNodesRef.current.has(index)) return;
|
||||
|
||||
settledMapNodesRef.current.add(index);
|
||||
setSettledMapNodeCount(settledMapNodesRef.current.size);
|
||||
}, []);
|
||||
|
||||
const showEmptyMap = useCallback(
|
||||
(currentStep: string) => {
|
||||
setMapNodes([]);
|
||||
setMapLoaded(true);
|
||||
settledMapNodesRef.current.clear();
|
||||
setSettledMapNodeCount(0);
|
||||
onLoadingStateChange?.({
|
||||
currentStep,
|
||||
progress: 0.7,
|
||||
status: "loading",
|
||||
});
|
||||
},
|
||||
[onLoadingStateChange],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadingStateChange?.({
|
||||
@@ -94,11 +123,7 @@ export function GameMap({
|
||||
const sceneData = await loadMapSceneData();
|
||||
if (!sceneData) {
|
||||
logger.warn("GameMap", "map.json not found");
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Map introuvable",
|
||||
progress: 1,
|
||||
status: "loading",
|
||||
});
|
||||
showEmptyMap("Map introuvable");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,9 +153,10 @@ export function GameMap({
|
||||
|
||||
setMapNodes(loadedMapNodes);
|
||||
setMapLoaded(true);
|
||||
setVisibleNodeCount(0);
|
||||
settledMapNodesRef.current.clear();
|
||||
setSettledMapNodeCount(0);
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Montage progressif des models",
|
||||
currentStep: "Chargement des modèles de la map",
|
||||
progress: 0.25,
|
||||
status: "loading",
|
||||
});
|
||||
@@ -138,63 +164,41 @@ export function GameMap({
|
||||
logger.error("GameMap", "Error loading map", {
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
});
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Erreur de chargement de la map",
|
||||
progress: 1,
|
||||
status: "loading",
|
||||
});
|
||||
showEmptyMap("Erreur de chargement de la map");
|
||||
}
|
||||
};
|
||||
|
||||
loadMap();
|
||||
}, [onLoaded, onLoadingStateChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mapNodes.length === 0 || visibleNodeCount >= mapNodes.length) return;
|
||||
|
||||
const frameId = window.requestAnimationFrame(() => {
|
||||
setVisibleNodeCount((current) =>
|
||||
Math.min(current + MAP_RENDER_BATCH_SIZE, mapNodes.length),
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(frameId);
|
||||
};
|
||||
}, [mapNodes.length, visibleNodeCount]);
|
||||
}, [onLoadingStateChange, showEmptyMap]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mapNodes.length === 0) return;
|
||||
|
||||
const renderProgress =
|
||||
mapNodes.length === 0 ? 1 : visibleNodeCount / mapNodes.length;
|
||||
mapNodes.length === 0 ? 1 : settledMapNodeCount / mapNodes.length;
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Montage progressif des models",
|
||||
currentStep: "Chargement des modèles de la map",
|
||||
progress: 0.25 + renderProgress * 0.45,
|
||||
status: "loading",
|
||||
});
|
||||
}, [mapNodes.length, onLoadingStateChange, visibleNodeCount]);
|
||||
}, [mapNodes.length, onLoadingStateChange, settledMapNodeCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<group>
|
||||
{visibleMapNodes.map((mapNode, index) => (
|
||||
{mapNodes.map((mapNode, index) => (
|
||||
<ModelErrorBoundary
|
||||
key={index}
|
||||
fallback={<FallbackMapNode node={mapNode.node} />}
|
||||
modelUrl={mapNode.modelUrl}
|
||||
node={mapNode.node}
|
||||
onSettled={() => handleMapNodeSettled(index)}
|
||||
>
|
||||
{mapNode.modelUrl ? (
|
||||
<Suspense fallback={<FallbackMapNode node={mapNode.node} />}>
|
||||
<ModelInstance
|
||||
node={mapNode.node}
|
||||
modelUrl={mapNode.modelUrl}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
<FallbackMapNode node={mapNode.node} />
|
||||
)}
|
||||
<MapNodeInstance
|
||||
node={mapNode.node}
|
||||
modelUrl={mapNode.modelUrl}
|
||||
onSettled={() => handleMapNodeSettled(index)}
|
||||
/>
|
||||
</ModelErrorBoundary>
|
||||
))}
|
||||
</group>
|
||||
@@ -210,12 +214,40 @@ export function GameMap({
|
||||
);
|
||||
}
|
||||
|
||||
function MapNodeInstance({
|
||||
node,
|
||||
modelUrl,
|
||||
onSettled,
|
||||
}: {
|
||||
node: MapNode;
|
||||
modelUrl: string | null;
|
||||
onSettled: () => void;
|
||||
}): React.JSX.Element {
|
||||
useEffect(() => {
|
||||
if (modelUrl !== null) return;
|
||||
|
||||
onSettled();
|
||||
}, [modelUrl, onSettled]);
|
||||
|
||||
if (!modelUrl) {
|
||||
return <FallbackMapNode node={node} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<FallbackMapNode node={node} />}>
|
||||
<ModelInstance node={node} modelUrl={modelUrl} onLoaded={onSettled} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function ModelInstance({
|
||||
node,
|
||||
modelUrl,
|
||||
onLoaded,
|
||||
}: {
|
||||
node: MapNode;
|
||||
modelUrl: string;
|
||||
onLoaded: () => void;
|
||||
}): React.JSX.Element {
|
||||
const { position, rotation, scale } = node;
|
||||
const { scene } = useLoggedGLTF(modelUrl, {
|
||||
@@ -226,6 +258,10 @@ function ModelInstance({
|
||||
});
|
||||
const sceneInstance = useClonedObject(scene);
|
||||
|
||||
useEffect(() => {
|
||||
onLoaded();
|
||||
}, [onLoaded]);
|
||||
|
||||
return (
|
||||
<primitive
|
||||
object={sceneInstance}
|
||||
|
||||
@@ -102,11 +102,19 @@ export function GameMapCollision({
|
||||
}: GameMapCollisionProps): React.JSX.Element {
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const settledCollisionNodesRef = useRef(new Set<number>());
|
||||
const loadedNotifiedRef = useRef(false);
|
||||
const [settledCollisionNodeCount, setSettledCollisionNodeCount] = useState(0);
|
||||
const collisionNodes = nodes.filter(isCollisionNode);
|
||||
const collisionReady =
|
||||
mapReady && settledCollisionNodeCount >= collisionNodes.length;
|
||||
|
||||
const notifyLoaded = useCallback(() => {
|
||||
if (loadedNotifiedRef.current) return;
|
||||
|
||||
loadedNotifiedRef.current = true;
|
||||
onLoaded?.();
|
||||
}, [onLoaded]);
|
||||
|
||||
const handleCollisionNodeSettled = useCallback((index: number) => {
|
||||
if (settledCollisionNodesRef.current.has(index)) return;
|
||||
|
||||
@@ -122,9 +130,9 @@ export function GameMapCollision({
|
||||
status: "loading",
|
||||
});
|
||||
onOctreeReady(octree);
|
||||
onLoaded?.();
|
||||
notifyLoaded();
|
||||
},
|
||||
[onLoaded, onLoadingStateChange, onOctreeReady],
|
||||
[notifyLoaded, onLoadingStateChange, onOctreeReady],
|
||||
);
|
||||
|
||||
useOctreeGraphNode(
|
||||
@@ -138,7 +146,12 @@ export function GameMapCollision({
|
||||
if (!mapReady) return;
|
||||
|
||||
if (collisionNodes.length === 0) {
|
||||
onLoaded?.();
|
||||
notifyLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
if (collisionReady && !buildOctree) {
|
||||
notifyLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,10 +163,11 @@ export function GameMapCollision({
|
||||
status: "loading",
|
||||
});
|
||||
}, [
|
||||
buildOctree,
|
||||
collisionNodes.length,
|
||||
collisionReady,
|
||||
mapReady,
|
||||
onLoaded,
|
||||
notifyLoaded,
|
||||
onLoadingStateChange,
|
||||
]);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
||||
import { AudioManager } from "@/managers/AudioManager";
|
||||
|
||||
const GAME_MUSIC_PATH = "/sounds/musique/test.mp3";
|
||||
const GAME_MUSIC_VOLUME = 0.45;
|
||||
const GAME_MUSIC_VOLUME = 0.33;
|
||||
|
||||
export function GameMusic(): null {
|
||||
useEffect(() => {
|
||||
|
||||
+38
-27
@@ -1,4 +1,4 @@
|
||||
import { Suspense } from "react";
|
||||
import { Suspense, useEffect } from "react";
|
||||
import { Physics } from "@react-three/rapier";
|
||||
import {
|
||||
PLAYER_SPAWN_POSITION_GAME,
|
||||
@@ -8,6 +8,7 @@ import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
||||
import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls";
|
||||
import { DebugHelpers } from "@/components/debug/scene/DebugHelpers";
|
||||
import { HandTrackingGlove } from "@/components/three/handTracking/HandTrackingGlove";
|
||||
@@ -26,23 +27,19 @@ interface WorldProps {
|
||||
onLoadingStateChange?: SceneLoadingChangeHandler | undefined;
|
||||
}
|
||||
|
||||
function hasBootFlag(name: string): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
return new URLSearchParams(window.location.search).has(name);
|
||||
}
|
||||
|
||||
export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
||||
const cameraMode = useCameraMode();
|
||||
const sceneMode = useSceneMode();
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const { status, usageStatus } = useHandTrackingSnapshot();
|
||||
const { octree, showGameStage, handleGameMapLoaded, handleOctreeReady } =
|
||||
useWorldSceneLoading({ sceneMode, onLoadingStateChange });
|
||||
const noCinematics = hasBootFlag("noCinematics");
|
||||
const noDialogues = hasBootFlag("noDialogues");
|
||||
const noMap = hasBootFlag("noMap");
|
||||
const noMusic = hasBootFlag("noMusic");
|
||||
const noOctree = hasBootFlag("noOctree");
|
||||
const noPlayer = hasBootFlag("noPlayer");
|
||||
const {
|
||||
octree,
|
||||
gameplayReady,
|
||||
showGameStage,
|
||||
handleGameStageLoaded,
|
||||
handleGameMapLoaded,
|
||||
handleOctreeReady,
|
||||
} = useWorldSceneLoading({ sceneMode, onLoadingStateChange });
|
||||
const playerSpawnPosition =
|
||||
sceneMode === "game"
|
||||
? PLAYER_SPAWN_POSITION_GAME
|
||||
@@ -50,6 +47,9 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
||||
const showHandTrackingGloves =
|
||||
sceneMode === "physics" ||
|
||||
(status !== "idle" && usageStatus !== "inactive");
|
||||
const spawnPlayer =
|
||||
cameraMode !== "debug" &&
|
||||
(sceneMode === "game" ? gameplayReady : octree !== null);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -65,30 +65,41 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
||||
{cameraMode === "debug" ? <DebugCameraControls /> : null}
|
||||
{sceneMode === "game" ? (
|
||||
<>
|
||||
{noMusic ? null : <GameMusic />}
|
||||
{noCinematics ? null : <GameCinematics />}
|
||||
{noDialogues ? null : <GameDialogues />}
|
||||
{noMap ? null : (
|
||||
<GameMap
|
||||
buildOctree={!noOctree}
|
||||
onLoaded={handleGameMapLoaded}
|
||||
onLoadingStateChange={onLoadingStateChange}
|
||||
onOctreeReady={handleOctreeReady}
|
||||
/>
|
||||
)}
|
||||
{noMap || showGameStage ? (
|
||||
<GameMap
|
||||
onLoaded={handleGameMapLoaded}
|
||||
onLoadingStateChange={onLoadingStateChange}
|
||||
onOctreeReady={handleOctreeReady}
|
||||
/>
|
||||
{showGameStage ? (
|
||||
<Physics>
|
||||
<GameStageLoaded onLoaded={handleGameStageLoaded} />
|
||||
<GameStageContent />
|
||||
</Physics>
|
||||
) : null}
|
||||
{spawnPlayer ? (
|
||||
<>
|
||||
<GameMusic />
|
||||
{mainState === "outro" ? <GameCinematics /> : null}
|
||||
<GameDialogues />
|
||||
<Player octree={octree} spawnPosition={playerSpawnPosition} />
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<TestMap onOctreeReady={handleOctreeReady} />
|
||||
)}
|
||||
|
||||
{cameraMode !== "debug" && !noPlayer ? (
|
||||
{sceneMode !== "game" && spawnPlayer ? (
|
||||
<Player octree={octree} spawnPosition={playerSpawnPosition} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function GameStageLoaded({ onLoaded }: { onLoaded: () => void }): null {
|
||||
useEffect(() => {
|
||||
onLoaded();
|
||||
}, [onLoaded]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import type { Octree } from "three/addons/math/Octree.js";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
@@ -16,7 +16,7 @@ export function Player({
|
||||
}: PlayerProps): React.JSX.Element {
|
||||
const camera = useThree((state) => state.camera);
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
camera.position.set(...spawnPosition);
|
||||
}, [camera, spawnPosition]);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { Capsule } from "three/addons/math/Capsule.js";
|
||||
@@ -57,6 +57,18 @@ const _up = new THREE.Vector3(0, 1, 0);
|
||||
const _translateVec = new THREE.Vector3();
|
||||
const _collisionCorrection = new THREE.Vector3();
|
||||
|
||||
function createSpawnCapsule(spawnPosition: Vector3Tuple): Capsule {
|
||||
return new Capsule(
|
||||
new THREE.Vector3(
|
||||
spawnPosition[0],
|
||||
spawnPosition[1] - PLAYER_EYE_HEIGHT + PLAYER_CAPSULE_RADIUS,
|
||||
spawnPosition[2],
|
||||
),
|
||||
new THREE.Vector3(...spawnPosition),
|
||||
PLAYER_CAPSULE_RADIUS,
|
||||
);
|
||||
}
|
||||
|
||||
function isPlayerInputLocked(): boolean {
|
||||
return (
|
||||
useSettingsStore.getState().isSettingsMenuOpen ||
|
||||
@@ -94,16 +106,10 @@ export function PlayerController({
|
||||
const velocity = useRef(new THREE.Vector3());
|
||||
const onFloor = useRef(false);
|
||||
const wantsJump = useRef(false);
|
||||
const initializedRef = useRef(false);
|
||||
const capsule = useRef(createSpawnCapsule(spawnPosition));
|
||||
|
||||
const capsule = useRef(
|
||||
new Capsule(
|
||||
new THREE.Vector3(0, PLAYER_CAPSULE_RADIUS, 0),
|
||||
new THREE.Vector3(0, PLAYER_EYE_HEIGHT - PLAYER_CAPSULE_RADIUS, 0),
|
||||
PLAYER_CAPSULE_RADIUS,
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
capsule.current.start.set(
|
||||
spawnPosition[0],
|
||||
spawnPosition[1] - PLAYER_EYE_HEIGHT + PLAYER_CAPSULE_RADIUS,
|
||||
@@ -114,6 +120,7 @@ export function PlayerController({
|
||||
onFloor.current = false;
|
||||
wantsJump.current = false;
|
||||
camera.position.copy(capsule.current.end);
|
||||
initializedRef.current = true;
|
||||
}, [camera, spawnPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -200,6 +207,8 @@ export function PlayerController({
|
||||
}, []);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
if (!initializedRef.current) return;
|
||||
|
||||
if (isPlayerInputLocked()) {
|
||||
keys.current = { ...DEFAULT_KEYS };
|
||||
velocity.current.set(0, 0, 0);
|
||||
|
||||
Reference in New Issue
Block a user