import React, { useRef, useState, useEffect } from 'react'; import { Canvas, useFrame, useThree } from '@react-three/fiber'; import { Physics, useBox, usePlane, useSphere } from '@react-three/cannon'; import { Sky, Text, OrbitControls } from '@react-three/drei'; import { Vector3 } from 'three'; // Game component export default function Game() { const [score, setScore] = useState(0); return (

Fugitive Chase

Score: {score}

Controls: WASD to move, Space to jump

); } // Ground component function Ground() { const [ref] = usePlane(() => ({ rotation: [-Math.PI / 2, 0, 0], position: [0, 0, 0], type: 'Static' })); return ( ); } // Arch component function Arch({ position }) { const [baseRef] = useBox(() => ({ args: [10, 1, 5], position: [position[0], position[1] + 0.5, position[2]], type: 'Static' })); const [leftPillarRef] = useBox(() => ({ args: [1, 5, 5], position: [position[0] - 4, position[1] + 3, position[2]], type: 'Static' })); const [rightPillarRef] = useBox(() => ({ args: [1, 5, 5], position: [position[0] + 4, position[1] + 3, position[2]], type: 'Static' })); const [topRef] = useBox(() => ({ args: [9, 1, 5], position: [position[0], position[1] + 6, position[2]], type: 'Static' })); // Trigger zone for scoring const [triggerRef] = useBox(() => ({ args: [7, 5, 5], position: [position[0], position[1] + 3, position[2]], isTrigger: true, type: 'Static' })); return ( {/* Base */} {/* Left pillar */} {/* Right pillar */} {/* Top */} {/* Invisible trigger zone */} {/* Sign */} Ow Shitz ); } // Player component function Player({ setScore }) { const [ref, api] = useSphere(() => ({ mass: 1, position: [0, 1, 0], args: [0.5] })); const velocity = useRef([0, 0, 0]); const position = useRef([0, 1, 0]); // Store followers const [followers, setFollowers] = useState([]); // Track which fugitives are following useEffect(() => { const unsubscribe = api.position.subscribe(p => { position.current = p; }); return unsubscribe; }, [api.position]); useEffect(() => { const unsubscribe = api.velocity.subscribe(v => { velocity.current = v; }); return unsubscribe; }, [api.velocity]); // Handle keyboard controls const { camera } = useThree(); useFrame(() => { // Update camera to follow player camera.position.x = position.current[0]; camera.position.y = position.current[1] + 5; camera.position.z = position.current[2] + 10; // Look at player camera.lookAt(position.current[0], position.current[1], position.current[2]); }); useEffect(() => { const handleKeyDown = (e) => { const speed = 5; // Movement controls if (e.code === 'KeyW') { api.velocity.set(velocity.current[0], velocity.current[1], -speed); } if (e.code === 'KeyS') { api.velocity.set(velocity.current[0], velocity.current[1], speed); } if (e.code === 'KeyA') { api.velocity.set(-speed, velocity.current[1], velocity.current[2]); } if (e.code === 'KeyD') { api.velocity.set(speed, velocity.current[1], velocity.current[2]); } if (e.code === 'Space') { api.velocity.set(velocity.current[0], 10, velocity.current[2]); } }; const handleKeyUp = (e) => { if (['KeyW', 'KeyS'].includes(e.code)) { api.velocity.set(velocity.current[0], velocity.current[1], 0); } if (['KeyA', 'KeyD'].includes(e.code)) { api.velocity.set(0, velocity.current[1], velocity.current[2]); } }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, [api]); // Check for collisions with fugitives and arch useEffect(() => { // This function will be called by fugitives when they collide with player window.playerCollideWithFugitive = (fugitiveId) => { if (!followers.includes(fugitiveId)) { setFollowers(prev => [...prev, fugitiveId]); } }; // This function will be called when a follower enters the arch window.fugitiveEntersArch = (fugitiveId) => { if (followers.includes(fugitiveId)) { setFollowers(prev => prev.filter(id => id !== fugitiveId)); setScore(prev => prev + 1); // Notify the fugitive to disappear window.fugitiveDisappear(fugitiveId); } }; return () => { window.playerCollideWithFugitive = null; window.fugitiveEntersArch = null; }; }, [followers, setScore]); return ( ); } // Fugitives component function Fugitives({ count = 5 }) { return ( <> {Array.from({ length: count }).map((_, i) => ( ))} ); } // Individual Fugitive component function Fugitive({ id }) { const [ref, api] = useSphere(() => ({ mass: 1, position: [ Math.random() * 20 - 10, 1, Math.random() * 20 - 10 ], args: [0.5] })); const position = useRef([0, 1, 0]); const [isFollowing, setIsFollowing] = useState(false); const [isVisible, setIsVisible] = useState(true); const playerPosition = useRef([0, 0, 0]); const moveDirection = useRef(new Vector3( Math.random() * 2 - 1, 0, Math.random() * 2 - 1 ).normalize()); // Subscribe to position updates useEffect(() => { const unsubscribe = api.position.subscribe(p => { position.current = p; // Check if in arch trigger zone if (p[2] < -17 && p[2] > -23 && p[0] > -3.5 && p[0] < 3.5 && p[1] > 0 && p[1] < 6) { if (isFollowing && window.fugitiveEntersArch) { window.fugitiveEntersArch(id); } } }); return unsubscribe; }, [api.position, id, isFollowing]); // Handle collision with player useEffect(() => { // Check distance to player (simplified collision detection) const checkPlayerCollision = () => { if (!isFollowing && isVisible) { const dx = position.current[0] - playerPosition.current[0]; const dy = position.current[1] - playerPosition.current[1]; const dz = position.current[2] - playerPosition.current[2]; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); if (distance < 1.5) { setIsFollowing(true); if (window.playerCollideWithFugitive) { window.playerCollideWithFugitive(id); } } } }; const interval = setInterval(checkPlayerCollision, 100); return () => clearInterval(interval); }, [id, isFollowing, isVisible]); // Make fugitive disappear when it enters the arch useEffect(() => { window.fugitiveDisappear = (fugitiveId) => { if (fugitiveId === id) { setIsVisible(false); // Respawn after a delay setTimeout(() => { api.position.set( Math.random() * 20 - 10, 1, Math.random() * 20 - 10 ); setIsFollowing(false); setIsVisible(true); }, 3000); } }; return () => { if (window.fugitiveDisappear === fugitiveDisappear) { window.fugitiveDisappear = null; } }; }, [api, id]); // Movement behavior useFrame(() => { if (!isVisible) return; if (isFollowing) { // Follow the player const direction = new Vector3( playerPosition.current[0] - position.current[0], 0, playerPosition.current[2] - position.current[2] ).normalize().multiplyScalar(3); api.velocity.set(direction.x, 0, direction.z); } else { // Random movement if (Math.random() < 0.02) { moveDirection.current = new Vector3( Math.random() * 2 - 1, 0, Math.random() * 2 - 1 ).normalize(); } api.velocity.set(moveDirection.current.x * 2, 0, moveDirection.current.z * 2); // Avoid going out of bounds if ( position.current[0] > 40 || position.current[0] < -40 || position.current[2] > 40 || position.current[2] < -40 ) { moveDirection.current.negate(); } } }); // Track player position useEffect(() => { const trackPlayer = () => { if (window.player && window.player.position) { playerPosition.current = window.player.position; } }; const interval = setInterval(trackPlayer, 100); return () => clearInterval(interval); }, []); // Expose position to window for player to access useEffect(() => { if (!window.fugitives) window.fugitives = {}; window.fugitives[id] = { position: position.current, isFollowing }; return () => { if (window.fugitives && window.fugitives[id]) { delete window.fugitives[id]; } }; }, [id, isFollowing]); // Expose player position to window useEffect(() => { if (!window.player) window.player = { position: [0, 0, 0] }; const updatePlayerPosition = () => { if (document.querySelector('canvas')) { window.player.position = position.current; } }; const interval = setInterval(updatePlayerPosition, 100); return () => clearInterval(interval); }, []); return isVisible ? ( ) : null; }