像素风机甲对战小游戏HTML
先放效果图
🎮 游戏玩法设计
功能说明:
双人对战:两个玩家在同一键盘上对战
移动系统:左右移动 + 跳跃(带重力物理)
攻击系统: 近战攻击,有冷却时间和范围判定
防御系统:开启护盾减少50%伤害
胜负判定:血量先归零的一方失败
操作说明
玩家1(绿色):WASD移动,F攻击,G防御
玩家2(红色):方向键移动,L攻击,K防御
🎨 像素风格美术方案
游戏采用程序化像素渲染,无需外部素材:
元素和实现方式 :
机甲角色:使用Canvas矩形拼接成像素风格机体
动画系统:4帧循环动画(待机/行走/攻击/防御/受伤)
场景背景: 渐变夜空 + 像素星星 + 远景城市剪影
特效:粒子爆炸效果 + 浮动伤害数字
UI:复古像素风格血条 + 操作提示面板
🏗️ 核心代码结构
Mecha Class(机甲类)
├── 物理属性(位置、速度、重力)
├── 战斗属性(血量、攻击力、防御状态)
├── 动画系统(状态机管理)
├── update() - 更新逻辑
├── drawPixelMecha() - 像素渲染
└── attack/defend/move - 行为方法
Particle Class(粒子特效)
FloatingText Class(浮动文字)
游戏主循环
├── 输入处理
├── 物理更新
├── 碰撞检测
├── 胜负判定
└── 渲染绘制
代码部分:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>像素风机甲对战</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #1a1a2e; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; font-family: 'Courier New', monospace; color: #fff; } h1 { margin-bottom: 10px; text-shadow: 2px 2px 0 #e94560; font-size: 24px; } #gameCanvas { border: 4px solid #4a4a6a; box-shadow: 0 0 20px rgba(233, 69, 96, 0.5); image-rendering: pixelated; image-rendering: crisp-edges; } .controls { margin-top: 15px; display: flex; gap: 40px; background: #2a2a4a; padding: 15px 30px; border-radius: 10px; } .player-controls { text-align: center; } .player-controls h3 { margin-bottom: 8px; font-size: 14px; } .player1 h3 { color: #4ecca3; } .player2 h3 { color: #e94560; } .keys { display: flex; flex-wrap: wrap; justify-content: center; gap: 5px; max-width: 150px; } .key { background: #3a3a5a; border: 2px solid #5a5a7a; padding: 5px 10px; border-radius: 5px; font-size: 12px; min-width: 40px; } .instructions { margin-top: 10px; font-size: 12px; color: #888; } #gameOver { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); padding: 30px 50px; border-radius: 15px; text-align: center; display: none; border: 3px solid #e94560; } #gameOver h2 { font-size: 32px; margin-bottom: 15px; text-shadow: 2px 2px 0 #e94560; } #restartBtn { margin-top: 15px; padding: 10px 30px; font-size: 16px; background: #e94560; color: white; border: none; border-radius: 5px; cursor: pointer; font-family: 'Courier New', monospace; } #restartBtn:hover { background: #ff6b6b; } </style> </head> <body> <h1>⚔️ 像素风机甲对战 ⚔️</h1> <canvas id="gameCanvas" width="800" height="450"></canvas> <div class="controls"> <div class="player-controls player1"> <h3>🟢 玩家1 (左侧)</h3> <div class="keys"> <span class="key">W</span> <span class="key">A</span> <span class="key">S</span> <span class="key">D</span> <span class="key">F</span> <span class="key">G</span> </div> <div class="instructions">移动: WASD | 攻击: F | 防御: G</div> </div> <div class="player-controls player2"> <h3>🔴 玩家2 (右侧)</h3> <div class="keys"> <span class="key">↑</span> <span class="key">←</span> <span class="key">↓</span> <span class="key">→</span> <span class="key">L</span> <span class="key">K</span> </div> <div class="instructions">移动: 方向键 | 攻击: L | 防御: K</div> </div> </div> <div id="gameOver"> <h2 id="winnerText"></h2> <p>按下方按钮重新开始</p> <button id="restartBtn">重新开始</button> </div> <script> // 游戏画布和上下文 const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; // 游戏常量 const GRAVITY = 0.6; const GROUND_Y = 380; const GAME_WIDTH = 800; const GAME_HEIGHT = 450; // 像素艺术风格配置 const PIXEL_SIZE = 4; // 游戏状态 let gameRunning = true; let particles = []; let projectiles = []; // 机甲类 class Mecha { constructor(x, y, color, isPlayer1) { this.x = x; this.y = y; this.width = 48; this.height = 64; this.color = color; this.isPlayer1 = isPlayer1; // 移动属性 this.vx = 0; this.vy = 0; this.speed = 4; this.jumpPower = -12; this.onGround = false; // 战斗属性 this.maxHp = 100; this.hp = 100; this.attackDamage = 15; this.isDefending = false; this.defenseReduction = 0.5; // 动画状态 this.facing = isPlayer1 ? 1 : -1; this.animFrame = 0; this.animTimer = 0; this.state = 'idle'; // idle, walk, attack, defend, hurt // 攻击冷却 this.attackCooldown = 0; this.attackCooldownMax = 30; // 受击闪烁 this.hitFlash = 0; } update() { // 应用重力 this.vy += GRAVITY; // 更新位置 this.x += this.vx; this.y += this.vy; // 地面碰撞 if (this.y + this.height >= GROUND_Y) { this.y = GROUND_Y - this.height; this.vy = 0; this.onGround = true; } else { this.onGround = false; } // 边界限制 if (this.x < 0) this.x = 0; if (this.x + this.width > GAME_WIDTH) this.x = GAME_WIDTH - this.width; // 更新动画 this.animTimer++; if (this.animTimer >= 8) { this.animTimer = 0; this.animFrame = (this.animFrame + 1) % 4; } // 更新状态 if (this.attackCooldown > 0) this.attackCooldown--; if (this.hitFlash > 0) this.hitFlash--; // 自动恢复防御状态 if (this.state === 'defend' && !this.isDefending) { this.state = 'idle'; } // 攻击状态恢复 if (this.state === 'attack' && this.attackCooldown <= 20) { this.state = 'idle'; } // 受伤状态恢复 if (this.state === 'hurt' && this.hitFlash <= 0) { this.state = 'idle'; } } move(dx, dy) { this.vx = dx * this.speed; if (dx !== 0) { this.facing = dx > 0 ? 1 : -1; if (this.onGround && this.state !== 'attack' && this.state !== 'defend') { this.state = 'walk'; } } else if (this.onGround && this.state === 'walk') { this.state = 'idle'; } if (dy < 0 && this.onGround) { this.vy = this.jumpPower; this.onGround = false; createParticles(this.x + this.width/2, this.y + this.height, 5, '#888'); } } attack(target) { if (this.attackCooldown > 0 || this.isDefending) return; this.state = 'attack'; this.attackCooldown = this.attackCooldownMax; // 检测攻击命中 const attackRange = 80; const distance = Math.abs((this.x + this.width/2) - (target.x + target.width/2)); if (distance < attackRange && Math.abs(this.y - target.y) < 40) { // 创建攻击特效 const hitX = target.x + target.width/2; const hitY = target.y + target.height/2; createParticles(hitX, hitY, 10, '#ff6b6b'); // 计算伤害 let damage = this.attackDamage; if (target.isDefending) { damage *= (1 - target.defenseReduction); createFloatingText(hitX, hitY - 20, 'BLOCK!', '#4ecca3'); } else { target.state = 'hurt'; target.hitFlash = 10; createFloatingText(hitX, hitY - 20, Math.floor(damage), '#ff6b6b'); } target.hp = Math.max(0, target.hp - damage); // 击退效果 const knockback = this.facing * 5; target.vx = knockback; target.vy = -3; } } defend(active) { this.isDefending = active; if (active) { this.state = 'defend'; this.vx = 0; } else if (this.state === 'defend') { this.state = 'idle'; } } draw() { ctx.save(); // 受击闪烁效果 if (this.hitFlash > 0 && Math.floor(this.hitFlash / 2) % 2 === 0) { ctx.globalAlpha = 0.5; } // 绘制机甲(像素风格) this.drawPixelMecha(); // 绘制防御护盾 if (this.isDefending) { this.drawShield(); } ctx.restore(); // 绘制血条 this.drawHealthBar(); } drawPixelMecha() { const x = Math.floor(this.x); const y = Math.floor(this.y); const w = this.width; const h = this.height; const facing = this.facing; // 身体颜色 const bodyColor = this.color; const darkColor = this.darkenColor(bodyColor, 30); const lightColor = this.lightenColor(bodyColor, 30); // 动画偏移 let bobOffset = 0; if (this.state === 'walk') { bobOffset = Math.sin(this.animFrame * Math.PI / 2) * 3; } else if (this.state === 'attack') { bobOffset = -5; } // 绘制阴影 ctx.fillStyle = 'rgba(0,0,0,0.3)'; ctx.fillRect(x + 8, GROUND_Y - 5, w - 16, 8); // 腿部(像素风格) ctx.fillStyle = darkColor; const legOffset = this.state === 'walk' ? Math.sin(this.animFrame * Math.PI / 2) * 8 : 0; // 左腿 ctx.fillRect(x + 12 + (facing === 1 ? 0 : legOffset), y + h - 20 + bobOffset, 10, 20); // 右腿 ctx.fillRect(x + w - 22 - (facing === 1 ? legOffset : 0), y + h - 20 + bobOffset, 10, 20); // 身体 ctx.fillStyle = bodyColor; ctx.fillRect(x + 8, y + 20 + bobOffset, w - 16, 30); // 身体细节 ctx.fillStyle = lightColor; ctx.fillRect(x + 12, y + 25 + bobOffset, w - 24, 8); ctx.fillRect(x + 12, y + 38 + bobOffset, w - 24, 8); // 驾驶舱/头部 ctx.fillStyle = '#2a2a4a'; ctx.fillRect(x + 16, y + 8 + bobOffset, w - 32, 16); // 眼睛/传感器 ctx.fillStyle = this.state === 'attack' ? '#ff6b6b' : '#4ecca3'; const eyeX = facing === 1 ? x + w - 24 : x + 16; ctx.fillRect(eyeX, y + 12 + bobOffset, 8, 6); // 手臂 ctx.fillStyle = darkColor; const armOffset = this.state === 'attack' ? facing * 15 : 0; // 左臂 ctx.fillRect(x - 4, y + 22 + bobOffset, 12, 20); // 右臂(攻击时有动作) ctx.fillRect(x + w - 8 + armOffset, y + 22 + bobOffset, 12, 20); // 武器(右臂) ctx.fillStyle = '#888'; const weaponX = x + w - 6 + armOffset; ctx.fillRect(weaponX, y + 30 + bobOffset, 8, 25); // 武器发光效果 ctx.fillStyle = this.state === 'attack' ? '#ff6b6b' : '#aaa'; ctx.fillRect(weaponX + 2, y + 32 + bobOffset, 4, 15); // 肩部装甲 ctx.fillStyle = lightColor; ctx.fillRect(x, y + 18 + bobOffset, 12, 12); ctx.fillRect(x + w - 12, y + 18 + bobOffset, 12, 12); } drawShield() { const x = this.x - 10; const y = this.y - 5; const w = this.width + 20; const h = this.height + 10; // 护盾发光效果 ctx.strokeStyle = `rgba(78, 204, 163, ${0.5 + Math.sin(Date.now() / 200) * 0.3})`; ctx.lineWidth = 3; ctx.beginPath(); ctx.roundRect(x, y, w, h, 10); ctx.stroke(); // 护盾内部 ctx.fillStyle = 'rgba(78, 204, 163, 0.15)'; ctx.fill(); // 护盾网格 ctx.strokeStyle = 'rgba(78, 204, 163, 0.3)'; ctx.lineWidth = 1; for (let i = 10; i < w; i += 15) { ctx.beginPath(); ctx.moveTo(x + i, y); ctx.lineTo(x + i, y + h); ctx.stroke(); } } drawHealthBar() { const barWidth = 60; const barHeight = 8; const x = this.x + (this.width - barWidth) / 2; const y = this.y - 15; // 背景 ctx.fillStyle = '#333'; ctx.fillRect(x, y, barWidth, barHeight); // 血量 const hpPercent = this.hp / this.maxHp; const hpColor = hpPercent > 0.5 ? '#4ecca3' : hpPercent > 0.25 ? '#ffa500' : '#e94560'; ctx.fillStyle = hpColor; ctx.fillRect(x, y, barWidth * hpPercent, barHeight); // 边框 ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.strokeRect(x, y, barWidth, barHeight); // 血量数字 ctx.fillStyle = '#fff'; ctx.font = '10px Courier New'; ctx.textAlign = 'center'; ctx.fillText(`${Math.ceil(this.hp)}/${this.maxHp}`, this.x + this.width/2, y - 3); } darkenColor(color, percent) { const num = parseInt(color.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = Math.max((num >> 16) - amt, 0); const G = Math.max((num >> 8 & 0x00FF) - amt, 0); const B = Math.max((num & 0x0000FF) - amt, 0); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); } lightenColor(color, percent) { const num = parseInt(color.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = Math.min((num >> 16) + amt, 255); const G = Math.min((num >> 8 & 0x00FF) + amt, 255); const B = Math.min((num & 0x0000FF) + amt, 255); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); } } // 粒子类 class Particle { constructor(x, y, color) { this.x = x; this.y = y; this.vx = (Math.random() - 0.5) * 8; this.vy = (Math.random() - 0.5) * 8; this.life = 30; this.color = color; this.size = Math.random() * 4 + 2; } update() { this.x += this.vx; this.y += this.vy; this.vy += 0.3; this.life--; this.size *= 0.95; } draw() { ctx.fillStyle = this.color; ctx.globalAlpha = this.life / 30; ctx.fillRect(this.x, this.y, this.size, this.size); ctx.globalAlpha = 1; } } // 浮动文字类 class FloatingText { constructor(x, y, text, color) { this.x = x; this.y = y; this.text = text; this.color = color; this.life = 40; this.vy = -1; } update() { this.y += this.vy; this.life--; } draw() { ctx.fillStyle = this.color; ctx.globalAlpha = this.life / 40; ctx.font = 'bold 16px Courier New'; ctx.textAlign = 'center'; ctx.fillText(this.text, this.x, this.y); ctx.globalAlpha = 1; } } // 创建粒子 function createParticles(x, y, count, color) { for (let i = 0; i < count; i++) { particles.push(new Particle(x, y, color)); } } // 创建浮动文字 let floatingTexts = []; function createFloatingText(x, y, text, color) { floatingTexts.push(new FloatingText(x, y, text, color)); } // 绘制背景 function drawBackground() { // 天空渐变 const gradient = ctx.createLinearGradient(0, 0, 0, GAME_HEIGHT); gradient.addColorStop(0, '#1a1a2e'); gradient.addColorStop(0.5, '#2a2a4a'); gradient.addColorStop(1, '#3a3a5a'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); // 像素星星 ctx.fillStyle = '#fff'; for (let i = 0; i < 50; i++) { const x = (i * 137) % GAME_WIDTH; const y = (i * 73) % (GAME_HEIGHT / 2); const size = (i % 3) + 1; ctx.fillRect(x, y, size, size); } // 远景城市轮廓 ctx.fillStyle = '#1a1a3a'; for (let i = 0; i < 20; i++) { const x = i * 45; const height = 50 + (i * 17) % 80; ctx.fillRect(x, GROUND_Y - height, 40, height); } // 地面 ctx.fillStyle = '#2a2a3a'; ctx.fillRect(0, GROUND_Y, GAME_WIDTH, GAME_HEIGHT - GROUND_Y); // 地面像素纹理 ctx.fillStyle = '#3a3a4a'; for (let i = 0; i < GAME_WIDTH; i += 20) { for (let j = GROUND_Y; j < GAME_HEIGHT; j += 15) { if ((i + j) % 40 === 0) { ctx.fillRect(i, j, 12, 8); } } } // 地面边框线 ctx.strokeStyle = '#4ecca3'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(0, GROUND_Y); ctx.lineTo(GAME_WIDTH, GROUND_Y); ctx.stroke(); } // 输入处理 const keys = {}; document.addEventListener('keydown', (e) => { keys[e.key.toLowerCase()] = true; // 玩家1攻击 if (e.key.toLowerCase() === 'f' && gameRunning) { player1.attack(player2); } // 玩家1防御 if (e.key.toLowerCase() === 'g' && gameRunning) { player1.defend(true); } // 玩家2攻击 if (e.key.toLowerCase() === 'l' && gameRunning) { player2.attack(player1); } // 玩家2防御 if (e.key.toLowerCase() === 'k' && gameRunning) { player2.defend(true); } }); document.addEventListener('keyup', (e) => { keys[e.key.toLowerCase()] = false; // 玩家1停止防御 if (e.key.toLowerCase() === 'g') { player1.defend(false); } // 玩家2停止防御 if (e.key.toLowerCase() === 'k') { player2.defend(false); } }); // 处理移动输入 function handleInput() { // 玩家1 (WASD) let p1Dx = 0; let p1Dy = 0; if (keys['a']) p1Dx = -1; if (keys['d']) p1Dx = 1; if (keys['w']) p1Dy = -1; player1.move(p1Dx, p1Dy); // 玩家2 (方向键) let p2Dx = 0; let p2Dy = 0; if (keys['arrowleft']) p2Dx = -1; if (keys['arrowright']) p2Dx = 1; if (keys['arrowup']) p2Dy = -1; player2.move(p2Dx, p2Dy); } // 检查游戏结束 function checkGameOver() { if (player1.hp <= 0 || player2.hp <= 0) { gameRunning = false; const winner = player1.hp > 0 ? '玩家1 获胜!' : '玩家2 获胜!'; const winnerColor = player1.hp > 0 ? '#4ecca3' : '#e94560'; document.getElementById('winnerText').textContent = winner; document.getElementById('winnerText').style.color = winnerColor; document.getElementById('gameOver').style.display = 'block'; } } // 重置游戏 function resetGame() { player1 = new Mecha(150, GROUND_Y - 64, '#4ecca3', true); player2 = new Mecha(GAME_WIDTH - 200, GROUND_Y - 64, '#e94560', false); particles = []; floatingTexts = []; gameRunning = true; document.getElementById('gameOver').style.display = 'none'; } document.getElementById('restartBtn').addEventListener('click', resetGame); // 初始化游戏对象 let player1 = new Mecha(150, GROUND_Y - 64, '#4ecca3', true); let player2 = new Mecha(GAME_WIDTH - 200, GROUND_Y - 64, '#e94560', false); // 游戏主循环 function gameLoop() { // 清空画布 ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); // 绘制背景 drawBackground(); if (gameRunning) { // 处理输入 handleInput(); // 更新玩家 player1.update(); player2.update(); // 检查游戏结束 checkGameOver(); } // 绘制玩家 player1.draw(); player2.draw(); // 更新和绘制粒子 particles = particles.filter(p => p.life > 0); particles.forEach(p => { p.update(); p.draw(); }); // 更新和绘制浮动文字 floatingTexts = floatingTexts.filter(t => t.life > 0); floatingTexts.forEach(t => { t.update(); t.draw(); }); requestAnimationFrame(gameLoop); } // 启动游戏 gameLoop(); </script> </body> </html>