简介
Flappy Bird 是一款极简风格的飞行闯关游戏,玩家控制小鸟躲避管道障碍,穿越一对管道得一分。游戏厅版本使用 Canvas 渲染,实现了完整的物理模拟和碰撞检测。
游戏参数
1var W = 400, H = 500; // 画布尺寸
2var bird = { x: 80, y: 200, r: 16 }; // 小鸟位置和半径
3var gravity = 0.45; // 重力加速度
4var jumpForce = -7.5; // 跳跃力度
5var pipeW = 52; // 管道宽度
6var pipeGap = 140; // 管道间距
7var pipeSpeed = 2.2; // 管道移动速度
核心逻辑
重力与跳跃
小鸟每一帧受到重力影响向下加速,按空格键时获得向上的初速度:
1function update() {
2 // 重力加速
3 bird.vy += gravity;
4 bird.y += bird.vy;
5
6 // 顶部/底部边界
7 if (bird.y + bird.r > H - 45 || bird.y - bird.r < 0) {
8 gameOver();
9 return;
10 }
11}
跳跃时重置垂直速度:
1function jump() {
2 if (!playing) {
3 // 首次跳跃 = 开始游戏
4 startGame();
5 return;
6 }
7 bird.vy = jumpForce;
8}
管道系统
管道从右向左匀速移动,离开屏幕后移除。当上一对管道越过一定距离时生成新管道:
1var pipes = []; // { x, topH, passed }
2
3function spawnPipe() {
4 var minH = 50;
5 var maxH = H - pipeGap - 50 - 80; // 扣除地面
6 var topH = minH + Math.random() * maxH;
7 pipes.push({ x: W, topH: topH, passed: false });
8}
9
10function updatePipes() {
11 for (var i = pipes.length - 1; i >= 0; i--) {
12 pipes[i].x -= pipeSpeed;
13
14 // 计分:小鸟飞过管道
15 if (!pipes[i].passed && pipes[i].x + pipeW < bird.x) {
16 pipes[i].passed = true;
17 score++;
18 }
19
20 // 移除离屏管道
21 if (pipes[i].x + pipeW < 0) pipes.splice(i, 1);
22 }
23
24 // 生成新管道
25 if (pipes.length === 0 || pipes[pipes.length - 1].x < W - 200) {
26 spawnPipe();
27 }
28}
碰撞检测
小鸟的圆形碰撞体与管道矩形碰撞体的检测:
1function checkCollision() {
2 for (var i = 0; i < pipes.length; i++) {
3 var p = pipes[i];
4
5 // 小鸟是否在管道水平范围内
6 if (bird.x + bird.r > p.x && bird.x - bird.r < p.x + pipeW) {
7 // 碰到上管道或下管道
8 if (bird.y - bird.r < p.topH || bird.y + bird.r > p.topH + pipeGap) {
9 gameOver();
10 return;
11 }
12 }
13 }
14}
Canvas 渲染
天空渐变背景
1function drawSky() {
2 var sky = ctx.createLinearGradient(0, 0, 0, H);
3 sky.addColorStop(0, '#87CEEB'); // 天蓝
4 sky.addColorStop(0.4, '#98D8E8');
5 sky.addColorStop(0.7, '#E8DFC0'); // 渐变到沙色
6 sky.addColorStop(0.85, '#D4C898');
7 sky.addColorStop(1, '#C8B878');
8 ctx.fillStyle = sky;
9 ctx.fillRect(0, 0, W, H);
10}
地面
1function drawGround() {
2 // 砂土地面
3 ctx.fillStyle = '#8B7355';
4 ctx.fillRect(0, H - 40, W, 40);
5 // 草地边缘
6 ctx.fillStyle = '#6B8E23';
7 ctx.fillRect(0, H - 45, W, 8);
8}
管道
管道本体 + 管口加宽效果:
1function drawPipes() {
2 pipes.forEach(function(p) {
3 // 上管道
4 ctx.fillStyle = '#2d6a4f';
5 ctx.fillRect(p.x, 0, pipeW, p.topH);
6 // 上管口
7 ctx.fillStyle = '#40916c';
8 ctx.fillRect(p.x - 4, p.topH - 25, pipeW + 8, 25);
9
10 // 下管道
11 var bottomY = p.topH + pipeGap;
12 ctx.fillStyle = '#2d6a4f';
13 ctx.fillRect(p.x, bottomY, pipeW, H - bottomY - 40);
14 // 下管口
15 ctx.fillStyle = '#40916c';
16 ctx.fillRect(p.x - 4, bottomY, pipeW + 8, 25);
17 });
18}
小鸟
圆形身体 + 高光 + 眼睛 + 鸟嘴:
1function drawBird() {
2 // 身体
3 ctx.fillStyle = '#fbbf24';
4 ctx.beginPath();
5 ctx.arc(bird.x, bird.y, bird.r, 0, Math.PI * 2);
6 ctx.fill();
7
8 // 腹部高光
9 ctx.fillStyle = '#f59e0b';
10 ctx.beginPath();
11 ctx.arc(bird.x + 2, bird.y - 2, bird.r * 0.7, 0, Math.PI * 2);
12 ctx.fill();
13
14 // 眼睛
15 ctx.fillStyle = '#000';
16 ctx.beginPath();
17 ctx.arc(bird.x + bird.r * 0.5, bird.y - bird.r * 0.3, 3, 0, Math.PI * 2);
18 ctx.fill();
19
20 // 眼睛高光
21 ctx.fillStyle = '#fff';
22 ctx.beginPath();
23 ctx.arc(bird.x + bird.r * 0.5 - 1, bird.y - bird.r * 0.3 - 1, 1, 0, Math.PI * 2);
24 ctx.fill();
25
26 // 鸟嘴
27 ctx.fillStyle = '#f97316';
28 ctx.beginPath();
29 ctx.moveTo(bird.x + bird.r, bird.y);
30 ctx.lineTo(bird.x + bird.r + 10, bird.y + 2);
31 ctx.lineTo(bird.x + bird.r, bird.y + 5);
32 ctx.fill();
33}
游戏循环
1function gameLoop() {
2 if (!playing) return;
3 update();
4 drawSky();
5 drawGround();
6 drawPipes();
7 drawBird();
8 drawScore();
9 frameId = requestAnimationFrame(gameLoop);
10}
计分显示
1function drawScore() {
2 ctx.fillStyle = '#fff';
3 ctx.font = 'bold 32px sans-serif';
4 ctx.textAlign = 'center';
5 ctx.fillText(score, W / 2, 50);
6 // 黑色描边增加可读性
7 ctx.strokeStyle = '#000';
8 ctx.lineWidth = 2;
9 ctx.strokeText(score, W / 2, 50);
10}
游戏结束画面
1function gameOver() {
2 playing = false;
3 cancelAnimationFrame(frameId);
4
5 // 半透明遮罩
6 ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
7 ctx.fillRect(0, 0, W, H);
8
9 // 结束文字
10 ctx.fillStyle = '#fff';
11 ctx.font = 'bold 24px sans-serif';
12 ctx.textAlign = 'center';
13 ctx.fillText('游戏结束', W / 2, H / 2 - 10);
14 ctx.font = '16px sans-serif';
15 ctx.fillText('得分: ' + score + ' 点击重新开始', W / 2, H / 2 + 20);
16}
最高分持久化
1function updateBest() {
2 if (score > best) {
3 best = score;
4 localStorage.setItem('arcade-flappy-best', best);
5 }
6}
总结
Flappy Bird 看似简单,但涉及重力模拟、管道生成、碰撞检测等多个物理概念。Canvas 绘制的渐变天空和细节丰富的小鸟形象提升了视觉体验。管道生成使用了随机高度算法,确保每局游戏都不同。
留言评论
期待你的想法评论加载中