Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>📱 Cosmic Dodger</title> | |
| <style> | |
| * { margin: 0; padding: 0; touch-action: none; } | |
| body { overflow: hidden; background: #000; } | |
| #hud { | |
| position: fixed; | |
| top: 15px; | |
| left: 15px; | |
| color: #fff; | |
| font-family: 'Courier New', monospace; | |
| font-size: 26px; | |
| text-shadow: 0 0 10px #00ffff; | |
| z-index: 100; | |
| } | |
| #gameOver { | |
| display: none; | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| text-align: center; | |
| color: #fff; | |
| background: rgba(0,0,0,0.95); | |
| padding: 25px; | |
| border-radius: 15px; | |
| border: 2px solid #ff0066; | |
| width: 80%; | |
| max-width: 300px; | |
| } | |
| #restart { | |
| display: none; | |
| position: fixed; | |
| bottom: 25px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| padding: 15px 35px; | |
| background: #00ff88; | |
| color: #000; | |
| border: none; | |
| border-radius: 25px; | |
| font-size: 20px; | |
| cursor: pointer; | |
| font-weight: bold; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="hud"> | |
| <div>SCORE: <span id="score">0</span></div> | |
| <div>LIVES: <span id="lives">3</span></div> | |
| </div> | |
| <div id="gameOver"> | |
| <h1 style="color: #ff0066; font-size: 1.8em">GAME OVER</h1> | |
| <p style="font-size: 1.2em">Score: <span id="finalscore">0</span></p> | |
| </div> | |
| <button id="restart" onclick="location.reload()">PLAY AGAIN</button> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script> | |
| const CONFIG = { | |
| PLAYER_SPEED: 0.25, | |
| ASTEROID_SPEED: 0.15, | |
| PLAYER_SIZE: 0.6, | |
| X_BOUNDS: 6, | |
| Y_BOUNDS: 4 | |
| }; | |
| let score = 0, lives = 3, isGameOver = false; | |
| let touchStartX = 0, currentX = 0; | |
| const scene = new THREE.Scene(); | |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| document.body.appendChild(renderer.domElement); | |
| // Player (Flat Triangle - Mobile Friendly) | |
| const player = new THREE.Mesh( | |
| new THREE.ConeGeometry(CONFIG.PLAYER_SIZE, 1.2, 3), | |
| new THREE.MeshStandardMaterial({ | |
| color: 0x00ff88, | |
| emissive: 0x00ff88, | |
| emissiveIntensity: 0.5 | |
| }) | |
| ); | |
| player.rotation.x = Math.PI/2; | |
| player.position.z = 3; | |
| scene.add(player); | |
| // Player Boundary Markers | |
| const boundaryMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x00ffff, | |
| transparent: true, | |
| opacity: 0.2 | |
| }); | |
| const leftBoundary = new THREE.Mesh( | |
| new THREE.PlaneGeometry(0.5, 10), | |
| boundaryMaterial | |
| ); | |
| const rightBoundary = leftBoundary.clone(); | |
| leftBoundary.position.x = -CONFIG.X_BOUNDS; | |
| rightBoundary.position.x = CONFIG.X_BOUNDS; | |
| scene.add(leftBoundary, rightBoundary); | |
| // Asteroids (More Visible) | |
| function createAsteroid() { | |
| const asteroid = new THREE.Mesh( | |
| new THREE.IcosahedronGeometry(0.8), | |
| new THREE.MeshStandardMaterial({ | |
| color: 0xff4444, | |
| emissive: 0xff0000, | |
| emissiveIntensity: 0.3, | |
| wireframe: true | |
| }) | |
| ); | |
| asteroid.position.set( | |
| (Math.random() - 0.5) * 12, | |
| (Math.random() - 0.5) * 7, | |
| -40 | |
| ); | |
| return asteroid; | |
| } | |
| // Starfield (Optimized) | |
| const stars = new THREE.Points( | |
| new THREE.BufferGeometry().setFromPoints( | |
| Array(800).fill().map(() => new THREE.Vector3( | |
| (Math.random() - 0.5) * 100, | |
| (Math.random() - 0.5) * 100, | |
| (Math.random() - 0.5) * 100 | |
| )) | |
| ), | |
| new THREE.PointsMaterial({ | |
| size: 0.2, | |
| color: 0xffffff, | |
| transparent: true, | |
| opacity: 0.7 | |
| }) | |
| ); | |
| scene.add(stars); | |
| // Lighting | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.8); | |
| scene.add(ambientLight); | |
| // Controls (Improved Mobile Handling) | |
| function handleMove(x) { | |
| if(isGameOver) return; | |
| currentX = THREE.MathUtils.clamp( | |
| (x - touchStartX) * 0.03, | |
| -CONFIG.X_BOUNDS, | |
| CONFIG.X_BOUNDS | |
| ); | |
| player.position.x = currentX; | |
| } | |
| document.addEventListener('touchstart', e => { | |
| e.preventDefault(); | |
| touchStartX = e.touches[0].clientX; | |
| }); | |
| document.addEventListener('touchmove', e => handleMove(e.touches[0].clientX)); | |
| document.addEventListener('mousedown', e => touchStartX = e.clientX); | |
| document.addEventListener('mousemove', e => e.buttons === 1 && handleMove(e.clientX)); | |
| // Game Loop | |
| function animate() { | |
| if(isGameOver) return; | |
| // Spawn Logic | |
| if(Math.random() < 0.03 + score/5000) { | |
| const asteroid = createAsteroid(); | |
| scene.add(asteroid); | |
| // Animate asteroid | |
| (function move(obj) { | |
| obj.position.z += CONFIG.ASTEROID_SPEED + score/2500; | |
| obj.rotation.x += 0.02; | |
| obj.rotation.y += 0.02; | |
| if(obj.position.z > 5) scene.remove(obj); | |
| else requestAnimationFrame(() => move(obj)); | |
| })(asteroid); | |
| } | |
| // Visual Updates | |
| stars.rotation.y += 0.0003; | |
| player.rotation.z = Math.sin(performance.now()*0.005) * 0.3; | |
| // Collision Check | |
| scene.children.forEach(obj => { | |
| if(obj !== player && obj.position.z > 0) { | |
| if(obj.position.distanceTo(player.position) < 1.2) { | |
| lives--; | |
| scene.remove(obj); | |
| updateHUD(); | |
| if(lives <= 0) endGame(); | |
| } | |
| } | |
| }); | |
| renderer.render(scene, camera); | |
| requestAnimationFrame(animate); | |
| } | |
| function updateHUD() { | |
| document.getElementById('score').textContent = Math.floor(score); | |
| document.getElementById('lives').textContent = lives; | |
| score += 1 + Math.floor(score/1000); | |
| } | |
| function endGame() { | |
| isGameOver = true; | |
| document.getElementById('gameOver').style.display = 'block'; | |
| document.getElementById('restart').style.display = 'block'; | |
| document.getElementById('finalscore').textContent = Math.floor(score); | |
| localStorage.setItem('highScore', Math.max(score, localStorage.getItem('highScore') || 0)); | |
| } | |
| // Initial Setup | |
| camera.position.z = 8; | |
| animate(); | |
| updateHUD(); | |
| // Resize Handler | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // Restart Handler | |
| document.addEventListener('touchend', () => isGameOver && location.reload()); | |
| document.addEventListener('mouseup', () => isGameOver && location.reload()); | |
| </script> | |
| </body> | |
| </html> |