手把手教你用HTML5 Canvas开发简单射击游戏(Shooter)

文章目录

手把手教你用HTML5 Canvas开发简单射击游戏(Shooter)

射击游戏是游戏开发中的经典类型,玩家通过控制角色发射子弹击中敌人得分,同时避免敌人到达底部。本文将带你从零开始,使用HTML5 Canvas和原生JavaScript实现一款简单的射击游戏,掌握多物体管理、碰撞检测等核心游戏开发技术。
在这里插入图片描述

一、游戏核心原理与准备

在开始编码前,我们先明确这款射击游戏的核心机制:

  • 玩家(Player):位于屏幕底部,可左右移动
  • 子弹(Bullets):从玩家位置向上发射,击中敌人后消失
  • 敌人(Enemies):从屏幕顶部随机位置出现,向下移动
  • 碰撞检测:判断子弹是否击中敌人
  • 计分系统:击中敌人得分,敌人到达底部则游戏结束

准备工作:创建一个HTML文件(命名为shooter-game.html),我们将在这个文件中完成所有开发。

二、步骤1:搭建基础HTML结构

首先创建游戏的基本页面框架,包含Canvas元素和必要的样式:

<!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>
        body {
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #1a1a2e;
            font-family: Arial, sans-serif;
            color: #fff;
        }
        #gameCanvas {
            border: 2px solid #4CAF50;
            background-color: #16213e;
        }
        .game-stats {
            margin-bottom: 15px;
            font-size: 1.2em;
        }
    </style>
</head>
<body>
    <h1>太空射击游戏</h1>
    <div class="game-stats">分数: <span id="score">0</span></div>
    <!-- 游戏画布 -->
    <canvas id="gameCanvas" width="600" height="500"></canvas>
    <div class="game-stats">使用左右方向键移动,点击鼠标发射子弹</div>

    <script>
        // 游戏代码将写在这里
    </script>
</body>
</html>

代码说明

  • 创建了一个600x500像素的Canvas画布,作为游戏的主舞台
  • 使用深色背景和绿色边框,营造太空射击游戏的氛围
  • 添加了分数显示区域和操作说明,提升用户体验
  • 预留<script>标签用于编写游戏逻辑

三、步骤2:初始化游戏对象与参数

接下来定义游戏中所有元素的属性,包括玩家、子弹、敌人等核心对象。在<script>标签中添加:

// 获取Canvas元素和绘图上下文
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');

// 玩家属性
const player = {
    x: canvas.width / 2 - 25, // 初始X坐标(居中)
    y: canvas.height - 60,    // 初始Y坐标(底部上方)
    width: 50,                // 宽度
    height: 40,               // 高度
    speed: 5,                 // 移动速度
    dx: 0                     // 水平移动方向(0=静止)
};

// 子弹管理
const bullets = [];          // 存储所有子弹的数组
const bullet = {
    width: 4,                // 宽度
    height: 10,              // 高度
    speed: 7,                // 移动速度
    color: '#ff2e63'         // 颜色(红色)
};

// 敌人管理
const enemies = [];          // 存储所有敌人的数组
const enemy = {
    width: 40,               // 宽度
    height: 40,              // 高度
    minSpeed: 2,             // 最小速度
    maxSpeed: 4,             // 最大速度
    color: '#08d9d6'         // 颜色(青色)
};

// 游戏状态
let score = 0;
let gameOver = false;

代码说明

  • player对象:定义玩家的位置、大小和移动属性,初始位置居中底部
  • 子弹系统:使用数组bullets存储所有活跃子弹,bullet对象定义子弹的通用属性
  • 敌人系统:使用数组enemies存储所有活跃敌人,enemy对象定义敌人的通用属性(包含随机速度范围)
  • 游戏状态变量:记录当前分数和游戏是否结束

四、步骤3:绘制游戏元素

编写函数绘制玩家、子弹、敌人和游戏状态(分数、游戏结束画面):

