Discusión

Juego de Batalla de Tanques 2D en JavaScript con Canvas

De Wikiprompt, la enciclopedia libre de prompts

Dan ⚡️
Contribuido porDan ⚡️XFuente

7 ago 2025

Juego de Batalla de Tanques 2D en JavaScript con Canvas Un prompt integral para generar un juego de batalla de tanques 2D totalmente funcional usando JavaScript y HTML5 Canvas, con especificaciones detalladas para jugabilidad, terreno, IA y personalización.

Contenido del PromptGuardar

🌐
const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); let gridSize = 36; let tileSize = Math.floor(Math.min(window.innerWidth, window.innerHeight) / gridSize); canvas.width = tileSize * gridSize; canvas.height = tileSize * gridSize; window.addEventListener('resize', () => { tileSize = Math.floor(Math.min(window.innerWidth, window.innerHeight) / gridSize); canvas.width = tileSize * gridSize; canvas.height = tileSize * gridSize; draw(); }); let gameState = 'menu'; let player = { x: 1, y: 1, dir: 'up', lives: 3, cooldown: 0, color: '#4CAF50', special: false }; let enemies = []; let bullets = []; let terrain = []; let score = 0; let level = 1; let startTime = 0; let highScore = localStorage.getItem('tankHighScore') || 0; let enemyCount = 5; let selectedColor = '#4CAF50'; let keys = {}; let touchStart = null; const terrainTypes = { solid: { color: '#555', shootable: false, passable: false }, breakable: { color: '#B8860B', shootable: true, passable: false, hp: 2 }, water: { color: '#1E90FF', shootable: true, passable: false }, mud: { color: '#8B4513', shootable: true, passable: true, speed: 0.5 }, ground: { color: '#2E8B57', shootable: true, passable: true } }; function generateMap() { terrain = []; for (let y = 0; y < gridSize; y++) { terrain[y] = []; for (let x = 0; x < gridSize; x++) { if (x === 0 || y === 0 || x === gridSize - 1 || y === gridSize - 1) { terrain[y][x] = { type: 'solid', ...terrainTypes.solid }; } else if (Math.random() < 0.1) { terrain[y][x] = { type: 'breakable', ...terrainTypes.breakable, hp: 2 }; } else if (Math.random() < 0.05) { terrain[y][x] = { type: 'water', ...terrainTypes.water }; } else if (Math.random() < 0.05) { terrain[y][x] = { type: 'mud', ...terrainTypes.mud }; } else { terrain[y][x] = { type: 'ground', ...terrainTypes.ground }; } } } } function spawnEnemies(count) { enemies = []; for (let i = 0; i < count; i++) { let x, y; do { x = Math.floor(Math.random() * (gridSize - 2)) + 1; y = Math.floor(Math.random() * (gridSize - 2)) + 1; } while ((x === player.x && y === player.y) || terrain[y][x].type !== 'ground'); let behavior = Math.random() < 0.33 ? 'aggressive' : Math.random() < 0.5 ? 'defensive' : 'patrol'; enemies.push({ x, y, dir: 'up', cooldown: 0, behavior, color: behavior === 'aggressive' ? '#FF4444' : behavior === 'defensive' ? '#FFA500' : '#FF69B4', speed: 1 + level * 0.1, special: false }); } } function resetGame() { player = { x: 1, y: 1, dir: 'down', lives: 3, cooldown: 0, color: selectedColor, special: false }; score = 0; level = 1; startTime = Date.now(); generateMap(); spawnEnemies(enemyCount); gameState = 'playing'; } function drawTank(x, y, dir, color, isPlayer) { const cx = x * tileSize + tileSize / 2; const cy = y * tileSize + tileSize / 2; ctx.save(); ctx.translate(cx, cy); ctx.rotate({ up: 0, right: Math.PI / 2, down: Math.PI, left: -Math.PI / 2 }[dir]); ctx.fillStyle = color; ctx.fillRect(-tileSize * 0.3, -tileSize * 0.4, tileSize * 0.6, tileSize * 0.8); ctx.fillStyle = '#333'; ctx.fillRect(-tileSize * 0.1, -tileSize * 0.5, tileSize * 0.2, tileSize * 0.2); ctx.fillStyle = '#222'; ctx.beginPath(); ctx.arc(0, 0, tileSize * 0.2, 0, Math.PI * 2); ctx.fill(); if (isPlayer && player.special) { ctx.fillStyle = '#FFD700'; ctx.beginPath(); ctx.arc(0, 0, tileSize * 0.1, 0, Math.PI * 2); ctx.fill(); } ctx.restore(); } function drawTerrain() { for (let y = 0; y < gridSize; y++) { for (let x = 0; x < gridSize; x++) { const tile = terrain[y][x]; ctx.fillStyle = tile.color; ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize); if (tile.type === 'breakable' && tile.hp < 2) { ctx.fillStyle = 'rgba(0,0,0,0.3)'; ctx.fillRect(x * tileSize, y * tileSize, tileSize * (1 - tile.hp / 2), tileSize); } } } } function drawBullets() { bullets.forEach(b => { ctx.fillStyle = b.special ? '#FFD700' : '#FF0000'; ctx.beginPath(); ctx.arc(b.x * tileSize + tileSize / 2, b.y * tileSize + tileSize / 2, tileSize * 0.15, 0, Math.PI * 2); ctx.fill(); }); } function drawUI() { ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0, 0, canvas.width, 40); ctx.fillStyle = '#FFF'; ctx.font = '16px monospace'; ctx.fillText(`Score: ${score}`, 10, 25); ctx.fillText(`Lives: ${'♥'.repeat(player.lives)}`, 150, 25); ctx.fillText(`Level: ${level}`, 300, 25); ctx.fillText(`Time: ${Math.floor((Date.now() - startTime) / 1000)}s`, 450, 25); ctx.fillText(`High: ${highScore}`, 600, 25); } function drawMenu() { ctx.fillStyle = '#1a1a2e'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#FFF'; ctx.font = '48px monospace'; ctx.textAlign = 'center'; ctx.fillText('TANK BATTLE', canvas.width / 2, 100); ctx.font = '20px monospace'; ctx.fillText('Enemies: ' + enemyCount, canvas.width / 2, 180); ctx.fillText('Color: ' + selectedColor, canvas.width / 2, 220); ctx.fillText('Press ENTER to start', canvas.width / 2, 300); ctx.fillText('Use Arrow Keys / Swipe to move', canvas.width / 2, 340); ctx.fillText('Space / Tap to shoot', canvas.width / 2, 380); ctx.textAlign = 'left'; } function drawGameOver() { ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#FFF'; ctx.font = '48px monospace'; ctx.textAlign = 'center'; ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2 - 50); ctx.font = '24px monospace'; ctx.fillText(`Score: ${score}`, canvas.width / 2, canvas.height / 2 + 10); ctx.fillText(`Time: ${Math.floor((Date.now() - startTime) / 1000)}s`, canvas.width / 2, canvas.height / 2 + 50); ctx.fillText('Press ENTER to restart', canvas.width / 2, canvas.height / 2 + 100); ctx.textAlign = 'left'; } function drawVictory() { ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#FFD700'; ctx.font = '48px monospace'; ctx.textAlign = 'center'; ctx.fillText('VICTORY!', canvas.width / 2, canvas.height / 2 - 50); ctx.fillStyle = '#FFF'; ctx.font = '24px monospace'; ctx.fillText(`Score: ${score}`, canvas.width / 2, canvas.height / 2 + 10); ctx.fillText(`Time: ${Math.floor((Date.now() - startTime) / 1000)}s`, canvas.width / 2, canvas.height / 2 + 50); ctx.fillText('Press ENTER for next level', canvas.width / 2, canvas.height / 2 + 100); ctx.textAlign = 'left'; } function update() { if (gameState !== 'playing') return; if (player.cooldown > 0) player.cooldown -= 1 / 60; enemies.forEach(e => { if (e.cooldown > 0) e.cooldown -= 1 / 60; }); if (keys['ArrowUp'] || keys['w']) movePlayer(0, -1); if (keys['ArrowDown'] || keys['s']) movePlayer(0, 1); if (keys['ArrowLeft'] || keys['a']) movePlayer(-1, 0); if (keys['ArrowRight'] || keys['d']) movePlayer(1, 0); if (keys[' '] && player.cooldown <= 0) { shoot(player, true); player.cooldown = 2; } enemies.forEach(enemy => { if (enemy.cooldown <= 0 && Math.random() < 0.02) { shoot(enemy, false); enemy.cooldown = 2; } moveEnemy(enemy); }); bullets.forEach((bullet, i) => { bullet.x += bullet.dx; bullet.y += bullet.dy; if (bullet.x < 0 || bullet.x >= gridSize || bullet.y < 0 || bullet.y >= gridSize) { bullets.splice(i, 1); return; } const tile = terrain[Math.floor(bullet.y)][Math.floor(bullet.x)]; if (tile.type === 'breakable') { tile.hp--; if (tile.hp <= 0) terrain[Math.floor(bullet.y)][Math.floor(bullet.x)] = { type: 'ground', ...terrainTypes.ground }; bullets.splice(i, 1); return; } if (tile.type === 'solid') { bullets.splice(i, 1); return; } if (bullet.isPlayer) { enemies.forEach((enemy, j) => { if (Math.floor(bullet.x) === enemy.x && Math.floor(bullet.y) === enemy.y) { enemies.splice(j, 1); bullets.splice(i, 1); score += 100; if (bullet.special) { const explosionRadius = 2; for (let dx = -explosionRadius; dx <= explosionRadius; dx++) { for (let dy = -explosionRadius; dy <= explosionRadius; dy++) { const ex = enemy.x + dx; const ey = enemy.y + dy; if (ex >= 0 && ex < gridSize && ey >= 0 && ey < gridSize && terrain[ey][ex].type === 'breakable') { terrain[ey][ex] = { type: 'ground', ...terrainTypes.ground }; } } } } } }); } else { if (Math.floor(bullet.x) === player.x && Math.floor(bullet.y) === player.y) { player.lives--; bullets.splice(i, 1); if (player.lives <= 0) { gameState = 'gameover'; if (score > highScore) { highScore = score; localStorage.setItem('tankHighScore', highScore); } } } } }); if (enemies.length === 0) { gameState = 'victory'; score += Math.max(0, 1000 - Math.floor((Date.now() - startTime) / 1000) * 10); if (score > highScore) { highScore = score; localStorage.setItem('tankHighScore', highScore); } } } function movePlayer(dx, dy) { const nx = player.x + dx; const ny = player.y + dy; if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize) { const tile = terrain[ny][nx]; if (tile.passable) { player.x = nx; player.y = ny; player.dir = dx !== 0 ? (dx > 0 ? 'right' : 'left') : (dy > 0 ? 'down' : 'up'); if (tile.type === 'mud') { player.x += dx * 0.5; player.y += dy * 0.5; } } } } function moveEnemy(enemy) { const dirs = ['up', 'down', 'left', 'right']; let moved = false; for (let i = 0; i < 4; i++) { const dir = dirs[Math.floor(Math.random() * 4)]; const dx = dir === 'left' ? -1 : dir === 'right' ? 1 : 0; const dy = dir === 'up' ? -1 : dir === 'down' ? 1 : 0; const nx = enemy.x + dx; const ny = enemy.y + dy; if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize && terrain[ny][nx].passable) { enemy.x = nx; enemy.y = ny; enemy.dir = dir; moved = true; break; } } if (enemy.behavior === 'aggressive') { const dx = player.x - enemy.x; const dy = player.y - enemy.y; if (Math.abs(dx) > Math.abs(dy)) { const nx = enemy.x + Math.sign(dx); if (terrain[enemy.y][nx].passable) enemy.x = nx; } else { const ny = enemy.y + Math.sign(dy); if (terrain[ny][enemy.x].passable) enemy.y = ny; } } } function shoot(shooter, isPlayer) { const dirs = { up: [0, -1], down: [0, 1], left: [-1, 0], right: [1, 0] }; const [dx, dy] = dirs[shooter.dir]; bullets.push({ x: shooter.x + dx, y: shooter.y + dy, dx, dy, isPlayer, special: shooter.special }); } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); if (gameState === 'menu') { drawMenu(); } else if (gameState === 'gameover') { drawGameOver(); } else if (gameState === 'victory') { drawVictory(); } else { drawTerrain(); drawBullets(); enemies.forEach(e => moveTank(e.x, e.y, e.dir, e.color, false)); drawTank(player.x, player.y, player.dir, player.color, true); drawUI(); } } function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); } document.addEventListener('keydown', e => { keys[e.key] = true; if (e.key === 'Enter') { if (gameState === 'menu') { resetGame(); } else if (gameState === 'gameover' || gameState === 'victory') { resetGame(); } } if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); } }); document.addEventListener('keyup', e => { keys[e.key] = false; }); canvas.addEventListener('touchstart', e => { e.preventDefault(); const touch = e.touches[0]; selectedTouch = { x: touch.clientX, y: touch.clientY }; if (gameState === 'menu') { resetGame(); } }); canvas.addEventListener('touchmove', e => { e.preventDefault(); const touch = e.touches[0]; const dx = touch.clientX - selectedTouch.x; const dy = touch.clientY - selectedTouch.y; if (Math.abs(dx) > Math.abs(dy)) { if (dx > 10) movePlayer(1, 0); else if (dx < -10) movePlayer(-1, 0); } else { if (dy > 10) movePlayer(0, 1); else if (dy < -10) movePlayer(0, -1); } selectedTouch = { x: touch.clientX, y: touch.clientY }; }); canvas.addEventListener('touchend', e => { e.preventDefault(); if (player.cooldown <= 0) { shoot(player, true); player.cooldown = 2; } }); gameState = 'menu'; gameLoop();

Iniciá sesión para ver el prompt completo

Continuar con:

Al iniciar sesión, aceptás nuestros Términos de uso y Política de privacidad

Uso

Este prompt está diseñado para usarse con coding. Copiá el contenido de arriba y pegalo en tu herramienta de IA preferida.

Para mejores resultados, personalizá los marcadores (indicados con corchetes o mayúsculas) con tus requisitos específicos.

Referencias

Categorías:coding| twitter| javascript| canvas

Discusión