🎮ArcadeLab

Space Shooter - Táctil para iPhone

by ApexHero69
465 lines17.2 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, viewport-fit=cover">
    <title>Space Shooter - Táctil para iPhone</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            touch-action: manipulation;
        }

        body {
            background: black;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: 'Courier New', monospace;
            touch-action: pan-x pan-y; /* permite scroll pero lo limitamos en canvas */
        }

        .game-container {
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        canvas {
            display: block;
            margin: 0 auto;
            background: radial-gradient(circle at center, #0a0f1e, #03050a);
            box-shadow: 0 0 20px rgba(0, 200, 255, 0.2);
            cursor: pointer;
            width: 100%;
            height: auto;
        }

        #score {
            position: absolute;
            top: 15px;
            left: 20px;
            color: cyan;
            font-size: 1.4rem;
            font-weight: bold;
            text-shadow: 0 0 5px #00aaff;
            z-index: 10;
            pointer-events: none;
            font-family: monospace;
        }

        #restart-btn {
            position: absolute;
            bottom: 20px;
            right: 20px;
            background: rgba(0,0,0,0.6);
            color: white;
            border: 1px solid cyan;
            padding: 8px 16px;
            border-radius: 30px;
            font-size: 0.9rem;
            font-weight: bold;
            backdrop-filter: blur(4px);
            z-index: 20;
            cursor: pointer;
            touch-action: manipulation;
            font-family: monospace;
        }

        .controls-info {
            position: absolute;
            bottom: 20px;
            left: 20px;
            color: rgba(255,255,255,0.5);
            font-size: 0.7rem;
            background: rgba(0,0,0,0.5);
            padding: 6px 10px;
            border-radius: 20px;
            backdrop-filter: blur(4px);
            pointer-events: none;
            font-family: monospace;
        }

        @media (max-width: 700px) {
            .controls-info { font-size: 0.6rem; }
            #score { font-size: 1.2rem; }
        }
    </style>
</head>
<body>
<div class="game-container">
    <canvas id="gameCanvas" width="400" height="600"></canvas>
    <div id="score">💥 SCORE: 0</div>
    <button id="restart-btn">🔄 REINICIAR</button>
    <div class="controls-info">👆 TOQUE IZQUIERDA / DERECHA → Mover nave | 🔫 DISPARO AUTOMÁTICO</div>
</div>