// 绘制玩家
function drawPlayer() {
    // 玩家主体(三角形)
    ctx.fillStyle = '#eaeaea';
    ctx.beginPath();
    ctx.moveTo(player.x + player.width / 2, player.y); // 顶部中点
    ctx.lineTo(player.x, player.y + player.height);    // 左下
    ctx.lineTo(player.x + player.width, player.y + player.height); // 右下
    ctx.closePath();
    ctx.fill();
    
    // 玩家中心炮管
    ctx.fillStyle = '#ff2e63';
    ctx.fillRect(
        player.x + player.width / 2 - 2, 
        player.y - 10, 
        4, 
        15
    );
}

// 绘制子弹
function drawBullets() {
    bullets.forEach(bullet => {
        ctx.fillStyle = bullet.color;
        ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
        
        // 子弹尾部特效
        ctx.fillStyle = 'rgba(255, 46, 99, 0.5)';
        ctx.fillRect(bullet.x, bullet.y + bullet.height, bullet.width, bullet.height / 2);
    });
}

// 绘制敌人
function drawEnemies() {
    enemies.forEach(enemy => {
        // 敌人主体(六边形)
        ctx.fillStyle = enemy.color;
        ctx.beginPath();
        const centerX = enemy.x + enemy.width / 2;
        const centerY = enemy.y + enemy.height / 2;
        const radius = enemy.width / 2;
        const sides = 6;
        
        for (let i = 0; i < sides; i++) {
            const angle = (i * 2 * Math.PI / sides) - Math.PI / 2;
            const x = centerX + radius * Math.cos(angle);
            const y = centerY + radius * Math.sin(angle);
            if (i === 0) {
                ctx.moveTo(x, y);
            } else {
                ctx.lineTo(x, y);
            }
        }
        ctx.closePath();
        ctx.fill();
        
        // 敌人中心
        ctx.fillStyle = '#252a34';
        ctx.beginPath();
        ctx.arc(centerX, centerY, radius / 3, 0, Math.PI * 2);
        ctx.fill();
    });
}

// 绘制游戏结束画面
function drawGameOver() {
    // 半透明背景
    ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    // 游戏结束文字
    ctx.fillStyle = '#ff2e63';
    ctx.font = '40px Arial';
    ctx.textAlign = 'center';
    ctx.fillText('游戏结束', canvas.width / 2, canvas.height / 2 - 40);
    
    // 显示分数
    ctx.font = '24px Arial';
    ctx.fillText(`最终分数: ${score}`, canvas.width / 2, canvas.height / 2);
    
    // 重启提示
    ctx.font = '18px Arial';
    ctx.fillStyle = '#eaeaea';
    ctx.fillText('按F5重新开始', canvas.width / 2, canvas.height / 2 + 40);
}

代码说明

  • drawPlayer():使用三角形绘制玩家飞船,添加红色炮管增强视觉效果
  • drawBullets():绘制子弹并添加尾部半透明特效,增强动感
  • drawEnemies():绘制六边形敌人,添加中心深色圆点,使敌人更具辨识度
  • 游戏结束画面:使用半透明背景突出文字,显示最终分数和重启提示

五、步骤4:处理用户输入(控制玩家与发射子弹)

实现通过键盘控制玩家移动和鼠标点击发射子弹的功能:

// 键盘控制玩家移动
document.addEventListener('keydown', keyDown);
document.addEventListener('keyup', keyUp);

// 键盘按下事件
function keyDown(e) {
    if (gameOver) return; // 游戏结束后不响应输入
    
    if (e.key === 'ArrowLeft' || e.key === 'Left') {
        player.dx = -player.speed; // 左移
    } else if (e.key === 'ArrowRight' || e.key === 'Right') {
        player.dx = player.speed;  // 右移
    }
}

// 键盘释放事件
function keyUp(e) {
    if (e.key === 'ArrowLeft' || e.key === 'Left' || 
        e.key === 'ArrowRight' || e.key === 'Right') {
        player.dx = 0; // 停止移动
    }
}

