Atrapa Aliens 👽 | Nave espacial
by NeonPirate32655 lines24.8 KB
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Atrapa Aliens 👽 | Nave espacial</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
min-height: 100vh;
background: radial-gradient(circle at center, #030318 0%, #000000 100%);
display: flex;
justify-content: center;
align-items: center;
font-family: 'Orbitron', 'Courier New', monospace;
overflow: hidden;
position: relative;
}
/* Fondo estrellas dinámicas */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 15% 30%, #fff, rgba(0,0,0,0)),
radial-gradient(3px 3px at 75% 18%, #f8f9fa, rgba(0,0,0,0)),
radial-gradient(1px 1px at 92% 65%, #ffe6b0, rgba(0,0,0,0)),
radial-gradient(2px 2px at 8% 85%, #ffe0b3, rgba(0,0,0,0)),
radial-gradient(1px 1px at 42% 53%, #cdedff, rgba(0,0,0,0)),
radial-gradient(3px 3px at 64% 91%, #ffffff, rgba(0,0,0,0));
background-repeat: no-repeat;
background-size: 200px 200px, 300px 300px, 150px 150px, 250px 250px, 220px 220px, 280px 280px;
opacity: 0.7;
pointer-events: none;
z-index: 0;
animation: starTwinkle 3s infinite alternate;
}
@keyframes starTwinkle {
0% { opacity: 0.4; }
100% { opacity: 1; }
}
/* Contenedor principal juego */
.game-container {
position: relative;
width: 900px;
max-width: 95vw;
height: 650px;
max-height: 85vh;
background: rgba(0, 0, 20, 0.6);
border-radius: 32px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6), inset 0 0 30px rgba(0, 255, 255, 0.1);
backdrop-filter: blur(2px);
border: 1px solid rgba(66, 220, 255, 0.3);
overflow: hidden;
cursor: none;
z-index: 2;
}
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
cursor: none;
}
/* Panel de puntaje flotante elegante */
.score-panel {
position: absolute;
top: 20px;
right: 30px;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(8px);
padding: 12px 24px;
border-radius: 60px;
border: 1px solid rgba(0, 255, 255, 0.6);
box-shadow: 0 0 15px rgba(0, 230, 250, 0.3);
font-family: 'Orbitron', monospace;
z-index: 20;
text-align: center;
letter-spacing: 2px;
}
.score-label {
font-size: 1.1rem;
color: #b0f0ff;
text-shadow: 0 0 5px cyan;
font-weight: 500;
}
.score-value {
font-size: 3rem;
font-weight: bold;
color: #ffd966;
text-shadow: 0 0 10px #ffaa33, 0 0 5px #ff8800;
line-height: 1;
margin-left: 10px;
display: inline-block;
min-width: 80px;
text-align: right;
}
/* mensaje de inicio / game over */
.message-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(12px);
padding: 20px 35px;
border-radius: 50px;
text-align: center;
pointer-events: none;
z-index: 25;
border: 1px solid #2effec;
box-shadow: 0 0 50px rgba(0, 255, 255, 0.3);
transition: all 0.2s ease;
font-family: 'Orbitron', monospace;
white-space: nowrap;
}
.message-text {
font-size: 1.8rem;
font-weight: bold;
color: #fff5b0;
text-shadow: 0 0 8px #ffcc44;
letter-spacing: 2px;
}
.restart-btn {
margin-top: 15px;
background: #ffaa33;
border: none;
font-family: 'Orbitron', monospace;
font-weight: bold;
padding: 8px 20px;
border-radius: 40px;
cursor: pointer;
font-size: 1rem;
color: #0a0a2a;
transition: all 0.2s;
pointer-events: auto;
box-shadow: 0 0 10px #ffaa44;
}
.restart-btn:hover {
background: #ffcc55;
transform: scale(1.02);
box-shadow: 0 0 18px #ffdd88;
}
/* ocultar overlay cuando está jugando */
.hidden {
display: none;
}
/* instrucción sutil */
.instruction {
position: absolute;
bottom: 12px;
left: 0;
right: 0;
text-align: center;
color: #9bc4d4;
font-family: monospace;
font-size: 0.75rem;
background: rgba(0,0,0,0.5);
width: fit-content;
margin: 0 auto;
padding: 5px 15px;
border-radius: 30px;
pointer-events: none;
z-index: 20;
backdrop-filter: blur(4px);
}
@media (max-width: 600px) {
.score-value { font-size: 2.2rem; min-width: 60px; }
.score-label { font-size: 0.8rem; }
.message-text { font-size: 1.2rem; white-space: nowrap; }
.restart-btn { font-size: 0.8rem; padding: 6px 16px;}
}
</style>
</head>
<body>
<div class="game-container">
<canvas id="gameCanvas" width="900" height="650"></canvas>
<div class="score-panel">
<span class="score-label">👾 PUNTUACIÓN 👾</span>
<span class="score-value" id="scoreDisplay">0</span>
</div>
<div class="instruction">🖱️ Mueve el mouse → la nave sigue al cursor | Atrapa los 👽</div>
<div id="gameOverlay" class="message-overlay">
<div class="message-text" id="overlayMessage">🚀 ¡ATRAPA ALIENS! 🚀</div>
<button id="restartButton" class="restart-btn">▶ INICIAR JUEGO</button>
</div>
</div>
<script>
(function(){
// ---------- CONFIGURACIÓN DEL JUEGO ----------
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreSpan = document.getElementById('scoreDisplay');
const overlayDiv = document.getElementById('gameOverlay');
const overlayMessage = document.getElementById('overlayMessage');
// Ajustar tamaño real del canvas con resolución exacta (para evitar borrosidad)
function resizeCanvas() {
const container = canvas.parentElement;
const rect = container.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
}
window.addEventListener('resize', () => { resizeCanvas(); drawStatic(); });
resizeCanvas();
// Variables del juego
let gameRunning = true; // si false -> muestra game over
let score = 0;
let aliens = []; // cada alien: { x, y, radius, speedY }
let nave = { x: canvas.width/2, y: 0, width: 50, height: 45 }; // y se fijará abajo
let frameRequest;
let lastTimestamp = 0;
let spawnCounter = 0;
let spawnDelayFrames = 30; // frames entre spawns (aprox 0.5s a 60fps)
// parámetros del juego
const ALIEN_BASE_RADIUS = 22; // tamaño 👽 dibujo
const ALIEN_BASE_SPEED = 2.4;
const MAX_ALIENS = 18;
const NAVEY_OFFSET = 35; // distancia desde el borde inferior
// variables de mouse (movimiento horizontal)
let mouseX = canvas.width/2;
let mouseInside = true;
// ----- Actualizar nave Y según canvas height -----
function updateNaveY() {
nave.y = canvas.height - NAVEY_OFFSET;
}
// ----- MANEJO DE MOUSE -----
function handleMouseMove(e) {
if (!gameRunning) return;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width; // por si hay diferencia de tamaño
let clientX;
if (e.touches) {
// para soporte táctil simple (opcional)
clientX = e.touches[0].clientX;
e.preventDefault();
} else {
clientX = e.clientX;
}
let canvasX = (clientX - rect.left) * scaleX;
canvasX = Math.min(Math.max(canvasX, nave.width/2), canvas.width - nave.width/2);
nave.x = canvasX;
mouseX = nave.x;
}
function handleTouchMove(e) {
e.preventDefault();
if (!gameRunning) return;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
let touchX = e.touches[0].clientX;
let canvasX = (touchX - rect.left) * scaleX;
canvasX = Math.min(Math.max(canvasX, nave.width/2), canvas.width - nave.width/2);
nave.x = canvasX;
mouseX = nave.x;
}
// mouseleave: no hacer nada raro, pero evitamos que se vaya
function handleMouseLeave() {
// si el mouse sale, la nave se queda en su última posición (no se mueve)
// pero no rompemos el juego
}
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('touchmove', handleTouchMove, { passive: false });
canvas.addEventListener('touchstart', handleTouchMove);
canvas.addEventListener('mouseleave', handleMouseLeave);
// ----- FUNCIONES DEL JUEGO -----
function addAlien() {
if (!gameRunning) return;
if (aliens.length >= MAX_ALIENS) return;
let radius = ALIEN_BASE_RADIUS;
let x = Math.random() * (canvas.width - radius * 2) + radius;
let speedY = ALIEN_BASE_SPEED + Math.random() * 1.5;
aliens.push({
x: x,
y: -radius, // aparece desde arriba
radius: radius,
speedY: speedY
});
}
// actualizar posición de aliens y colisiones
function updateAliens() {
if (!gameRunning) return;
for (let i = 0; i < aliens.length; i++) {
const a = aliens[i];
a.y += a.speedY;
}
// COLLISION DETECTION: nave y alien (basado en distancias circulares simplificadas)
// La nave tendrá un área de captura circular y rectangular animado, pero usamos radio aprox
const naveRadius = nave.width * 0.4; // radio de colisión ~20px
for (let i = aliens.length-1; i >= 0; i--) {
const a = aliens[i];
// colisión con nave (atrapado)
const dx = a.x - nave.x;
const dy = a.y - nave.y;
const distancia = Math.hypot(dx, dy);
const colisionRad = a.radius + naveRadius;
if (distancia < colisionRad) {
// Captura alien 👽
aliens.splice(i,1);
score++;
updateScoreUI();
// pequeño efecto de vibración o punto extra (feedback visual opcional)
continue;
}
// si el alien se sale por abajo (pierde vida? no, no penalizamos, solo desaparece)
if (a.y + a.radius > canvas.height + 40) {
aliens.splice(i,1);
}
// también si sale por arriba (muy raro) limpiar
else if (a.y + a.radius < -50) {
aliens.splice(i,1);
}
}
// Spawn automático de aliens (control de frecuencia)
if (gameRunning) {
if (spawnCounter <= 0) {
addAlien();
// ajustar dinámicamente: entre 25 y 45 frames
let nextSpawn = Math.max(22, Math.min(42, 35 - Math.floor(score / 20)));
spawnCounter = nextSpawn;
} else {
spawnCounter--;
}
}
}
// actualizar el marcador en pantalla
function updateScoreUI() {
scoreSpan.innerText = score;
}
// ----- RENDER COMPLETO (fondo, aliens, nave, efectos) -----
function drawStars() {
// fondo oscuro sólido + estrellitas dibujadas aleatoriamente (se regenera cada frame para sensación)
ctx.fillStyle = "#02021a";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.shadowBlur = 0;
for(let i = 0; i < 150; i++) {
if (i%2 === 0) continue; // solo algunas aleatorias por frame, está bien
let sx = (i * 131) % canvas.width;
let sy = (i * 253) % canvas.height;
ctx.fillStyle = `rgba(255, 240, 200, ${0.3 + Math.sin(Date.now() * 0.001 + i) * 0.2})`;
ctx.beginPath();
ctx.arc(sx, sy, 1 + (i%3), 0, Math.PI*2);
ctx.fill();
}
// estrellas fijas pero bonitas:
for(let i = 0; i < 80; i++) {
let sx = (i * 79) % canvas.width;
let sy = (i * 131) % canvas.height;
ctx.fillStyle = `rgba(255, 255, 210, 0.7)`;
ctx.beginPath();
ctx.arc(sx, sy, 1, 0, Math.PI*2);
ctx.fill();
}
}
// dibujar nave espacial estilo 🛸 pero más pixelart con gradiente
function drawNave() {
const x = nave.x;
const y = nave.y;
const w = nave.width;
const h = nave.height;
// efecto de brillo exterior
ctx.shadowBlur = 12;
ctx.shadowColor = "#2efffa";
// cuerpo principal (platillo)
ctx.beginPath();
ctx.ellipse(x, y-5, w/2, h*0.3, 0, 0, Math.PI*2);
ctx.fillStyle = "#aaccdd";
ctx.fill();
ctx.beginPath();
ctx.ellipse(x, y-5, w/2.3, h*0.25, 0, 0, Math.PI*2);
ctx.fillStyle = "#ddffff";
ctx.fill();
// domo superior
ctx.beginPath();
ctx.arc(x, y-12, w*0.22, 0, Math.PI*2);
ctx.fillStyle = "#88ddff";
ctx.fill();
ctx.beginPath();
ctx.arc(x, y-12, w*0.16, 0, Math.PI*2);
ctx.fillStyle = "#ffffff";
ctx.fill();
// luces laterales
ctx.fillStyle = "#ffaa44";
ctx.shadowBlur = 6;
ctx.beginPath();
ctx.arc(x - w*0.3, y-3, 4, 0, Math.PI*2);
ctx.arc(x + w*0.3, y-3, 4, 0, Math.PI*2);
ctx.fill();
// cuerpo inferior con glaseado
ctx.beginPath();
ctx.ellipse(x, y+2, w*0.45, h*0.2, 0, 0, Math.PI*2);
ctx.fillStyle = "#77aacc";
ctx.fill();
// emoji antena
ctx.font = `${Math.floor(w*0.6)}px "Segoe UI Emoji"`;
ctx.fillStyle = "#ffffbb";
ctx.shadowBlur = 6;
ctx.fillText("🛸", x-14, y+5);
ctx.shadowBlur = 0;
}
// dibujar alien 👽 con estilo caricaturesco
function drawAlien(a) {
const x = a.x;
const y = a.y;
const r = a.radius;
ctx.save();
ctx.shadowBlur = 4;
ctx.shadowColor = "#7eff6b";
// cuerpo
ctx.beginPath();
ctx.ellipse(x, y, r*0.9, r, 0, 0, Math.PI*2);
ctx.fillStyle = "#64b45f";
ctx.fill();
ctx.beginPath();
ctx.ellipse(x, y-3, r*0.7, r*0.6, 0, 0, Math.PI*2);
ctx.fillStyle = "#7fd67a";
ctx.fill();
// ojos enormes
ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.arc(x - r*0.35, y - r*0.1, r*0.28, 0, Math.PI*2);
ctx.arc(x + r*0.35, y - r*0.1, r*0.28, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = "#000000";
ctx.beginPath();
ctx.arc(x - r*0.35, y - r*0.12, r*0.12, 0, Math.PI*2);
ctx.arc(x + r*0.35, y - r*0.12, r*0.12, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(x - r*0.4, y - r*0.18, r*0.05, 0, Math.PI*2);
ctx.arc(x + r*0.3, y - r*0.18, r*0.05, 0, Math.PI*2);
ctx.fill();
// sonrisa simple
ctx.beginPath();
ctx.arc(x, y + r*0.2, r*0.25, 0.05, Math.PI - 0.05);
ctx.strokeStyle = "#3a2a1a";
ctx.lineWidth = 2;
ctx.stroke();
// antenitas
ctx.beginPath();
ctx.moveTo(x - r*0.2, y - r*0.55);
ctx.lineTo(x - r*0.4, y - r*0.85);
ctx.lineTo(x - r*0.15, y - r*0.7);
ctx.fillStyle = "#446633";
ctx.fill();
ctx.beginPath();
ctx.moveTo(x + r*0.2, y - r*0.55);
ctx.lineTo(x + r*0.4, y - r*0.85);
ctx.lineTo(x + r*0.15, y - r*0.7);
ctx.fill();
// emoji alien
ctx.font = `${Math.floor(r*0.9)}px "Segoe UI Emoji"`;
ctx.fillStyle = "#2f4f2f";
ctx.fillText("👽", x-12, y+5);
ctx.restore();
}
function draw() {
if (!canvas || !ctx) return;
// 1. fondo espacial oscuro + estrellas
drawStars();
// 2. Nebulosas suaves
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = "rgba(30, 20, 70, 0.08)";
for(let i=0;i<3;i++) {
ctx.beginPath();
ctx.ellipse(canvas.width*0.2 + Math.sin(Date.now()*0.0005)*20, canvas.height*0.7, 150, 80, 0, 0, Math.PI*2);
ctx.fill();
}
ctx.globalCompositeOperation = 'source-over';
// 3. Dibujar aliens
for(let a of aliens) {
drawAlien(a);
}
// 4. Dibujar nave siempre encima
drawNave();
// 5. Información adicional en juego (efecto captura)
if(gameRunning) {
ctx.font = "bold 14px 'Orbitron'";
ctx.fillStyle = "#77ffff";
ctx.shadowBlur = 0;
ctx.fillText("👉 Mueve el mouse para atrapar 👈", canvas.width/2-140, 35);
}
ctx.shadowBlur = 0;
}
// función para dibujar estático (cuando no hay loop, al redimensionar)
function drawStatic() {
if(!gameRunning && frameRequest) cancelAnimationFrame(frameRequest);
draw();
}
// ----- LOOP PRINCIPAL
function gameLoop() {
if (!gameRunning) {
// si el juego está detenido, solo dibujamos el estado actual y no actualizamos lógica
draw();
frameRequest = requestAnimationFrame(gameLoop);
return;
}
// 1. actualizar posiciones de canvas y nave Y por si cambió tamaño
updateNaveY();
// Asegurar que nave.x no se salga
if (nave.x < nave.width/2) nave.x = nave.width/2;
if (nave.x > canvas.width - nave.width/2) nave.x = canvas.width - nave.width/2;
// 2. actualizar aliens y colisiones
updateAliens();
// 3. renderizar todo
draw();
// 4. seguir el loop
frameRequest = requestAnimationFrame(gameLoop);
}
// ----- REINICIO Y CONTROL DE JUEGO -----
function startNewGame() {
gameRunning = true;
score = 0;
aliens = [];
spawnCounter = 12; // arranque rápido
updateScoreUI();
// resetear posición de nave centrada
nave.x = canvas.width/2;
if (mouseInside && mouseX) nave.x = Math.min(Math.max(mouseX, nave.width/2), canvas.width - nave.width/2);
updateNaveY();
// ocultar el overlay
overlayDiv.classList.add('hidden');
// asegurar que no hay game over mostrado
}
function endGame() {
if (!gameRunning) return;
gameRunning = false;
overlayMessage.innerText = `💀 GAME OVER 💀 Puntaje: ${score}`;
overlayDiv.classList.remove('hidden');
// el botón permanece functional
}
// Condición de fallo: NO hay fallo por aliens que toquen el suelo, pero podemos implementar
// si acumula más de 15 aliens en pantalla desde cierto punto? mejor finalizamos cuando "se escapan muchos"?
// Para que sea retador: un límite de "escapados" sería opcional. Pero la consigna es solo sumar puntos.
// No obstante agregaremos un game over opcional: No, esta versión es infinita y sin perder.
// Para dar emoción: no hay game over, solo diversión. Pero si el usuario quiere reiniciar manual con el botón.
// Pero el botón reinicio reinicia la partida aunque esté corriendo, perfecto:
function restartGame() {
// detener el loop por un instante? no, solo reasignamos
gameRunning = true; // aseguramos
score = 0;
aliens = [];
spawnCounter = 10;
updateScoreUI();
nave.x = canvas.width/2;
if (mouseX) nave.x = Math.min(Math.max(mouseX, nave.width/2), canvas.width - nave.width/2);
updateNaveY();
overlayDiv.classList.add('hidden');
}
// Inicio con overlay interactivo
const restartBtn = document.getElementById('restartButton');
restartBtn.addEventListener('click', (e) => {
e.stopPropagation();
restartGame();
});
// iniciar el juego en modo pausa (esperando inicio)
function initialPause() {
gameRunning = false;
overlayDiv.classList.remove('hidden');
overlayMessage.innerText = "🚀 ¡ATRAPA ALIENS! 🚀\n✨ Mueve el mouse ✨";
drawStatic();
}
// Resize event hace que se ajuste la nave Y y redibuje
window.addEventListener('resize', () => {
resizeCanvas();
updateNaveY();
if(!gameRunning) drawStatic();
});
// Iniciar el loop aunque esté en pausa
initialPause();
gameLoop();
// Ajuste inicial para coordenadas del mouse al ingresar
canvas.addEventListener('mouseenter', (e) => {
if(!gameRunning) return;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
let canvasX = (e.clientX - rect.left) * scaleX;
canvasX = Math.min(Math.max(canvasX, nave.width/2), canvas.width - nave.width/2);
nave.x = canvasX;
});
// Si se presiona el botón de inicio de nuevo mientras juega => reinicia sin problemas
// También si alguien quiere repetir, el botón funciona.
// evitamos que el overlay tenga doble click falso.
// Nota: el juego NO termina a menos que se reinicie manualmente — es estilo arcade infinito.
// Para mejorar, el usuario puede reiniciar cuando quiera.
console.log("Juego listo | Atrapa aliens con la nave espacial");
})();
</script>
</body>
</html>Game Source: Atrapa Aliens 👽 | Nave espacial
Creator: NeonPirate32
Libraries: none
Complexity: complex (655 lines, 24.8 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: atrapa-aliens-nave-espacial-neonpirate32" to link back to the original. Then publish at arcadelab.ai/publish.