🎮ArcadeLab

Atrapa Aliens 👽 | Nave espacial

by NeonPirate32
655 lines24.8 KB
▶ Play
<!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.