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> |