文章目录
手把手教你用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
});
}
代码说明:
- 玩家移动:通过
keydown和keyup事件控制player.dx(水平移动方向),按下方向键时移动,释放时停止 - 子弹发射:支持两种方式
- 点击画布任意位置
- 按下空格键
- 子弹生成:在玩家中心位置创建新子弹,并添加到
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():定时生成新敌人,随机位置(水平方向)和速度,确保敌人从屏幕顶部外进入- 敌人移动:每帧向下移动,速度为随机生成的值(在指定范围内)
- 游戏结束条件:当任何敌人的底部超过画布底部时,设置
gameOver为true - 内存管理:及时移除完全超出屏幕的敌人,优化性能
七、步骤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; // 一个子弹只能击中一个敌人
}
}
}
代码说明:
- 子弹移动:每帧向上移动,超出屏幕顶部后从数组中移除
- 碰撞检测:使用矩形碰撞算法,检查子弹和敌人的边界是否重叠
- 击中处理:
- 从数组中移除被击中的敌人和子弹
- 分数增加10分
- 更新页面上的分数显示
- 优化处理:使用反向循环(从后往前)处理数组元素的删除,避免索引错误
八、步骤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():更新玩家位置并限制在画布范围内,避免玩家移出屏幕- 游戏主循环流程:
- 清空画布 → 2. 更新所有元素状态(玩家、子弹、敌人) → 3. 绘制所有元素
- 游戏状态判断:如果
gameOver为true,则绘制游戏结束画面 requestAnimationFrame:浏览器优化的动画API,提供平滑的动画效果
- 游戏启动:先调用
spawnEnemy()开始生成敌人,再启动主循环
九、完整代码与运行效果
将以上所有代码整合后,保存并在浏览器中打开,你将获得一个功能完整的射击游戏:
- 使用左右方向键控制玩家移动
- 点击鼠标或按空格键发射子弹
- 击中敌人得分,敌人到达底部则游戏结束
- 屏幕实时显示当前分数
- 游戏结束后可按F5重新开始
十、扩展功能建议(进阶练习)
如果想进一步提升游戏体验,可以尝试添加这些功能:
- 多种敌人类型:添加不同大小、速度和生命值的敌人,增加游戏多样性
- 道具系统:敌人被击中后有概率掉落道具(如:增加射速、扩大玩家尺寸、护盾等)
- 音效反馈:添加发射、击中、爆炸、游戏结束等音效
- 关卡系统:随着分数增加,敌人难度逐渐提升(速度更快、数量更多)
- 生命值系统:允许玩家承受几次撞击,而不是一次失败
- 粒子效果:敌人被击中时添加爆炸粒子效果,增强视觉反馈
通过本教程,你已经掌握了射击游戏的核心开发技术,包括多物体管理(子弹和敌人数组)、碰撞检测、用户输入处理和游戏循环。这些知识是2D游戏开发的基础,掌握后可以尝试开发更复杂的游戏类型。祝你编程愉快!

被折叠的 条评论
为什么被折叠?



