Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Cosmic Defender - Space Shooter</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700&display=swap'); | |
:root { | |
--primary: #00f0ff; | |
--secondary: #7b2dff; | |
--tertiary: #ff2d7b; | |
--dark: #0a0a1a; | |
--darker: #050510; | |
--light: #e0e0ff; | |
--accent: #ff2d7b; | |
} | |
body { | |
margin: 0; | |
overflow: hidden; | |
font-family: 'Space Grotesk', 'Orbitron', sans-serif; | |
background-color: var(--dark); | |
color: var(--light); | |
touch-action: none; | |
} | |
#canvas { | |
display: block; | |
position: fixed; | |
top: 0; | |
left: 0; | |
z-index: 1; | |
} | |
#game-container { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
z-index: 2; | |
} | |
#player-ship { | |
position: absolute; | |
width: 60px; | |
height: 60px; | |
z-index: 10; | |
transition: transform 0.1s ease; | |
filter: drop-shadow(0 0 5px var(--primary)); | |
} | |
.projectile { | |
position: absolute; | |
width: 24px; | |
height: 8px; | |
z-index: 5; | |
filter: drop-shadow(0 0 3px var(--primary)); | |
} | |
.enemy-ship { | |
position: absolute; | |
width: 50px; | |
height: 50px; | |
z-index: 5; | |
filter: drop-shadow(0 0 5px var(--tertiary)); | |
} | |
.explosion { | |
position: absolute; | |
width: 120px; | |
height: 120px; | |
z-index: 6; | |
pointer-events: none; | |
} | |
.explosion-particle { | |
position: absolute; | |
border-radius: 50%; | |
pointer-events: none; | |
} | |
.spark { | |
position: absolute; | |
width: 3px; | |
height: 3px; | |
border-radius: 50%; | |
pointer-events: none; | |
filter: blur(0.5px); | |
} | |
.smoke { | |
position: absolute; | |
border-radius: 50%; | |
pointer-events: none; | |
filter: blur(5px); | |
opacity: 0.8; | |
} | |
.shockwave { | |
position: absolute; | |
border-radius: 50%; | |
pointer-events: none; | |
border: 2px solid rgba(255, 255, 255, 0.7); | |
transform: scale(0); | |
opacity: 1; | |
} | |
@keyframes explode { | |
0% { transform: scale(0.5); opacity: 1; } | |
100% { transform: scale(2); opacity: 0; } | |
} | |
@keyframes particle-fly { | |
0% { transform: translate(0, 0) scale(1); opacity: 1; } | |
100% { transform: translate(var(--tx), var(--ty)) scale(0.2); opacity: 0; } | |
} | |
@keyframes spark-fly { | |
0% { transform: translate(0, 0); opacity: 1; } | |
100% { transform: translate(var(--tx), var(--ty)); opacity: 0; } | |
} | |
@keyframes smoke-rise { | |
0% { transform: translate(0, 0) scale(0.5); opacity: 0.8; } | |
100% { transform: translate(var(--tx), var(--ty)) scale(2); opacity: 0; } | |
} | |
@keyframes shockwave { | |
0% { transform: scale(0); opacity: 1; } | |
100% { transform: scale(3); opacity: 0; } | |
} | |
#hud { | |
position: fixed; | |
top: 20px; | |
left: 20px; | |
z-index: 20; | |
color: white; | |
font-family: 'Orbitron', sans-serif; | |
text-shadow: 0 0 5px var(--primary); | |
} | |
#game-over { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.8); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 30; | |
color: white; | |
font-size: 2rem; | |
display: none; | |
} | |
#restart-btn { | |
margin-top: 20px; | |
padding: 10px 20px; | |
background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
border: none; | |
border-radius: 5px; | |
color: white; | |
font-family: 'Orbitron', sans-serif; | |
cursor: pointer; | |
font-size: 1rem; | |
} | |
.glass-panel { | |
background: rgba(10, 10, 26, 0.4); | |
backdrop-filter: blur(16px); | |
-webkit-backdrop-filter: blur(16px); | |
border-radius: 1.5rem; | |
border: 1px solid rgba(224, 224, 255, 0.1); | |
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
padding: 1.25rem; | |
transition: all 0.3s ease; | |
} | |
.glass-panel:hover { | |
border-color: rgba(224, 224, 255, 0.2); | |
box-shadow: 0 8px 32px rgba(0, 240, 255, 0.1); | |
} | |
</style> | |
</head> | |
<body> | |
<div id="canvas"></div> | |
<div id="game-container"> | |
<div id="player-ship"></div> | |
<div id="hud"> | |
<div>SCORE: <span id="score">0</span></div> | |
<div>HEALTH: <span id="health">100</span>%</div> | |
<div>ENEMIES: <span id="enemies">0</span></div> | |
</div> | |
<div id="game-over"> | |
<h1>GAME OVER</h1> | |
<p>Final Score: <span id="final-score">0</span></p> | |
<button id="restart-btn">PLAY AGAIN</button> | |
</div> | |
</div> | |
<script> | |
// Main Three.js variables for background | |
let scene, camera, renderer; | |
let wormhole, particles = []; | |
let explosionParticles = []; | |
let debrisParticles = []; | |
let sparkParticles = []; | |
let starParticles = []; | |
// Game variables | |
let player = { | |
x: 100, | |
y: window.innerHeight / 2, | |
width: 60, | |
height: 60, | |
speed: 8, | |
health: 100, | |
lastShot: 0, | |
shootDelay: 300 | |
}; | |
let projectiles = []; | |
let enemies = []; | |
let explosions = []; | |
let score = 0; | |
let gameRunning = true; | |
let enemySpawnRate = 1000; // ms | |
let lastEnemySpawn = 0; | |
let keys = { | |
ArrowUp: false, | |
ArrowDown: false, | |
ArrowLeft: false, | |
ArrowRight: false, | |
' ': false | |
}; | |
// Procedural SVG generation functions | |
function generatePlayerShip() { | |
const shipId = 'player-ship-' + Date.now(); | |
const svg = ` | |
<svg id="${shipId}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"> | |
<defs> | |
<linearGradient id="shipGradient" x1="0%" y1="0%" x2="100%" y2="100%"> | |
<stop offset="0%" stop-color="#00f0ff" /> | |
<stop offset="100%" stop-color="#7b2dff" /> | |
</linearGradient> | |
<radialGradient id="engineGlow" cx="20%" cy="50%" r="50%" fx="20%" fy="50%"> | |
<stop offset="0%" stop-color="#ff2d7b" stop-opacity="0.8" /> | |
<stop offset="100%" stop-color="#ff2d7b" stop-opacity="0" /> | |
</radialGradient> | |
</defs> | |
<!-- Main hull --> | |
<path d="M30,5 L55,30 L45,55 L15,55 L5,30 Z" fill="url(#shipGradient)" stroke="#ffffff" stroke-width="1" /> | |
<!-- Cockpit --> | |
<ellipse cx="30" cy="25" rx="10" ry="8" fill="#0a0a1a" stroke="#ffffff" stroke-width="1" /> | |
<ellipse cx="30" cy="25" rx="6" ry="4" fill="#00f0ff" opacity="0.7" /> | |
<!-- Engine glow --> | |
<path d="M15,50 L45,50 L40,60 L20,60 Z" fill="url(#engineGlow)" /> | |
<!-- Details --> | |
<path d="M30,5 L30,25" stroke="#ffffff" stroke-width="1" stroke-dasharray="2,2" /> | |
<path d="M15,55 L5,30 L15,20" stroke="#ffffff" stroke-width="1" fill="none" /> | |
<path d="M45,55 L55,30 L45,20" stroke="#ffffff" stroke-width="1" fill="none" /> | |
<!-- Thrusters --> | |
<rect x="20" y="50" width="5" height="8" fill="#00f0ff" rx="1" /> | |
<rect x="35" y="50" width="5" height="8" fill="#00f0ff" rx="1" /> | |
</svg> | |
`; | |
return svg; | |
} | |
function generateEnemyShip() { | |
const shipId = 'enemy-ship-' + Date.now(); | |
const hue = Math.floor(Math.random() * 60) + 300; // Purple-red range | |
const svg = ` | |
<svg id="${shipId}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"> | |
<defs> | |
<linearGradient id="enemyGradient" x1="0%" y1="0%" x2="100%" y2="100%"> | |
<stop offset="0%" stop-color="hsl(${hue}, 100%, 60%)" /> | |
<stop offset="100%" stop-color="hsl(${hue + 20}, 100%, 40%)" /> | |
</linearGradient> | |
<radialGradient id="enemyGlow" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"> | |
<stop offset="0%" stop-color="hsl(${hue}, 100%, 80%)" stop-opacity="0.8" /> | |
<stop offset="100%" stop-color="hsl(${hue}, 100%, 60%)" stop-opacity="0" /> | |
</radialGradient> | |
</defs> | |
<!-- Main body --> | |
<path d="M25,5 L45,25 L25,45 L5,25 Z" fill="url(#enemyGradient)" stroke="#ffffff" stroke-width="1" /> | |
<!-- Core --> | |
<circle cx="25" cy="25" r="8" fill="url(#enemyGlow)" /> | |
<circle cx="25" cy="25" r="5" fill="#0a0a1a" /> | |
<!-- Spikes --> | |
<path d="M25,5 L30,0 L35,5 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
<path d="M45,25 L50,20 L50,30 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
<path d="M25,45 L30,50 L35,45 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
<path d="M5,25 L0,20 L0,30 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
<!-- Details --> | |
<path d="M15,15 L35,35" stroke="#ffffff" stroke-width="1" stroke-dasharray="1,1" /> | |
<path d="M15,35 L35,15" stroke="#ffffff" stroke-width="1" stroke-dasharray="1,1" /> | |
</svg> | |
`; | |
return svg; | |
} | |
function generateProjectile() { | |
const projId = 'projectile-' + Date.now(); | |
const svg = ` | |
<svg id="${projId}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 8"> | |
<defs> | |
<linearGradient id="projGradient" x1="0%" y1="50%" x2="100%" y2="50%"> | |
<stop offset="0%" stop-color="#00f0ff" /> | |
<stop offset="50%" stop-color="#7b2dff" /> | |
<stop offset="100%" stop-color="#00f0ff" /> | |
</linearGradient> | |
<radialGradient id="projGlow" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"> | |
<stop offset="0%" stop-color="#00f0ff" stop-opacity="0.8" /> | |
<stop offset="100%" stop-color="#00f0ff" stop-opacity="0" /> | |
</radialGradient> | |
</defs> | |
<!-- Projectile body --> | |
<rect x="0" y="2" width="24" height="4" rx="2" fill="url(#projGradient)" /> | |
<!-- Glow effects --> | |
<ellipse cx="12" cy="4" rx="12" ry="4" fill="url(#projGlow)" /> | |
<!-- Front tip --> | |
<path d="M24,2 L28,4 L24,6 Z" fill="#00f0ff" /> | |
</svg> | |
`; | |
return svg; | |
} | |
// Initialize the background scene | |
function initBackground() { | |
// Create scene | |
scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0x0a0a1a); | |
scene.fog = new THREE.FogExp2(0x0a0a1a, 0.002); | |
// Create camera | |
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); | |
camera.position.set(0, 0, 50); | |
// Create renderer | |
renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMap.enabled = true; | |
renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
document.getElementById('canvas').appendChild(renderer.domElement); | |
// Create lights | |
const ambientLight = new THREE.AmbientLight(0x404040); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 1); | |
directionalLight.position.set(1, 1, 1); | |
directionalLight.castShadow = true; | |
scene.add(directionalLight); | |
// Add colorful ambient light | |
const coloredLight1 = new THREE.PointLight(0x7b2dff, 0.5, 100); | |
coloredLight1.position.set(20, 20, 20); | |
scene.add(coloredLight1); | |
const coloredLight2 = new THREE.PointLight(0x00f0ff, 0.5, 100); | |
coloredLight2.position.set(-20, -20, -20); | |
scene.add(coloredLight2); | |
// Create wormhole | |
createWormhole(); | |
// Create particles | |
createParticles(); | |
} | |
// Create wormhole geometry | |
function createWormhole() { | |
const geometry = new THREE.TorusGeometry(15, 3, 32, 100); | |
const material = new THREE.MeshPhongMaterial({ | |
color: 0x00aaff, | |
emissive: 0x0066ff, | |
emissiveIntensity: 0.8, | |
transparent: true, | |
opacity: 0.9, | |
wireframe: true, | |
wireframeLinewidth: 2 | |
}); | |
wormhole = new THREE.Mesh(geometry, material); | |
wormhole.rotation.x = Math.PI / 2; | |
scene.add(wormhole); | |
// Add inner glow | |
const innerGlowGeometry = new THREE.SphereGeometry(12, 32, 32); | |
const innerGlowMaterial = new THREE.MeshBasicMaterial({ | |
color: 0x00aaff, | |
transparent: true, | |
opacity: 0.3 | |
}); | |
const innerGlow = new THREE.Mesh(innerGlowGeometry, innerGlowMaterial); | |
wormhole.add(innerGlow); | |
// Add energy field | |
const energyFieldGeometry = new THREE.SphereGeometry(16, 64, 64); | |
const energyFieldMaterial = new THREE.MeshBasicMaterial({ | |
color: 0x7b2dff, | |
transparent: true, | |
opacity: 0.05, | |
wireframe: true | |
}); | |
const energyField = new THREE.Mesh(energyFieldGeometry, energyFieldMaterial); | |
wormhole.add(energyField); | |
} | |
// Create various particles | |
function createParticles() { | |
// Stars background | |
const starGeometry = new THREE.BufferGeometry(); | |
const starMaterial = new THREE.PointsMaterial({ | |
color: 0xffffff, | |
size: 0.2, | |
transparent: true, | |
opacity: 0.8 | |
}); | |
const starPositions = []; | |
const starColors = []; | |
for (let i = 0; i < 2000; i++) { | |
starPositions.push( | |
Math.random() * 2000 - 1000, | |
Math.random() * 2000 - 1000, | |
Math.random() * 2000 - 1000 | |
); | |
// Add some color variation | |
const colorIntensity = 0.7 + Math.random() * 0.3; | |
starColors.push( | |
colorIntensity, | |
colorIntensity, | |
colorIntensity | |
); | |
} | |
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3)); | |
starGeometry.setAttribute('color', new THREE.Float32BufferAttribute(starColors, 3)); | |
const stars = new THREE.Points(starGeometry, starMaterial); | |
scene.add(stars); | |
starParticles.push(stars); | |
// Debris | |
for (let i = 0; i < 150; i++) { | |
const size = 0.2 + Math.random() * 1.5; | |
const debris = new THREE.Mesh( | |
new THREE.BoxGeometry(size, size, size), | |
new THREE.MeshPhongMaterial({ | |
color: 0x888888, | |
emissive: 0x333333, | |
emissiveIntensity: 0.1 | |
}) | |
); | |
debris.position.set( | |
Math.random() * 100 - 50, | |
Math.random() * 100 - 50, | |
Math.random() * 100 - 50 | |
); | |
debris.rotation.set( | |
Math.random() * Math.PI, | |
Math.random() * Math.PI, | |
Math.random() * Math.PI | |
); | |
debris.userData = { | |
velocity: new THREE.Vector3( | |
Math.random() * 0.2 - 0.1, | |
Math.random() * 0.2 - 0.1, | |
Math.random() * 0.2 - 0.1 | |
), | |
rotationSpeed: new THREE.Vector3( | |
Math.random() * 0.02 - 0.01, | |
Math.random() * 0.02 - 0.01, | |
Math.random() * 0.02 - 0.01 | |
) | |
}; | |
scene.add(debris); | |
debrisParticles.push(debris); | |
} | |
} | |
// Initialize the game | |
function initGame() { | |
// Create player ship with procedural SVG | |
const playerShip = document.getElementById('player-ship'); | |
playerShip.innerHTML = generatePlayerShip(); | |
// Position player ship | |
updatePlayerPosition(); | |
// Set up event listeners | |
window.addEventListener('keydown', handleKeyDown); | |
window.addEventListener('keyup', handleKeyUp); | |
window.addEventListener('resize', onWindowResize); | |
document.getElementById('restart-btn').addEventListener('click', resetGame); | |
// Start game loop | |
gameLoop(); | |
} | |
// Handle key down events | |
function handleKeyDown(e) { | |
if (keys.hasOwnProperty(e.key)) { | |
keys[e.key] = true; | |
e.preventDefault(); | |
} | |
} | |
// Handle key up events | |
function handleKeyUp(e) { | |
if (keys.hasOwnProperty(e.key)) { | |
keys[e.key] = false; | |
e.preventDefault(); | |
} | |
} | |
// Update player position based on key states | |
function updatePlayerPosition() { | |
if (keys.ArrowUp && player.y > player.height / 2) { | |
player.y -= player.speed; | |
} | |
if (keys.ArrowDown && player.y < window.innerHeight - player.height / 2) { | |
player.y += player.speed; | |
} | |
if (keys.ArrowLeft && player.x > player.width / 2) { | |
player.x -= player.speed; | |
} | |
if (keys.ArrowRight && player.x < window.innerWidth - player.width / 2) { | |
player.x += player.speed; | |
} | |
// Update DOM element position | |
const ship = document.getElementById('player-ship'); | |
ship.style.left = `${player.x - player.width / 2}px`; | |
ship.style.top = `${player.y - player.height / 2}px`; | |
// Shoot if space is pressed and delay has passed | |
const now = Date.now(); | |
if (keys[' '] && now - player.lastShot > player.shootDelay) { | |
shoot(); | |
player.lastShot = now; | |
} | |
} | |
// Create a new projectile | |
function shoot() { | |
const projectile = document.createElement('div'); | |
projectile.className = 'projectile'; | |
projectile.innerHTML = generateProjectile(); | |
projectile.style.left = `${player.x + player.width / 2}px`; | |
projectile.style.top = `${player.y - 4}px`; | |
document.getElementById('game-container').appendChild(projectile); | |
projectiles.push({ | |
element: projectile, | |
x: player.x + player.width / 2, | |
y: player.y, | |
speed: 12, | |
width: 24, | |
height: 8 | |
}); | |
} | |
// Update all projectiles | |
function updateProjectiles() { | |
for (let i = projectiles.length - 1; i >= 0; i--) { | |
const p = projectiles[i]; | |
p.x += p.speed; | |
p.element.style.left = `${p.x}px`; | |
// Remove if off screen | |
if (p.x > window.innerWidth) { | |
p.element.remove(); | |
projectiles.splice(i, 1); | |
} | |
} | |
} | |
// Spawn a new enemy | |
function spawnEnemy() { | |
const enemy = document.createElement('div'); | |
enemy.className = 'enemy-ship'; | |
enemy.innerHTML = generateEnemyShip(); | |
const y = Math.random() * (window.innerHeight - 100) + 50; | |
enemy.style.left = `${window.innerWidth}px`; | |
enemy.style.top = `${y - 25}px`; | |
document.getElementById('game-container').appendChild(enemy); | |
enemies.push({ | |
element: enemy, | |
x: window.innerWidth, | |
y: y, | |
speed: 3 + Math.random() * 3, | |
width: 50, | |
height: 50 | |
}); | |
} | |
// Update all enemies | |
function updateEnemies() { | |
for (let i = enemies.length - 1; i >= 0; i--) { | |
const e = enemies[i]; | |
e.x -= e.speed; | |
e.element.style.left = `${e.x}px`; | |
// Remove if off screen | |
if (e.x < -e.width) { | |
e.element.remove(); | |
enemies.splice(i, 1); | |
} | |
} | |
} | |
// Check for collisions | |
function checkCollisions() { | |
// Projectile-enemy collisions | |
for (let i = projectiles.length - 1; i >= 0; i--) { | |
const p = projectiles[i]; | |
for (let j = enemies.length - 1; j >= 0; j--) { | |
const e = enemies[j]; | |
if (p.x < e.x + e.width && | |
p.x + p.width > e.x && | |
p.y < e.y + e.height && | |
p.y + p.height > e.y) { | |
// Collision detected | |
createEnhancedExplosion(e.x + e.width/2, e.y + e.height/2); | |
// Remove both projectile and enemy | |
p.element.remove(); | |
projectiles.splice(i, 1); | |
e.element.remove(); | |
enemies.splice(j, 1); | |
// Increase score | |
score += 100; | |
document.getElementById('score').textContent = score; | |
document.getElementById('enemies').textContent = enemies.length; | |
break; | |
} | |
} | |
} | |
// Player-enemy collisions | |
for (let i = enemies.length - 1; i >= 0; i--) { | |
const e = enemies[i]; | |
if (player.x < e.x + e.width && | |
player.x + player.width > e.x && | |
player.y < e.y + e.height && | |
player.y + player.height > e.y) { | |
// Collision detected | |
createEnhancedExplosion(e.x + e.width/2, e.y + e.height/2); | |
// Remove enemy | |
e.element.remove(); | |
enemies.splice(i, 1); | |
// Decrease player health | |
player.health -= 20; | |
document.getElementById('health').textContent = player.health; | |
if (player.health <= 0) { | |
gameOver(); | |
} | |
break; | |
} | |
} | |
} | |
// Create enhanced explosion effect with particles, sparks and smoke | |
function createEnhancedExplosion(x, y) { | |
// Create explosion container | |
const explosion = document.createElement('div'); | |
explosion.className = 'explosion'; | |
explosion.style.left = `${x - 60}px`; | |
explosion.style.top = `${y - 60}px`; | |
document.getElementById('game-container').appendChild(explosion); | |
// Create shockwave | |
const shockwave = document.createElement('div'); | |
shockwave.className = 'shockwave'; | |
shockwave.style.left = '60px'; | |
shockwave.style.top = '60px'; | |
shockwave.style.width = '40px'; | |
shockwave.style.height = '40px'; | |
shockwave.style.animation = 'shockwave 0.5s ease-out forwards'; | |
explosion.appendChild(shockwave); | |
// Create core explosion | |
const core = document.createElement('div'); | |
core.className = 'explosion-particle'; | |
core.style.left = '60px'; | |
core.style.top = '60px'; | |
core.style.width = '40px'; | |
core.style.height = '40px'; | |
core.style.background = 'radial-gradient(circle, #ff9900, #ff2d7b)'; | |
core.style.boxShadow = '0 0 20px #ff2d7b'; | |
core.style.animation = 'explode 0.5s forwards'; | |
explosion.appendChild(core); | |
// Create particles (50-100) | |
const particleCount = 50 + Math.floor(Math.random() * 50); | |
for (let i = 0; i < particleCount; i++) { | |
const particle = document.createElement('div'); | |
particle.className = 'explosion-particle'; | |
// Random size between 4-12px | |
const size = 4 + Math.random() * 8; | |
particle.style.width = `${size}px`; | |
particle.style.height = `${size}px`; | |
// Random color (yellow to red) | |
const hue = 20 + Math.random() * 40; | |
particle.style.background = `hsl(${hue}, 100%, 50%)`; | |
particle.style.boxShadow = `0 0 ${size/2}px hsl(${hue}, 100%, 50%)`; | |
// Random position around explosion center | |
const angle = Math.random() * Math.PI * 2; | |
const distance = 10 + Math.random() * 20; | |
const px = 60 + Math.cos(angle) * distance; | |
const py = 60 + Math.sin(angle) * distance; | |
particle.style.left = `${px}px`; | |
particle.style.top = `${py}px`; | |
// Random movement direction and distance | |
const tx = (Math.random() - 0.5) * 100; | |
const ty = (Math.random() - 0.5) * 100; | |
particle.style.setProperty('--tx', `${tx}px`); | |
particle.style.setProperty('--ty', `${ty}px`); | |
particle.style.animation = `particle-fly ${0.5 + Math.random() * 0.5}s ease-out forwards`; | |
explosion.appendChild(particle); | |
} | |
// Create sparks (30-60) | |
const sparkCount = 30 + Math.floor(Math.random() * 30); | |
for (let i = 0; i < sparkCount; i++) { | |
const spark = document.createElement('div'); | |
spark.className = 'spark'; | |
// Random color (white to yellow) | |
const hue = 40 + Math.random() * 20; | |
spark.style.background = `hsl(${hue}, 100%, 80%)`; | |
// Random position around explosion center | |
const angle = Math.random() * Math.PI * 2; | |
const distance = 5 + Math.random() * 15; | |
const px = 60 + Math.cos(angle) * distance; | |
const py = 60 + Math.sin(angle) * distance; | |
spark.style.left = `${px}px`; | |
spark.style.top = `${py}px`; | |
// Random movement direction and distance | |
const tx = (Math.random() - 0.5) * 150; | |
const ty = (Math.random() - 0.5) * 150; | |
spark.style.setProperty('--tx', `${tx}px`); | |
spark.style.setProperty('--ty', `${ty}px`); | |
spark.style.animation = `spark-fly ${0.3 + Math.random() * 0.4}s linear forwards`; | |
explosion.appendChild(spark); | |
} | |
// Create smoke (20-40) | |
const smokeCount = 20 + Math.floor(Math.random() * 20); | |
for (let i = 0; i < smokeCount; i++) { | |
const smoke = document.createElement('div'); | |
smoke.className = 'smoke'; | |
// Random size between 10-30px | |
const size = 10 + Math.random() * 20; | |
smoke.style.width = `${size}px`; | |
smoke.style.height = `${size}px`; | |
// Random gray color | |
const lightness = 20 + Math.random() * 30; | |
smoke.style.background = `hsla(0, 0%, ${lightness}%, 0.7)`; | |
// Random position around explosion center | |
const angle = Math.random() * Math.PI * 2; | |
const distance = 5 + Math.random() * 15; | |
const px = 60 + Math.cos(angle) * distance; | |
const py = 60 + Math.sin(angle) * distance; | |
smoke.style.left = `${px}px`; | |
smoke.style.top = `${py}px`; | |
// Random upward movement with some horizontal variation | |
const tx = (Math.random() - 0.5) * 40; | |
const ty = -30 - Math.random() * 50; | |
smoke.style.setProperty('--tx', `${tx}px`); | |
smoke.style.setProperty('--ty', `${ty}px`); | |
smoke.style.animation = `smoke-rise ${1 + Math.random() * 1}s ease-out forwards`; | |
explosion.appendChild(smoke); | |
} | |
// Remove explosion after animation completes | |
setTimeout(() => { | |
explosion.remove(); | |
}, 1000); | |
} | |
// Game over state | |
function gameOver() { | |
gameRunning = false; | |
document.getElementById('final-score').textContent = score; | |
document.getElementById('game-over').style.display = 'flex'; | |
} | |
// Reset the game | |
function resetGame() { | |
// Clear all elements | |
projectiles.forEach(p => p.element.remove()); | |
enemies.forEach(e => e.element.remove()); | |
// Reset variables | |
projectiles = []; | |
enemies = []; | |
player.health = 100; | |
score = 0; | |
gameRunning = true; | |
// Update HUD | |
document.getElementById('score').textContent = score; | |
document.getElementById('health').textContent = player.health; | |
document.getElementById('enemies').textContent = enemies.length; | |
document.getElementById('game-over').style.display = 'none'; | |
// Reset player position | |
player.x = 100; | |
player.y = window.innerHeight / 2; | |
updatePlayerPosition(); | |
// Regenerate player ship | |
const playerShip = document.getElementById('player-ship'); | |
playerShip.innerHTML = generatePlayerShip(); | |
} | |
// Handle window resize | |
function onWindowResize() { | |
// Update Three.js renderer | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
// Update player position if needed | |
if (player.y > window.innerHeight - player.height / 2) { | |
player.y = window.innerHeight - player.height / 2; | |
updatePlayerPosition(); | |
} | |
} | |
// Main game loop | |
function gameLoop() { | |
if (!gameRunning) return; | |
// Update background animation | |
updateBackground(); | |
// Update game elements | |
updatePlayerPosition(); | |
updateProjectiles(); | |
updateEnemies(); | |
checkCollisions(); | |
// Spawn new enemies | |
const now = Date.now(); | |
if (now - lastEnemySpawn > enemySpawnRate) { | |
spawnEnemy(); | |
lastEnemySpawn = now; | |
// Increase difficulty | |
if (score > 0 && score % 1000 === 0) { | |
enemySpawnRate = Math.max(300, enemySpawnRate - 50); | |
} | |
} | |
// Continue loop | |
requestAnimationFrame(gameLoop); | |
} | |
// Update background animation | |
function updateBackground() { | |
// Rotate wormhole | |
wormhole.rotation.z += 0.005; | |
// Update debris particles | |
debrisParticles.forEach(debris => { | |
debris.position.add(debris.userData.velocity); | |
debris.rotation.x += debris.userData.rotationSpeed.x; | |
debris.rotation.y += debris.userData.rotationSpeed.y; | |
debris.rotation.z += debris.userData.rotationSpeed.z; | |
// Wrap around if out of bounds | |
if (Math.abs(debris.position.x) > 100) debris.position.x = -debris.position.x; | |
if (Math.abs(debris.position.y) > 100) debris.position.y = -debris.position.y; | |
if (Math.abs(debris.position.z) > 100) debris.position.z = -debris.position.z; | |
}); | |
renderer.render(scene, camera); | |
} | |
// Initialize everything | |
function init() { | |
initBackground(); | |
initGame(); | |
} | |
// Start the game | |
init(); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LukasBe/andromeda-blast" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |