文章目录
手把手教你用HTML5 Canvas开发Flappy Bird类游戏
Flappy Bird是一款风靡全球的休闲小游戏,玩家通过点击屏幕控制小鸟穿越管道,操作简单却极具挑战性。本文将带你从零开始,使用HTML5 Canvas和原生JavaScript实现这款游戏,掌握2D游戏中的重力模拟、碰撞检测等核心技术。

一、游戏核心原理与准备
在开始编码前,我们先明确Flappy Bird的核心机制:
- 小鸟(Bird):受重力影响持续下落,点击屏幕时向上跳跃
- 管道(Pipes):从右侧持续向左移动,管道中间有间隙供小鸟穿过
- 重力系统:通过加速度模拟重力效果,让小鸟的运动更真实
- 碰撞检测:检测小鸟是否撞到管道、地面或天花板
- 计分系统:小鸟成功穿过管道后得分
准备工作:创建一个HTML文件(命名为flappy-bird.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>Flappy Bird类游戏</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
#gameCanvas {
border: 2px solid #333;
background-color: #4EC0CA; /* 天空蓝色背景 */
}
.game-info {
margin: 10px 0;
font-size: 1.2em;
color: #333;
}
</style>
</head>
<body>
<h1>Flappy Bird</h1>
<div class="game-info">点击屏幕或按空格键让小鸟飞起来</div>
<!-- 游戏画布 -->
<canvas id="gameCanvas" width="400" height="600"></canvas>
<script>
// 游戏代码将写在这里
</script>
</body>
</html>
代码说明:
- 创建了一个400x600像素的Canvas画布,高度大于宽度符合竖屏游戏体验
- 背景色设为天空蓝,营造游戏氛围
- 添加了简单的说明文字,指导玩家操作
- 预留
<script>标签用于编写游戏逻辑
三、步骤2:初始化游戏对象与参数
接下来定义游戏中所有元素的属性,包括小鸟、管道、地面等核心对象。在<script>标签中添加:
// 获取Canvas元素和绘图上下文
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// 小鸟属性
const bird = {
x: 100, // 初始X坐标
y: 200, // 初始Y坐标
width: 34, // 宽度
height: 24, // 高度
velocity: 0, // 垂直速度
gravity: 0.5, // 重力加速度(控制下落速度)
jumpStrength: -10 // 跳跃力度(负值表示向上)
};
// 管道属性
const pipe = {
width: 70, // 管道宽度
gap: 150, // 管道中间的间隙大小
speed: 2, // 管道移动速度
frequency: 150 // 生成新管道的间隔(帧)
};
// 存储所有管道的数组
let pipes = [];
// 地面属性
const ground = {
y: canvas.height - 50, // 地面Y坐标
height: 50 // 地面高度
};
// 游戏状态
let score = 0;
let frameCount = 0; // 帧计数器,用于控制管道生成
let gameOver = false;
代码说明:
bird对象:包含小鸟的位置、大小和运动属性,gravity模拟重力,jumpStrength控制跳跃力度pipe对象:定义管道的尺寸、间隙和移动速度,frequency控制管道生成频率pipes数组:存储所有当前在屏幕上的管道ground对象:定义地面位置和高度,作为碰撞检测的边界- 游戏状态变量:记录分数、帧计数和游戏是否结束
四、步骤3:绘制游戏元素
编写函数绘制小鸟、管道、地面和游戏信息(分数、游戏结束画面):
// 绘制小鸟
function drawBird() {
ctx.fillStyle = '#FFD700'; // 金黄色小鸟
// 绘制小鸟身体(椭圆)
ctx.beginPath();
ctx.ellipse(
bird.x + bird.width/2,
bird.y + bird.height/2,
bird.width/2,
bird.height/2,
0, 0, Math.PI * 2
);
ctx.fill();
// 绘制眼睛
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(bird.x + bird.width * 0.7, bird.y + bird.height/3, 3, 0, Math.PI * 2);
ctx.fill();
// 绘制嘴巴
ctx.fillStyle = '#FF6B35';
ctx.beginPath();
ctx.moveTo(bird.x + bird.width, bird.y + bird.height/2);
ctx.lineTo(bird.x + bird.width + 6, bird.y + bird.height/2 - 2);
ctx.lineTo(bird.x + bird.width, bird.y + bird.height/2 - 4);
ctx.fill();
}
// 绘制管道
function drawPipes() {
pipes.forEach(pipe => {
// 上管道(倒置)
ctx.fillStyle = '#78AB46';
ctx.fillRect(pipe.x, 0, pipe.width, pipe.topHeight);
// 上管道顶部装饰
ctx.fillRect(pipe.x - 5, pipe.topHeight - 10, pipe.width + 10, 10);
// 下管道
ctx.fillRect(pipe.x, pipe.bottomY, pipe.width, canvas.height - pipe.bottomY);
// 下管道顶部装饰
ctx.fillRect(pipe.x - 5, pipe.bottomY, pipe.width + 10, 10);
});
}
// 绘制地面
function drawGround() {
ctx.fillStyle = '#78AB46'; // 绿色地面
ctx.fillRect(0, ground.y, canvas.width, ground.height);
// 绘制地面纹理(简单的线条)
ctx.fillStyle = '#5A8C35';
for (let i = 0; i < canvas.width; i += 40) {
ctx.fillRect(i, ground.y + 10, 20, 5);
}
}
// 绘制分数
function drawScore() {
ctx.fillStyle = '#FFF';
ctx.font = '30px Arial';
ctx.textAlign = 'center';
ctx.fillText(score, canvas.width / 2, 50);
}
// 绘制游戏结束画面
function drawGameOver() {
// 半透明背景
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 游戏结束文字
ctx.fillStyle = '#FFF';
ctx.font = '30px Arial';
ctx.textAlign = 'center';
ctx.fillText('游戏结束', canvas.width / 2, canvas.height / 2 - 30);
// 显示分数
ctx.font = '24px Arial';
ctx.fillText(`得分: ${score}`, canvas.width / 2, canvas.height / 2 + 10);
// 重启提示
ctx.font = '18px Arial';
ctx.fillText('点击屏幕重启', canvas.width / 2, canvas.height / 2 + 50);
}
代码说明:
drawBird():使用椭圆和基本图形绘制小鸟,添加眼睛和嘴巴增强视觉效果drawPipes():绘制上下管道及顶部装饰,使管道更美观drawGround():绘制绿色地面并添加简单纹理,增强游戏视觉效果- 信息绘制:在屏幕顶部显示当前分数,游戏结束时显示结果和重启提示
五、步骤4:处理用户输入(控制小鸟跳跃)
实现通过点击屏幕或按空格键让小鸟跳跃的功能:
// 处理跳跃输入(点击屏幕)
canvas.addEventListener('click', handleJump);
// 处理跳跃输入(空格键)
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
handleJump();
}
});
// 跳跃处理函数
function handleJump() {
if (gameOver) {
// 游戏结束时点击重启
resetGame();
return;
}
// 应用跳跃力(向上的速度)
bird.velocity = bird.jumpStrength;
}
代码说明:
- 监听
click事件:点击画布任意位置触发小鸟跳跃 - 监听
keydown事件:按下空格键也能触发跳跃,增加操作方式 - 游戏结束时点击屏幕会触发重置游戏函数
- 跳跃原理:给小鸟一个向上的初始速度(负值),对抗重力
六、步骤5:实现管道生成与移动逻辑
编写代码控制管道的生成、移动和移除:
// 生成新管道
function spawnPipe() {
// 计算管道间隙的随机位置(限制在合理范围内)
const minGapTop = 50; // 最小顶部管道高度
const maxGapTop = canvas.height - ground.height - pipe.gap - minGapTop; // 最大顶部管道高度
const topHeight = minGapTop + Math.random() * maxGapTop;
// 添加新管道到数组
pipes.push({
x: canvas.width, // 从右侧屏幕外进入
topHeight: topHeight, // 上管道高度
bottomY: topHeight + pipe.gap // 下管道起始Y坐标
});
}
// 更新管道位置
function updatePipes() {
// 控制管道生成频率(每N帧生成一个)
frameCount++;
if (frameCount % pipe.frequency === 0) {
spawnPipe();
}
// 移动管道并移除超出屏幕的管道
for (let i = pipes.length - 1; i >= 0; i--) {
pipes[i].x -= pipe.speed;
// 检查小鸟是否穿过管道(计分)
if (!pipes[i].scored && pipes[i].x + pipe.width < bird.x) {
score++;
pipes[i].scored = true; // 标记为已计分,避免重复计分
}
// 移除左侧超出屏幕的管道
if (pipes[i].x + pipe.width < 0) {
pipes.splice(i, 1);
}
}
}
代码说明:
spawnPipe():随机生成管道间隙位置,确保间隙在合理范围内(不会太靠近顶部或底部)- 管道生成逻辑:通过
frameCount控制生成频率,每150帧生成一个新管道 - 管道移动:每帧向左移动一定距离,模拟小鸟向前飞行的效果
- 计分逻辑:当管道完全通过小鸟位置时加分,使用
scored标记避免重复计分 - 内存管理:及时移除超出屏幕的管道,避免数组无限增长
七、步骤6:实现碰撞检测
碰撞检测是游戏的核心逻辑,需要检测小鸟是否撞到管道、地面或天花板:
// 检测碰撞
function checkCollisions() {
// 撞到地面或天花板
if (bird.y + bird.height > ground.y || bird.y < 0) {
gameOver = true;
}
// 撞到管道
for (const p of pipes) {
// 检测水平方向是否重叠
if (bird.x + bird.width > p.x && bird.x < p.x + pipe.width) {
// 检测垂直方向是否撞到上管道或下管道
if (bird.y < p.topHeight || bird.y + bird.height > p.bottomY) {
gameOver = true;
break;
}
}
}
}
代码说明:
- 地面/天花板碰撞:小鸟底部超过地面Y坐标或顶部小于0时判定为碰撞
- 管道碰撞:分两步检测
- 水平方向:小鸟与管道在X轴上是否重叠
- 垂直方向:小鸟是否撞到上管道底部或下管道顶部
- 碰撞后立即设置
gameOver为true,结束当前游戏
八、步骤7:实现游戏主循环与重置功能
游戏主循环负责更新游戏状态和重绘画面,重置功能用于游戏结束后重新开始:
// 重置游戏
function resetGame() {
// 重置小鸟状态
bird.y = 200;
bird.velocity = 0;
// 清空管道
pipes = [];
// 重置游戏状态
score = 0;
frameCount = 0;
gameOver = false;
}
// 游戏主循环
function gameLoop() {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!gameOver) {
// 更新小鸟位置(应用重力)
bird.velocity += bird.gravity;
bird.y += bird.velocity;
// 更新管道
updatePipes();
// 检测碰撞
checkCollisions();
}
// 绘制所有游戏元素
drawPipes();
drawGround();
drawBird();
drawScore();
// 如果游戏结束,绘制结束画面
if (gameOver) {
drawGameOver();
}
// 继续游戏循环
requestAnimationFrame(gameLoop);
}
// 启动游戏
gameLoop();
代码说明:
resetGame():重置小鸟位置、清空管道、重置分数和游戏状态,为新游戏做准备- 游戏主循环流程:
- 清空画布 → 2. 更新游戏状态(小鸟位置、管道) → 3. 检测碰撞 → 4. 绘制所有元素
- 重力模拟:通过
bird.velocity += bird.gravity实现加速度效果,让小鸟下落速度越来越快 requestAnimationFrame:浏览器优化的动画API,自动控制帧率(通常60帧/秒)- 游戏结束处理:绘制结束画面,保持当前状态直到用户点击重启
九、完整代码与运行效果
将以上所有代码整合后,保存并在浏览器中打开,你将获得一个功能完整的Flappy Bird类游戏:
- 点击屏幕或按空格键控制小鸟跳跃
- 小鸟受重力影响自然下落
- 管道从右侧不断出现,中间有间隙供小鸟穿过
- 成功穿过管道得分,撞到管道或地面则游戏结束
- 游戏结束后点击屏幕可重新开始
十、扩展功能建议(进阶练习)
如果想进一步提升游戏体验,可以尝试添加这些功能:
- 难度递增:随着分数增加,管道移动速度逐渐加快,间隙变小
- 音效系统:添加跳跃、得分、碰撞的音效
- 动画效果:给小鸟添加扇动翅膀的动画,管道添加移动特效
- 最高分记录:使用
localStorage保存玩家的最高分数 - 开始界面:添加游戏标题和"开始游戏"按钮
- 粒子效果:碰撞时添加爆炸粒子效果,增强视觉反馈
通过本教程,你已经掌握了Flappy Bird类游戏的核心开发技术,包括重力模拟、物体移动、碰撞检测和游戏循环。这些知识是2D游戏开发的基础,掌握后可以尝试开发更复杂的游戏。祝你编程愉快!

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



