import React, { useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; export default function AdvancedRacingGame() { const mountRef = useRef(null); const [winner, setWinner] = useState(null); const [scores, setScores] = useState({ player1: 0, player2: 0 }); const [lapTimes, setLapTimes] = useState({ player1: [], player2: [] }); const [currentLap, setCurrentLap] = useState({ player1: 0, player2: 0 }); const [cameraMode, setCameraMode] = useState('follow'); const audioContextRef = useRef(null); const soundsRef = useRef({}); const gameStateRef = useRef({ player1: { position: new THREE.Vector3(-2, 0.5, 0), rotation: 0, speed: 0, boost: 0, startTime: null, lastLapTime: null, particles: [] }, player2: { position: new THREE.Vector3(2, 0.5, 0), rotation: 0, speed: 0, boost: 0, startTime: null, lastLapTime: null, particles: [] }, keys: {}, totalLaps: 3, checkpoints: [], powerUps: [], obstacles: [] }); // Initialize audio useEffect(() => { audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); // Create simple sound effects const createSound = (frequency, duration, type = 'sine') => { return () => { const ctx = audioContextRef.current; const oscillator = ctx.createOscillator(); const gainNode = ctx.createGain(); oscillator.type = type; oscillator.frequency.setValueAtTime(frequency, ctx.currentTime); gainNode.gain.setValueAtTime(0.3, ctx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + duration); oscillator.connect(gainNode); gainNode.connect(ctx.destination); oscillator.start(ctx.currentTime); oscillator.stop(ctx.currentTime + duration); }; }; soundsRef.current = { boost: createSound(800, 0.2, 'square'), collision: createSound(100, 0.3, 'sawtooth'), checkpoint: createSound(600, 0.15, 'sine'), win: createSound(1000, 0.5, 'sine') }; return () => { if (audioContextRef.current) { audioContextRef.current.close(); } }; }, []); useEffect(() => { if (!mountRef.current) return; // Scene setup const scene = new THREE.Scene(); scene.background = new THREE.Color(0x87CEEB); scene.fog = new THREE.Fog(0x87CEEB, 70, 150); const camera = new THREE.PerspectiveCamera( 75, mountRef.current.clientWidth / mountRef.current.clientHeight, 0.1, 1000 ); camera.position.set(0, 20, -20); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(mountRef.current.clientWidth, mountRef.current.clientHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; mountRef.current.appendChild(renderer.domElement); // Enhanced lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); const sunLight = new THREE.DirectionalLight(0xffffff, 1); sunLight.position.set(30, 50, 30); sunLight.castShadow = true; sunLight.shadow.camera.left = -80; sunLight.shadow.camera.right = 80; sunLight.shadow.camera.top = 80; sunLight.shadow.camera.bottom = -80; sunLight.shadow.mapSize.width = 2048; sunLight.shadow.mapSize.height = 2048; scene.add(sunLight); // Add hemisphere light for better atmosphere const hemiLight = new THREE.HemisphereLight(0x87CEEB, 0x545454, 0.6); scene.add(hemiLight); // Create curved track const trackPath = new THREE.CurvePath(); const segments = [ new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 40)), new THREE.QuadraticBezierCurve3( new THREE.Vector3(0, 0, 40), new THREE.Vector3(20, 0, 50), new THREE.Vector3(30, 0, 40) ), new THREE.LineCurve3(new THREE.Vector3(30, 0, 40), new THREE.Vector3(30, 0, 10)), new THREE.QuadraticBezierCurve3( new THREE.Vector3(30, 0, 10), new THREE.Vector3(20, 0, 0), new THREE.Vector3(0, 0, 0) ) ]; segments.forEach(seg => trackPath.add(seg)); // Ground const groundGeometry = new THREE.PlaneGeometry(200, 200); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.9 }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground); // Main track const trackGeometry = new THREE.PlaneGeometry(15, 150); const trackMaterial = new THREE.MeshStandardMaterial({ color: 0x404040, roughness: 0.8 }); const track = new THREE.Mesh(trackGeometry, trackMaterial); track.rotation.x = -Math.PI / 2; track.position.z = 40; track.receiveShadow = true; scene.add(track); // Track borders with better materials const borderMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000, emissive: 0x330000, roughness: 0.5 }); const leftBorder = new THREE.Mesh( new THREE.BoxGeometry(0.5, 1.5, 150), borderMaterial ); leftBorder.position.set(-7.5, 0.75, 40); leftBorder.castShadow = true; scene.add(leftBorder); const rightBorder = leftBorder.clone(); rightBorder.position.x = 7.5; scene.add(rightBorder); // Center line with glow for (let i = 0; i < 30; i++) { const line = new THREE.Mesh( new THREE.BoxGeometry(0.3, 0.1, 3), new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xffffff, emissiveIntensity: 0.5 }) ); line.position.set(0, 0.05, i * 5 - 10); scene.add(line); } // Checkpoints const checkpointPositions = [30, 60, 90, 115]; checkpointPositions.forEach((z, index) => { const checkpointGeometry = new THREE.PlaneGeometry(15, 2); const checkpointMaterial = new THREE.MeshStandardMaterial({ color: index === checkpointPositions.length - 1 ? 0xffff00 : 0x00ff00, transparent: true, opacity: 0.6, emissive: index === checkpointPositions.length - 1 ? 0xffff00 : 0x00ff00, emissiveIntensity: 0.3 }); const checkpoint = new THREE.Mesh(checkpointGeometry, checkpointMaterial); checkpoint.rotation.x = -Math.PI / 2; checkpoint.position.set(0, 0.1, z); checkpoint.userData = { index, isFinishLine: index === checkpointPositions.length - 1 }; scene.add(checkpoint); gameStateRef.current.checkpoints.push({ z, passed: [false, false] }); }); // Boost pads const boostPositions = [20, 50, 80]; boostPositions.forEach(z => { const boostPad = new THREE.Mesh( new THREE.CylinderGeometry(1.5, 1.5, 0.2, 8), new THREE.MeshStandardMaterial({ color: 0xff00ff, emissive: 0xff00ff, emissiveIntensity: 0.8, transparent: true, opacity: 0.8 }) ); boostPad.rotation.x = Math.PI / 2; boostPad.position.set(0, 0.1, z); scene.add(boostPad); gameStateRef.current.powerUps.push({ position: new THREE.Vector3(0, 0, z), type: 'boost', mesh: boostPad }); }); // Obstacles const obstaclePositions = [ { x: -3, z: 35 }, { x: 3, z: 45 }, { x: -3, z: 70 }, { x: 3, z: 100 } ]; obstaclePositions.forEach(pos => { const obstacle = new THREE.Mesh( new THREE.BoxGeometry(2, 2, 2), new THREE.MeshStandardMaterial({ color: 0xff6600, roughness: 0.5 }) ); obstacle.position.set(pos.x, 1, pos.z); obstacle.castShadow = true; scene.add(obstacle); gameStateRef.current.obstacles.push({ position: new THREE.Vector3(pos.x, 1, pos.z), mesh: obstacle }); }); // Scenery - Trees for (let i = 0; i < 20; i++) { const tree = new THREE.Group(); const trunk = new THREE.Mesh( new THREE.CylinderGeometry(0.3, 0.4, 3), new THREE.MeshStandardMaterial({ color: 0x8B4513 }) ); trunk.position.y = 1.5; trunk.castShadow = true; tree.add(trunk); const foliage = new THREE.Mesh( new THREE.SphereGeometry(2), new THREE.MeshStandardMaterial({ color: 0x228B22 }) ); foliage.position.y = 4; foliage.castShadow = true; tree.add(foliage); const side = Math.random() > 0.5 ? 1 : -1; tree.position.set( side * (12 + Math.random() * 20), 0, Math.random() * 120 - 10 ); scene.add(tree); } // Create enhanced cars function createCar(color) { const car = new THREE.Group(); // Car body with better materials const bodyGeometry = new THREE.BoxGeometry(1.4, 0.7, 2.5); const bodyMaterial = new THREE.MeshStandardMaterial({ color, metalness: 0.6, roughness: 0.4 }); const body = new THREE.Mesh(bodyGeometry, bodyMaterial); body.position.y = 0.35; body.castShadow = true; car.add(body); // Car top (cabin) const topGeometry = new THREE.BoxGeometry(1, 0.5, 1.2); const top = new THREE.Mesh(topGeometry, bodyMaterial); top.position.set(0, 0.95, -0.2); top.castShadow = true; car.add(top); // Windows const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x88ccff, transparent: true, opacity: 0.6, metalness: 0.9, roughness: 0.1 }); const windowGeometry = new THREE.BoxGeometry(0.95, 0.45, 1.15); const window = new THREE.Mesh(windowGeometry, windowMaterial); window.position.set(0, 0.95, -0.2); car.add(window); // Spoiler const spoiler = new THREE.Mesh( new THREE.BoxGeometry(1.3, 0.1, 0.4), bodyMaterial ); spoiler.position.set(0, 0.8, -1.5); car.add(spoiler); // Wheels with more detail const wheelGeometry = new THREE.CylinderGeometry(0.35, 0.35, 0.25, 16); const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, metalness: 0.8, roughness: 0.2 }); const rimMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.9, roughness: 0.1 }); const wheelPositions = [ [-0.7, 0.35, 0.9], [0.7, 0.35, 0.9], [-0.7, 0.35, -0.9], [0.7, 0.35, -0.9] ]; wheelPositions.forEach(pos => { const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial); wheel.position.set(pos[0], pos[1], pos[2]); wheel.rotation.z = Math.PI / 2; wheel.castShadow = true; car.add(wheel); const rim = new THREE.Mesh( new THREE.CylinderGeometry(0.2, 0.2, 0.26, 16), rimMaterial ); rim.position.set(pos[0], pos[1], pos[2]); rim.rotation.z = Math.PI / 2; car.add(rim); }); // Headlights const headlightGeometry = new THREE.SphereGeometry(0.15); const headlightMaterial = new THREE.MeshStandardMaterial({ color: 0xffffaa, emissive: 0xffffaa, emissiveIntensity: 1 }); [-0.5, 0.5].forEach(x => { const headlight = new THREE.Mesh(headlightGeometry, headlightMaterial); headlight.position.set(x, 0.4, 1.3); car.add(headlight); }); // Taillights const taillightMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000, emissive: 0xff0000, emissiveIntensity: 0.5 }); [-0.5, 0.5].forEach(x => { const taillight = new THREE.Mesh(headlightGeometry, taillightMaterial); taillight.position.set(x, 0.4, -1.3); car.add(taillight); }); return car; } const car1 = createCar(0xff0000); car1.position.copy(gameStateRef.current.player1.position); scene.add(car1); const car2 = createCar(0x0000ff); car2.position.copy(gameStateRef.current.player2.position); scene.add(car2); // Particle system for exhaust and dust function createParticle(position, color) { const geometry = new THREE.SphereGeometry(0.1); const material = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.6 }); const particle = new THREE.Mesh(geometry, material); particle.position.copy(position); particle.userData = { velocity: new THREE.Vector3( (Math.random() - 0.5) * 0.1, Math.random() * 0.1, -0.2 ), life: 1.0 }; scene.add(particle); return particle; } // Keyboard controls const handleKeyDown = (e) => { gameStateRef.current.keys[e.key.toLowerCase()] = true; // Camera switch if (e.key === 'c' || e.key === 'C') { setCameraMode(prev => { const modes = ['follow', 'topDown', 'firstPerson']; const currentIndex = modes.indexOf(prev); return modes[(currentIndex + 1) % modes.length]; }); } }; const handleKeyUp = (e) => { gameStateRef.current.keys[e.key.toLowerCase()] = false; }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); // Game loop const clock = new THREE.Clock(); let particleCounter = 0; function animate() { const delta = clock.getDelta(); const state = gameStateRef.current; if (!winner) { // Start timer on first movement [state.player1, state.player2].forEach((player, index) => { if (!player.startTime && (player.speed !== 0 || state.keys[index === 0 ? 'w' : 'arrowup'])) { player.startTime = Date.now(); } }); // Player 1 controls (WASD) const p1 = state.player1; const maxSpeed = p1.boost > 0 ? 0.8 : 0.5; if (state.keys['w']) p1.speed = Math.min(p1.speed + 0.4, maxSpeed); else if (state.keys['s']) p1.speed = Math.max(p1.speed - 0.4, -0.25); else p1.speed *= 0.96; if (state.keys['a']) p1.rotation += 0.06; if (state.keys['d']) p1.rotation -= 0.06; // Player 2 controls (Arrow keys) const p2 = state.player2; const maxSpeed2 = p2.boost > 0 ? 0.8 : 0.5; if (state.keys['arrowup']) p2.speed = Math.min(p2.speed + 0.4, maxSpeed2); else if (state.keys['arrowdown']) p2.speed = Math.max(p2.speed - 0.4, -0.25); else p2.speed *= 0.96; if (state.keys['arrowleft']) p2.rotation += 0.06; if (state.keys['arrowright']) p2.rotation -= 0.06; // Update positions and physics [p1, p2].forEach((player, index) => { const car = index === 0 ? car1 : car2; // Apply boost if (player.boost > 0) { player.boost -= delta; } // Movement player.position.x += Math.sin(player.rotation) * player.speed; player.position.z += Math.cos(player.rotation) * player.speed; // Collision with borders if (Math.abs(player.position.x) > 7) { player.position.x = Math.sign(player.position.x) * 7; player.speed *= 0.5; if (soundsRef.current.collision) soundsRef.current.collision(); } // Collision with obstacles state.obstacles.forEach(obstacle => { const distance = player.position.distanceTo(obstacle.position); if (distance < 2) { const pushBack = new THREE.Vector3() .subVectors(player.position, obstacle.position) .normalize() .multiplyScalar(0.5); player.position.add(pushBack); player.speed *= 0.3; if (soundsRef.current.collision) soundsRef.current.collision(); } }); // Check power-ups state.powerUps.forEach(powerUp => { const distance = player.position.distanceTo(powerUp.position); if (distance < 2 && powerUp.mesh.visible) { if (powerUp.type === 'boost') { player.boost = 2; powerUp.mesh.visible = false; if (soundsRef.current.boost) soundsRef.current.boost(); setTimeout(() => { powerUp.mesh.visible = true; }, 5000); } } }); // Check checkpoints state.checkpoints.forEach((checkpoint, cpIndex) => { if (!checkpoint.passed[index] && Math.abs(player.position.z - checkpoint.z) < 1) { checkpoint.passed[index] = true; if (soundsRef.current.checkpoint) soundsRef.current.checkpoint(); // Check if finish line if (cpIndex === state.checkpoints.length - 1) { // Lap completed const lapTime = Date.now() - player.startTime; setCurrentLap(prev => ({ ...prev, [`player${index + 1}`]: prev[`player${index + 1}`] + 1 })); setLapTimes(prev => ({ ...prev, [`player${index + 1}`]: [...prev[`player${index + 1}`], lapTime] })); // Check if race is complete if (currentLap[`player${index + 1}`] >= state.totalLaps - 1) { const playerName = index === 0 ? 'Player 1' : 'Player 2'; setWinner(playerName); setScores(prevScores => ({ ...prevScores, [`player${index + 1}`]: prevScores[`player${index + 1}`] + 1 })); if (soundsRef.current.win) soundsRef.current.win(); } else { // Reset checkpoints for next lap state.checkpoints.forEach(cp => cp.passed[index] = false); player.startTime = Date.now(); } } } }); car.position.copy(player.position); car.rotation.y = player.rotation; // Tilt car when turning const turnAmount = state.keys[index === 0 ? 'a' : 'arrowleft'] ? 0.15 : state.keys[index === 0 ? 'd' : 'arrowright'] ? -0.15 : 0; car.rotation.z = THREE.MathUtils.lerp(car.rotation.z, turnAmount, 0.1); // Create particles if (Math.abs(player.speed) > 0.1 && particleCounter % 3 === 0) { const exhaustPos = new THREE.Vector3( player.position.x - Math.sin(player.rotation) * 1.2, 0.3, player.position.z - Math.cos(player.rotation) * 1.2 ); const particle = createParticle(exhaustPos, player.boost > 0 ? 0xff6600 : 0x666666); player.particles.push(particle); } // Update and remove old particles player.particles = player.particles.filter(particle => { particle.position.add(particle.userData.velocity); particle.userData.life -= delta; particle.material.opacity = particle.userData.life * 0.6; if (particle.userData.life <= 0) { scene.remove(particle); particle.geometry.dispose(); particle.material.dispose(); return false; } return true; }); }); particleCounter++; // Camera modes const avgZ = (p1.position.z + p2.position.z) / 2; const avgX = (p1.position.x + p2.position.x) / 2; switch (cameraMode) { case 'follow': camera.position.x = THREE.MathUtils.lerp(camera.position.x, avgX, 0.1); camera.position.y = 20; camera.position.z = THREE.MathUtils.lerp(camera.position.z, avgZ - 20, 0.1); camera.lookAt(avgX, 0, avgZ + 10); break; case 'topDown': camera.position.x = avgX; camera.position.y = 50; camera.position.z = avgZ; camera.lookAt(avgX, 0, avgZ + 20); break; case 'firstPerson': camera.position.x = p1.position.x; camera.position.y = p1.position.y + 1; camera.position.z = p1.position.z; camera.lookAt( p1.position.x + Math.sin(p1.rotation) * 10, p1.position.y, p1.position.z + Math.cos(p1.rotation) * 10 ); break; } // Animate power-up pads state.powerUps.forEach(powerUp => { if (powerUp.mesh.visible) { powerUp.mesh.rotation.z += 0.02; powerUp.mesh.position.y = 0.1 + Math.sin(Date.now() * 0.003) * 0.1; } }); } renderer.render(scene, camera); requestAnimationFrame(animate); } animate(); // Cleanup return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); if (mountRef.current && renderer.domElement.parentNode === mountRef.current) { mountRef.current.removeChild(renderer.domElement); } // Dispose of all geometries and materials scene.traverse(object => { if (object.geometry) object.geometry.dispose(); if (object.material) { if (Array.isArray(object.material)) { object.material.forEach(material => material.dispose()); } else { object.material.dispose(); } } }); renderer.dispose(); }; }, [winner, cameraMode, currentLap]); const resetGame = () => { setWinner(null); setLapTimes({ player1: [], player2: [] }); setCurrentLap({ player1: 0, player2: 0 }); gameStateRef.current.player1 = { position: new THREE.Vector3(-2, 0.5, 0), rotation: 0, speed: 0, boost: 0, startTime: null, particles: [] }; gameStateRef.current.player2 = { position: new THREE.Vector3(2, 0.5, 0), rotation: 0, speed: 0, boost: 0, startTime: null, particles: [] }; gameStateRef.current.checkpoints.forEach(cp => cp.passed = [false, false]); }; const formatTime = (ms) => { const seconds = Math.floor(ms / 1000); const milliseconds = Math.floor((ms % 1000) / 10); return `${seconds}.${milliseconds.toString().padStart(2, '0')}s`; }; return (