diff --git a/src/components/ui/VideoPlayer.tsx b/src/components/ui/VideoPlayer.tsx new file mode 100644 index 0000000..d81d644 --- /dev/null +++ b/src/components/ui/VideoPlayer.tsx @@ -0,0 +1,80 @@ +import { useEffect, useRef } from "react"; +import { useGameStore } from "@/managers/stores/useGameStore"; + +export function VideoPlayer(): null { + const currentVideo = useGameStore((state) => state.missionFlow.currentVideo); + const clearVideo = useGameStore((state) => state.clearVideo); + const setCinematicPlaying = useGameStore( + (state) => state.setCinematicPlaying, + ); + const videoRef = useRef(null); + + useEffect(() => { + if (currentVideo) { + setCinematicPlaying(true); + } + }, [currentVideo, setCinematicPlaying]); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && currentVideo) { + closeVideo(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [currentVideo]); + + const closeVideo = () => { + if (videoRef.current) { + videoRef.current.pause(); + } + clearVideo(); + setCinematicPlaying(false); + }; + + useEffect(() => { + const video = videoRef.current; + if (!video) return; + + const handleEnded = () => { + closeVideo(); + }; + + video.addEventListener("ended", handleEnded); + return () => video.removeEventListener("ended", handleEnded); + }, []); + + if (!currentVideo) return null; + + return ( +
+
+ ); +} diff --git a/src/managers/stores/useGameStore.ts b/src/managers/stores/useGameStore.ts index 357bb80..f22dc9e 100644 --- a/src/managers/stores/useGameStore.ts +++ b/src/managers/stores/useGameStore.ts @@ -29,6 +29,7 @@ interface MissionFlowState { canMove: boolean; dialogMessage: string | null; playerName: string; + currentVideo: string | null; } interface GameState { @@ -78,6 +79,8 @@ interface GameActions { rewindGameState: () => void; resetGame: () => void; showDialog: (dialogMessage: string) => void; + playVideo: (videoSrc: string) => void; + clearVideo: () => void; } type GameStore = GameState & GameActions; @@ -233,6 +236,7 @@ function createInitialGameState(): GameState { canMove: false, dialogMessage: null, playerName: "", + currentVideo: null, }, intro: { currentStep: "intro", @@ -342,4 +346,12 @@ export const useGameStore = create()((set) => ({ set((state) => ({ missionFlow: { ...state.missionFlow, dialogMessage }, })), + playVideo: (videoSrc) => + set((state) => ({ + missionFlow: { ...state.missionFlow, currentVideo: videoSrc }, + })), + clearVideo: () => + set((state) => ({ + missionFlow: { ...state.missionFlow, currentVideo: null }, + })), })); diff --git a/src/world/World.tsx b/src/world/World.tsx index 9bda20f..8e6af05 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -29,6 +29,7 @@ import { GameStageContent } from "@/world/GameStageContent"; import { Player } from "@/world/player/Player"; import { TestMap } from "@/world/debug/TestMap"; import { NetTest } from "@/components/three/NetTest"; +import { VideoPlayer } from "@/components/ui/VideoPlayer"; import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading"; interface WorldProps { @@ -62,6 +63,7 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element { return ( <> + {showHandTrackingGloves ? (