// 鼠标点击发射子弹
canvas.addEventListener('click', shootBullet);

// 空格键发射子弹
document.addEventListener('keydown', (e) => {
    if (e.code === 'Space' && !gameOver) {
        shootBullet();
    }
});

// 发射子弹函数
function shootBullet() {
    // 在玩家中心位置创建子弹
    bullets.push({
        x: player.x + player.width / 2 - bullet.width / 2,
        y: player.y - bullet.height,
        width: bullet.width,
        height: bullet.height,
        speed: bullet.speed,
        color: bullet.color
    });
}

代码说明

  • 玩家移动:通过keydownkeyup事件控制player.dx(水平移动方向),按下方向键时移动,释放时停止
  • 子弹发射:支持两种方式
    1. 点击画布任意位置
    2. 按下空格键
  • 子弹生成:在玩家中心位置创建新子弹,并添加到bullets数组中
  • 游戏结束后:所有输入操作失效,直到玩家重启游戏

六、步骤5:实现敌人生成与移动逻辑

编写代码控制敌人的定时生成、移动和超出屏幕的处理:

// 生成敌人
function spawnEnemy() {
    if (gameOver) return; // 游戏结束后停止生成
    
    // 随机X坐标(确保敌人完全在画布内)
    const minX = enemy.width / 2;
    const maxX = canvas.width - enemy.width / 2;
    const x = minX + Math.random() * (maxX - minX);
    
    // 随机速度(在minSpeed和maxSpeed之间)
    const speed = enemy.minSpeed + Math.random() * (enemy.maxSpeed - enemy.minSpeed);
    
    // 添加新敌人到数组
    enemies.push({
        x: x - enemy.width / 2, // 调整到左上角坐标
        y: -enemy.height,       // 从屏幕顶部外进入
        width: enemy.width,
        height: enemy.height,
        speed: speed,
        color: enemy.color
    });
    
    // 定时生成新敌人(1.5秒一次)
    setTimeout(spawnEnemy, 1500);
}

// 更新敌人位置
function updateEnemies() {
    for (let i = enemies.length - 1; i >= 0; i--) {
        const enemy = enemies[i];
        enemy.y += enemy.speed; // 敌人向下移动
        
        // 检查敌人是否到达底部(游戏结束条件)
        if (enemy.y + enemy.height > canvas.height) {
            gameOver = true;
            break;
        }
        
        // 移除超出屏幕的敌人(虽然已触发游戏结束,但仍需清理)
        if (enemy.y > canvas.height) {
            enemies.splice(i, 1);
        }
    }
}

代码说明

  • spawnEnemy():定时生成新敌人,随机位置(水平方向)和速度,确保敌人从屏幕顶部外进入
  • 敌人移动:每帧向下移动,速度为随机生成的值(在指定范围内)
  • 游戏结束条件:当任何敌人的底部超过画布底部时,设置gameOvertrue
  • 内存管理:及时移除完全超出屏幕的敌人,优化性能

七、步骤6:实现碰撞检测与子弹管理

编写代码检测子弹与敌人的碰撞,并处理子弹的移动和超出屏幕的清理:

// 更新子弹位置并检测碰撞
function updateBullets() {
    // 移动子弹并移除超出屏幕的子弹
    for (let i = bullets.length - 1; i >= 0; i--) {
        const bullet = bullets[i];
        bullet.y -= bullet.speed; // 子弹向上移动
        
        // 移除超出屏幕顶部的子弹
        if (bullet.y + bullet.height < 0) {
            bullets.splice(i, 1);
            continue;
        }
        
        // 检测子弹是否击中敌人
        checkBulletEnemyCollision(bullet, i);
    }
}