<script>
    (function(){
        // ----- CONFIGURACIÓN -----
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const scoreElement = document.getElementById('score');

        // Ajustar tamaño REAL en píxeles lógicos (fijo para el juego, pero se escala en CSS)
        // Mantenemos un canvas interno de 400x600 para cálculos consistentes
        canvas.width = 400;
        canvas.height = 600;
        
        // Variables del juego
        let gameRunning = true;
        let score = 0;
        
        // Nave
        const PLAYER_WIDTH = 32;
        const PLAYER_HEIGHT = 32;
        let playerX = canvas.width/2 - PLAYER_WIDTH/2;
        const playerY = canvas.height - 60;
        
        // Disparos
        let bullets = [];
        const BULLET_WIDTH = 4;
        const BULLET_HEIGHT = 12;
        const BULLET_SPEED = 6;
        let shotCooldown = 0;
        const SHOT_DELAY_FRAMES = 12; // disparos cada ~12 frames (aprox 60fps -> 5 por segundo)
        
        // Enemigos
        let enemies = [];
        const ENEMY_WIDTH = 30;
        const ENEMY_HEIGHT = 30;
        const ENEMY_BASE_SPEED = 2.5;
        let enemySpawnCounter = 0;
        const ENEMY_SPAWN_DELAY_FRAMES = 28; // cada 28 frames aparece un enemigo
        
        // Controles táctiles: estado de movimiento (izquierda / derecha / ninguno)
        let moveLeft = false;
        let moveRight = false;
        
        // Velocidad movimiento nave (píxeles por frame)
        const PLAYER_SPEED = 5.5;
        
        // ----- FUNCIONES AUXILIARES -----
        function updateScoreUI() {
            scoreElement.innerText = `💥 SCORE: ${Math.floor(score)}`;
        }
        
        // Reiniciar juego completamente
        function restartGame() {
            gameRunning = true;
            score = 0;
            bullets = [];
            enemies = [];
            playerX = canvas.width/2 - PLAYER_WIDTH/2;
            shotCooldown = 0;
            enemySpawnCounter = 5; // para que aparezcan rápido
            moveLeft = false;
            moveRight = false;
            updateScoreUI();
        }
        
        // Disparar bala desde el centro de la nave
        function shootBullet() {
            if (!gameRunning) return;
            bullets.push({
                x: playerX + PLAYER_WIDTH/2 - BULLET_WIDTH/2,
                y: playerY - 8,
                w: BULLET_WIDTH,
                h: BULLET_HEIGHT
            });
        }
        
        // Actualizar lógica del juego
        function updateGame() {
            if (!gameRunning) return;
            
            // 1. Movimiento nave (táctil)
            if (moveLeft && !moveRight) {
                playerX -= PLAYER_SPEED;
            }
            if (moveRight && !moveLeft) {
                playerX += PLAYER_SPEED;
            }
            // Limitar dentro del canvas
            if (playerX < 0) playerX = 0;
            if (playerX + PLAYER_WIDTH > canvas.width) playerX = canvas.width - PLAYER_WIDTH;
            
            // 2. Cooldown de disparo automático
            if (shotCooldown <= 0) {
                shootBullet();
                shotCooldown = SHOT_DELAY_FRAMES;
            } else {
                shotCooldown--;
            }
            
            // 3. Actualizar balas
            for (let i = 0; i < bullets.length; i++) {
                bullets[i].y -= BULLET_SPEED;
                if (bullets[i].y + bullets[i].h < 0 || bullets[i].y > canvas.height) {
                    bullets.splice(i,1);
                    i--;
                }
            }
            
            // 4. Actualizar enemigos y colisiones con balas
            for (let i = 0; i < enemies.length; i++) {
                enemies[i].y += enemies[i].speed;
                // Si sale de la pantalla por abajo -> game over (choca con base)
                if (enemies[i].y + ENEMY_HEIGHT > canvas.height + 40) {
                    gameRunning = false;
                    return;
                }
            }
            
            // Colisiones balas vs enemigos (recorrer al revés)
            for (let i = bullets.length-1; i >= 0; i--) {
                const bullet = bullets[i];
                let hitIndex = -1;
                for (let j = 0; j < enemies.length; j++) {
                    const e = enemies[j];
                    if (bullet.x < e.x + ENEMY_WIDTH &&
                        bullet.x + BULLET_WIDTH > e.x &&
                        bullet.y < e.y + ENEMY_HEIGHT &&
                        bullet.y + BULLET_HEIGHT > e.y) {
                        hitIndex = j;
                        break;
                    }
                }
                if (hitIndex !== -1) {
                    // Eliminar bala y enemigo, sumar puntos
                    bullets.splice(i,1);
                    enemies.splice(hitIndex,1);
                    score += 10;
                    updateScoreUI();
                }
            }
            
            // 5. Colisión nave vs enemigos (game over)
            for (let i = 0; i < enemies.length; i++) {
                const e = enemies[i];
                if (playerX < e.x + ENEMY_WIDTH &&
                    playerX + PLAYER_WIDTH > e.x &&
                    playerY < e.y + ENEMY_HEIGHT &&
                    playerY + PLAYER_HEIGHT > e.y) {
                    gameRunning = false;
                    break;
                }
            }
            
            // 6. Spawn de enemigos (solo si el juego sigue activo)
            if (gameRunning) {
                if (enemySpawnCounter <= 0) {
                    let randX = Math.random() * (canvas.width - ENEMY_WIDTH);
                    let speedVar = ENEMY_BASE_SPEED + Math.random() * 1.5;
                    enemies.push({
                        x: randX,
                        y: -ENEMY_HEIGHT,
                        w: ENEMY_WIDTH,
                        h: ENEMY_HEIGHT,
                        speed: speedVar
                    });
                    enemySpawnCounter = ENEMY_SPAWN_DELAY_FRAMES;
                    // Dificultad: con más puntuación, spawneamos más rápido
                    let dynamicDelay = Math.max(18, ENEMY_SPAWN_DELAY_FRAMES - Math.floor(score / 300));
                    enemySpawnCounter = dynamicDelay;
                } else {
                    enemySpawnCounter--;
                }
            }
        }
        
        // ----- DIBUJADO (gráficos estilo retro) -----
        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // Estrellas de fondo (estáticas pero pequeñas)
            ctx.fillStyle = "white";
            for (let i = 0; i < 150; i++) {
                if (i%2 === 0) continue; // solo para no poner demasiado código, las estrellas fijas
                let sx = (i * 131) % canvas.width;
                let sy = (i * 253) % canvas.height;
                ctx.fillRect(sx, sy, 1.5, 1.5);
            }
            // mejor estrellas aleatorias con ruido suave
            for (let s = 0; s < 80; s++) {
                let sx = (s * 179) % canvas.width;
                let sy = (s * 311) % canvas.height;
                ctx.fillStyle = `rgba(255,240,200,${0.5+Math.sin(Date.now()*0.001+s)*0.3})`;
                ctx.fillRect(sx, sy, 1.2, 1.2);
            }
            
            // ---- Nave Jugador (triángulo estilizado) ----
            ctx.save();
            ctx.shadowBlur = 0;
            ctx.beginPath();
            // Nave estilo caza triangular
            const centerX = playerX + PLAYER_WIDTH/2;
            const bottomY = playerY + PLAYER_HEIGHT;
            ctx.moveTo(centerX, playerY);                              // punta arriba
            ctx.lineTo(playerX + PLAYER_WIDTH - 6, bottomY - 6);
            ctx.lineTo(playerX + PLAYER_WIDTH - 2, bottomY);
            ctx.lineTo(centerX, bottomY - 4);
            ctx.lineTo(playerX + 2, bottomY);
            ctx.lineTo(playerX + 6, bottomY - 6);
            ctx.closePath();
            ctx.fillStyle = "#2ad4ff";
            ctx.fill();
            ctx.strokeStyle = "#ffffff";
            ctx.lineWidth = 1.5;
            ctx.stroke();
            // Ala glow
            ctx.fillStyle = "#00a6c4";
            ctx.beginPath();
            ctx.rect(playerX+12, playerY+18, 8, 10);
            ctx.fill();
            
            // ---- Balas (proyectiles) ----
            for (let b of bullets) {
                ctx.fillStyle = "#ffff60";
                ctx.shadowBlur = 8;
                ctx.shadowColor = "#ffaa00";
                ctx.fillRect(b.x, b.y, b.w, b.h);
                ctx.fillStyle = "orange";
                ctx.fillRect(b.x+1, b.y+2, b.w-2, b.h-4);
            }
            
            // ---- Enemigos (rojos, forma de nave alien) ----
            for (let e of enemies) {
                ctx.shadowBlur = 4;
                ctx.shadowColor = "#ff0000";
                ctx.fillStyle = "#d64545";
                ctx.beginPath();
                ctx.ellipse(e.x+ENEMY_WIDTH/2, e.y+ENEMY_HEIGHT/2, ENEMY_WIDTH/2, ENEMY_HEIGHT/2, 0, 0, Math.PI*2);
                ctx.fill();
                ctx.fillStyle = "#a1222a";
                ctx.beginPath();
                ctx.rect(e.x+8, e.y+10, 6, 12);
                ctx.rect(e.x+16, e.y+10, 6, 12);
                ctx.fill();
                ctx.fillStyle = "black";
                ctx.fillRect(e.x+10, e.y+18, 4, 6);
                ctx.fillRect(e.x+18, e.y+18, 4, 6);
            }
            
            // Si el juego terminó, mensaje
            if (!gameRunning) {
                ctx.font = "bold 24px monospace";
                ctx.shadowBlur = 0;
                ctx.fillStyle = "#ff3366";
                ctx.shadowColor = "black";
                ctx.textAlign = "center";
                ctx.fillText("💀 GAME OVER 💀", canvas.width/2, canvas.height/2 - 40);
                ctx.font = "16px monospace";
                ctx.fillStyle = "cyan";
                ctx.fillText("Toca REINICIAR", canvas.width/2, canvas.height/2 + 30);
                ctx.textAlign = "left";
            }
            
            ctx.shadowBlur = 0;
            // Información de puntuación extra en canvas
            ctx.font = "bold 14px monospace";
            ctx.fillStyle = "#bbffff";
            ctx.fillText("⚡", 10, 45);
        }
        
        // ----- CONTROL TÁCTIL PARA IPHONE -----
        function handleTouchStart(e) {
            e.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;   // canvas lógico 400 / ancho visual en px
            const touch = e.touches[0];
            // calcular coordenada X relativa al canvas en píxeles lógicos (0..400)
            let touchX = (touch.clientX - rect.left) * scaleX;
            if (touchX < 0) touchX = 0;
            if (touchX > canvas.width) touchX = canvas.width;
            
            // Mitad izquierda (0 a 200) -> mover izquierda; derecha (200 a 400) -> mover derecha
            if (touchX < canvas.width/2) {
                moveLeft = true;
                moveRight = false;
            } else {
                moveRight = true;
                moveLeft = false;
            }
        }
        
        function handleTouchMove(e) {
            e.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const touch = e.touches[0];
            let touchX = (touch.clientX - rect.left) * scaleX;
            if (touchX < 0) touchX = 0;
            if (touchX > canvas.width) touchX = canvas.width;
            
            if (touchX < canvas.width/2) {
                moveLeft = true;
                moveRight = false;
            } else {
                moveRight = true;
                moveLeft = false;
            }
        }
        
        function handleTouchEnd(e) {
            e.preventDefault();
            // Detener movimiento cuando se levanta el dedo (cualquier dedo)
            moveLeft = false;
            moveRight = false;
        }
        
        // Para evitar que el scroll y zoom interfieran
        canvas.addEventListener('touchstart', handleTouchStart, { passive: false });
        canvas.addEventListener('touchmove', handleTouchMove, { passive: false });
        canvas.addEventListener('touchend', handleTouchEnd);
        canvas.addEventListener('touchcancel', handleTouchEnd);
        
        // También soporte mouse para depuración en PC (opcional, pero útil)
        canvas.addEventListener('mousemove', (e) => {
            if (window.innerWidth > 900) { // si es PC, permitimos mouse
                const rect = canvas.getBoundingClientRect();
                const scaleX = canvas.width / rect.width;
                let mouseX = (e.clientX - rect.left) * scaleX;
                if (mouseX >= 0 && mouseX <= canvas.width) {
                    playerX = mouseX - PLAYER_WIDTH/2;
                    if (playerX < 0) playerX = 0;
                    if (playerX + PLAYER_WIDTH > canvas.width) playerX = canvas.width - PLAYER_WIDTH;
                }
            }
        });
        
        // Botón reiniciar
        const restartBtn = document.getElementById('restart-btn');
        restartBtn.addEventListener('click', (e) => {
            e.preventDefault();
            restartGame();
        });
        restartBtn.addEventListener('touchstart', (e) => {
            e.preventDefault();
            restartGame();
        });
        
        // Ajuste para que en móvil no se pueda hacer zoom con doble tap en canvas
        canvas.addEventListener('touchstart', (e) => {
            if (e.touches.length > 1) e.preventDefault();
        }, { passive: false });
        
        // BUCLE ANIMACIÓN
        function gameLoop() {
            updateGame();
            draw();
            requestAnimationFrame(gameLoop);
        }
        
        // Iniciar juego
        restartGame();
        gameLoop();
    })();
</script>
</body>
</html>

Game Source: Space Shooter - Táctil para iPhone

Creator: ApexHero69

Libraries: none

Complexity: complex (465 lines, 17.2 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: space-shooter-t-ctil-para-iphone-apexhero69" to link back to the original. Then publish at arcadelab.ai/publish.