用原生JS手搓一个Flappy Bird小游戏(完整代码+逐行讲解)
最近有学员问我:"学JavaScript到底有什么用?"我的回答是——它能让你亲手创造世界。今天我们就用不到200行代码,还原那个曾经风靡全球的Flappy Bird。不需要任何框架,只用最原生的Canvas API和基础JS语法,你就能理解游戏开发的核心逻辑。
1. 搭建游戏骨架
我们先从最基础的HTML结构开始。创建一个480x270像素的画布,这将是我们的主战场:
<!DOCTYPE html> <html> <head> <title>Flappy Bird JS版</title> <style> body { margin: 0; display: flex; justify-content: center; } canvas { background: skyblue; margin-top: 20px; } </style> </head> <body> <canvas id="game" width="480" height="270"></canvas> <script src="game.js"></script> </body> </html>接下来在game.js中初始化游戏环境:
const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); // 游戏状态控制 let gameRunning = true; let score = 0;2. 创建游戏主角
小鸟对象需要包含位置、速度等属性,以及更新和绘制方法:
class Bird { constructor() { this.x = 50; this.y = canvas.height / 2; this.width = 30; this.height = 30; this.velocity = 0; this.gravity = 0.5; this.lift = -10; } draw() { ctx.fillStyle = 'yellow'; ctx.beginPath(); ctx.arc(this.x, this.y, this.width/2, 0, Math.PI * 2); ctx.fill(); } update() { this.velocity += this.gravity; this.y += this.velocity; // 边界检测 if (this.y > canvas.height - this.height/2) { this.y = canvas.height - this.height/2; this.velocity = 0; gameOver(); } if (this.y < this.height/2) { this.y = this.height/2; this.velocity = 0; } } flap() { this.velocity = this.lift; } }关键点解析:
gravity常量控制下落加速度lift常量决定每次点击的上升力度- 使用
arc()方法绘制圆形小鸟 - 边界检测防止飞出画布
3. 障碍物系统设计
管道需要成对出现(上方和下方),并持续向左移动:
class Pipe { constructor() { this.x = canvas.width; this.width = 60; this.gap = 120; this.topHeight = Math.random() * (canvas.height - this.gap - 100) + 50; this.bottomHeight = canvas.height - this.topHeight - this.gap; this.speed = 2; this.passed = false; } draw() { ctx.fillStyle = 'green'; // 上方管道 ctx.fillRect(this.x, 0, this.width, this.topHeight); // 下方管道 ctx.fillRect(this.x, canvas.height - this.bottomHeight, this.width, this.bottomHeight); } update() { this.x -= this.speed; // 计分检测 if (!this.passed && this.x + this.width < bird.x) { score++; this.passed = true; } } collide(bird) { return ( bird.x + bird.width/2 > this.x && bird.x - bird.width/2 < this.x + this.width && (bird.y - bird.height/2 < this.topHeight || bird.y + bird.height/2 > canvas.height - this.bottomHeight) ); } }管道生成逻辑需要控制频率:
let pipes = []; let pipeTimer = 0; const pipeInterval = 120; // 帧数 function generatePipes() { if (pipeTimer % pipeInterval === 0) { pipes.push(new Pipe()); } pipeTimer++; }4. 游戏循环与碰撞检测
核心游戏循环负责更新所有对象状态并重绘画面:
function gameLoop() { if (!gameRunning) return; // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新状态 bird.update(); generatePipes(); pipes.forEach((pipe, index) => { pipe.update(); pipe.draw(); // 碰撞检测 if (pipe.collide(bird)) { gameOver(); } // 移除屏幕外的管道 if (pipe.x + pipe.width < 0) { pipes.splice(index, 1); } }); // 绘制小鸟和分数 bird.draw(); drawScore(); requestAnimationFrame(gameLoop); } function drawScore() { ctx.fillStyle = 'black'; ctx.font = '24px Arial'; ctx.fillText(`得分: ${score}`, 20, 30); } function gameOver() { gameRunning = false; ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; ctx.font = '36px Arial'; ctx.textAlign = 'center'; ctx.fillText('游戏结束', canvas.width/2, canvas.height/2); ctx.fillText(`最终得分: ${score}`, canvas.width/2, canvas.height/2 + 50); }5. 添加交互控制
为游戏添加键盘和触摸控制:
// 键盘控制 document.addEventListener('keydown', (e) => { if (e.code === 'Space') { bird.flap(); if (!gameRunning) resetGame(); } }); // 触摸控制 canvas.addEventListener('click', () => { bird.flap(); if (!gameRunning) resetGame(); }); function resetGame() { bird = new Bird(); pipes = []; score = 0; gameRunning = true; gameLoop(); }6. 性能优化与扩展建议
现有基础版本还可以进一步优化:
性能优化点:
- 使用
requestAnimationFrame替代setInterval - 对象池技术重用管道对象
- 离屏Canvas预渲染静态元素
// 对象池示例 const pipePool = []; function getPipe() { if (pipePool.length > 0) { return pipePool.pop(); } return new Pipe(); } function recyclePipe(pipe) { pipePool.push(pipe); }扩展功能建议:
- 添加开始菜单和难度选择
- 实现粒子特效(碰撞时的爆炸效果)
- 加入音效系统
- 本地存储最高分记录
- 移动端适配(viewport设置)
完整代码已经包含了游戏所有核心机制。现在你应该能理解:
- Canvas绘图的基本原理
- 游戏循环的实现方式
- 物理模拟(重力加速度)
- 碰撞检测算法
- 事件驱动的交互设计
试着修改参数看看效果:把gravity改为0.3会让游戏变简单,调整pipeInterval可以改变管道生成频率。我在教学时发现,很多初学者都是在调整这些参数时突然开窍,理解了变量如何影响程序行为。