Space Shooter - Táctil para iPhone
by ApexHero69465 lines17.2 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, 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.