像素风机甲对战小游戏HTML

0 阅读8分钟

  先放效果图

​编辑

🎮 游戏玩法设计

功能说明:
双人对战:两个玩家在同一键盘上对战 
移动系统:左右移动 + 跳跃(带重力物理) 
攻击系统: 近战攻击,有冷却时间和范围判定 
防御系统:开启护盾减少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>