andromeda-blast / index.html
LukasBe's picture
Add 3 files
6939b8e verified
<!DOCTYPE html>
<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>