// 检测子弹与敌人的碰撞
function checkBulletEnemyCollision(bullet, bulletIndex) {
    for (let j = enemies.length - 1; j >= 0; j--) {
        const enemy = enemies[j];
        
        // 检测矩形碰撞(子弹与敌人是否重叠)
        if (
            bullet.x < enemy.x + enemy.width &&
            bullet.x + bullet.width > enemy.x &&
            bullet.y < enemy.y + enemy.height &&
            bullet.y + bullet.height > enemy.y
        ) {
            // 击中:移除子弹和敌人,加分
            bullets.splice(bulletIndex, 1);
            enemies.splice(j, 1);
            score += 10;
            scoreElement.textContent = score;
            break; // 一个子弹只能击中一个敌人
        }
    }
}

代码说明

  • 子弹移动:每帧向上移动,超出屏幕顶部后从数组中移除
  • 碰撞检测:使用矩形碰撞算法,检查子弹和敌人的边界是否重叠
  • 击中处理:
    1. 从数组中移除被击中的敌人和子弹
    2. 分数增加10分
    3. 更新页面上的分数显示
  • 优化处理:使用反向循环(从后往前)处理数组元素的删除,避免索引错误

八、步骤7:实现游戏主循环与玩家移动限制

游戏主循环负责更新所有游戏元素的状态并重新绘制,同时限制玩家移动范围:

// 更新玩家位置(限制在画布内)
function updatePlayer() {
    player.x += player.dx;
    
    // 限制玩家在画布左侧边界内
    if (player.x < 0) {
        player.x = 0;
    }
    // 限制玩家在画布右侧边界内
    if (player.x + player.width > canvas.width) {
        player.x = canvas.width - player.width;
    }
}

// 游戏主循环
function gameLoop() {
    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    if (!gameOver) {
        // 更新游戏元素
        updatePlayer();
        updateBullets();
        updateEnemies();
        
        // 绘制游戏元素
        drawPlayer();
        drawBullets();
        drawEnemies();
    } else {
        // 绘制游戏结束画面
        drawGameOver();
    }
    
    // 继续游戏循环
    requestAnimationFrame(gameLoop);
}

// 启动游戏
spawnEnemy(); // 开始生成敌人
gameLoop();   // 启动主循环

代码说明

  • updatePlayer():更新玩家位置并限制在画布范围内,避免玩家移出屏幕
  • 游戏主循环流程:
    1. 清空画布 → 2. 更新所有元素状态(玩家、子弹、敌人) → 3. 绘制所有元素
    2. 游戏状态判断:如果gameOvertrue,则绘制游戏结束画面
    3. requestAnimationFrame:浏览器优化的动画API,提供平滑的动画效果
  • 游戏启动:先调用spawnEnemy()开始生成敌人,再启动主循环

九、完整代码与运行效果

将以上所有代码整合后,保存并在浏览器中打开,你将获得一个功能完整的射击游戏:

  • 使用左右方向键控制玩家移动
  • 点击鼠标或按空格键发射子弹
  • 击中敌人得分,敌人到达底部则游戏结束
  • 屏幕实时显示当前分数
  • 游戏结束后可按F5重新开始

十、扩展功能建议(进阶练习)

如果想进一步提升游戏体验,可以尝试添加这些功能:

  1. 多种敌人类型:添加不同大小、速度和生命值的敌人,增加游戏多样性
  2. 道具系统:敌人被击中后有概率掉落道具(如:增加射速、扩大玩家尺寸、护盾等)
  3. 音效反馈:添加发射、击中、爆炸、游戏结束等音效
  4. 关卡系统:随着分数增加,敌人难度逐渐提升(速度更快、数量更多)
  5. 生命值系统:允许玩家承受几次撞击,而不是一次失败
  6. 粒子效果:敌人被击中时添加爆炸粒子效果,增强视觉反馈

通过本教程,你已经掌握了射击游戏的核心开发技术,包括多物体管理(子弹和敌人数组)、碰撞检测、用户输入处理和游戏循环。这些知识是2D游戏开发的基础,掌握后可以尝试开发更复杂的游戏类型。祝你编程愉快!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值