像素风机甲对战小游戏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 langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.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 idgameCanvas width800 height450/canvas div classcontrols div classplayer-controls player1 h3 玩家1 (左侧)/h3 div classkeys span classkeyW/span span classkeyA/span span classkeyS/span span classkeyD/span span classkeyF/span span classkeyG/span /div div classinstructions移动: WASD | 攻击: F | 防御: G/div /div div classplayer-controls player2 h3 玩家2 (右侧)/h3 div classkeys span classkey↑/span span classkey←/span span classkey↓/span span classkey→/span span classkeyL/span span classkeyK/span /div div classinstructions移动: 方向键 | 攻击: L | 防御: K/div /div /div div idgameOver h2 idwinnerText/h2 p按下方按钮重新开始/p button idrestartBtn重新开始/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