当前位置: 首页 > news >正文

用原生JS手搓一个Flappy Bird小游戏(附完整源码和重力模拟详解)

用原生JS手搓一个Flappy Bird小游戏(附完整源码和重力模拟详解)

在游戏开发的世界里,Flappy Bird以其简单的机制和令人上瘾的玩法成为了经典。对于前端开发者来说,用原生JavaScript实现这个小游戏不仅能巩固基础,还能深入理解游戏开发的核心原理。本文将带你从零开始,一步步构建一个完整的Flappy Bird游戏,重点解析重力模拟、碰撞检测等关键机制。

1. 游戏基础架构搭建

任何游戏开发的第一步都是搭建基础架构。对于我们的Flappy Bird来说,这意味着创建HTML结构、设置Canvas画布以及初始化游戏状态。

首先创建一个基本的HTML文件结构:

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>原生JS实现Flappy Bird</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f0f0f0; } #gameCanvas { background: skyblue; border: 2px solid #333; } </style> </head> <body> <canvas id="gameCanvas" width="400" height="600"></canvas> <script src="game.js"></script> </body> </html>

接下来是游戏的核心JavaScript文件(game.js)的初始化:

// 获取Canvas元素和上下文 const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // 游戏状态 const gameState = { isRunning: false, score: 0, highScore: 0, gravity: 0.5, bird: { x: 100, y: 300, width: 40, height: 30, velocity: 0, jumpForce: -10 }, pipes: [], lastPipeTime: 0, pipeInterval: 1500 // 管道生成间隔(毫秒) };

2. 游戏角色与物理系统实现

Flappy Bird的核心在于鸟类的飞行物理系统。我们需要模拟重力、跳跃和碰撞等物理行为。

2.1 重力与跳跃模拟

重力是让游戏具有挑战性的关键因素。以下是实现代码:

function updateBird() { // 应用重力 gameState.bird.velocity += gameState.gravity; gameState.bird.y += gameState.bird.velocity; // 防止鸟飞出屏幕顶部 if (gameState.bird.y < 0) { gameState.bird.y = 0; gameState.bird.velocity = 0; } // 检测是否落地 if (gameState.bird.y + gameState.bird.height > canvas.height) { gameOver(); } } function jump() { gameState.bird.velocity = gameState.bird.jumpForce; } // 监听空格键和鼠标点击事件 document.addEventListener('keydown', (e) => { if (e.code === 'Space') { if (!gameState.isRunning) startGame(); jump(); } }); canvas.addEventListener('click', () => { if (!gameState.isRunning) startGame(); jump(); });

2.2 鸟类动画效果

为了让游戏更生动,我们可以为小鸟添加简单的动画效果:

function drawBird() { ctx.save(); ctx.translate(gameState.bird.x, gameState.bird.y); // 根据速度改变鸟的角度 const angle = Math.min(Math.max(gameState.bird.velocity * 3, -30), 30); ctx.rotate(angle * Math.PI / 180); // 绘制鸟的身体 ctx.fillStyle = '#FFD700'; ctx.beginPath(); ctx.ellipse(20, 15, 20, 15, 0, 0, Math.PI * 2); ctx.fill(); // 绘制鸟的喙 ctx.fillStyle = '#FF6347'; ctx.beginPath(); ctx.moveTo(40, 15); ctx.lineTo(50, 15); ctx.lineTo(40, 20); ctx.fill(); // 绘制鸟的眼睛 ctx.fillStyle = 'black'; ctx.beginPath(); ctx.arc(30, 10, 3, 0, Math.PI * 2); ctx.fill(); ctx.restore(); }

3. 障碍物系统设计

Flappy Bird的挑战来自于随机生成的管道障碍物。我们需要实现管道的生成、移动和碰撞检测。

3.1 管道生成逻辑

function generatePipe() { const now = Date.now(); if (now - gameState.lastPipeTime < gameState.pipeInterval) return; gameState.lastPipeTime = now; // 管道参数 const pipeWidth = 60; const gapHeight = 150; const minPipeHeight = 50; const maxPipeHeight = canvas.height - gapHeight - minPipeHeight; // 随机生成顶部管道高度 const topPipeHeight = Math.floor( Math.random() * (maxPipeHeight - minPipeHeight + 1) + minPipeHeight ); // 创建管道对象 const newPipe = { x: canvas.width, width: pipeWidth, topHeight: topPipeHeight, bottomY: topPipeHeight + gapHeight, passed: false }; gameState.pipes.push(newPipe); }

3.2 管道移动与绘制

function updatePipes() { // 生成新管道 generatePipe(); // 更新现有管道位置 for (let i = 0; i < gameState.pipes.length; i++) { const pipe = gameState.pipes[i]; pipe.x -= 2; // 管道移动速度 // 检测是否通过管道 if (!pipe.passed && pipe.x + pipe.width < gameState.bird.x) { pipe.passed = true; gameState.score++; if (gameState.score > gameState.highScore) { gameState.highScore = gameState.score; } } // 检测碰撞 if (checkCollision(pipe)) { gameOver(); return; } } // 移除屏幕外的管道 gameState.pipes = gameState.pipes.filter(pipe => pipe.x + pipe.width > 0); } function drawPipes() { ctx.fillStyle = '#4CAF50'; for (const pipe of gameState.pipes) { // 绘制顶部管道 ctx.fillRect(pipe.x, 0, pipe.width, pipe.topHeight); // 绘制底部管道 ctx.fillRect( pipe.x, pipe.bottomY, pipe.width, canvas.height - pipe.bottomY ); } }

3.3 碰撞检测实现

精确的碰撞检测是游戏体验的关键:

function checkCollision(pipe) { const bird = gameState.bird; // 检测与顶部管道的碰撞 if (bird.x + bird.width > pipe.x && bird.x < pipe.x + pipe.width && bird.y < pipe.topHeight) { return true; } // 检测与底部管道的碰撞 if (bird.x + bird.width > pipe.x && bird.x < pipe.x + pipe.width && bird.y + bird.height > pipe.bottomY) { return true; } return false; }

4. 游戏循环与状态管理

一个流畅的游戏体验依赖于稳定的游戏循环和清晰的状态管理。

4.1 游戏主循环

function gameLoop() { if (!gameState.isRunning) return; // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新游戏状态 updateBird(); updatePipes(); // 绘制游戏元素 drawBackground(); drawPipes(); drawBird(); drawScore(); // 继续循环 requestAnimationFrame(gameLoop); } function startGame() { if (gameState.isRunning) return; // 重置游戏状态 gameState.isRunning = true; gameState.score = 0; gameState.bird.y = 300; gameState.bird.velocity = 0; gameState.pipes = []; gameState.lastPipeTime = 0; // 开始游戏循环 gameLoop(); } function gameOver() { gameState.isRunning = false; drawGameOver(); }

4.2 背景与UI绘制

良好的视觉效果能提升游戏体验:

function drawBackground() { // 渐变天空背景 const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, '#87CEEB'); gradient.addColorStop(1, '#E0F7FA'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制地面 ctx.fillStyle = '#8BC34A'; ctx.fillRect(0, canvas.height - 20, canvas.width, 20); } function drawScore() { ctx.fillStyle = '#000'; ctx.font = '24px Arial'; ctx.textAlign = 'center'; ctx.fillText(`得分: ${gameState.score}`, canvas.width / 2, 50); if (gameState.highScore > 0) { ctx.font = '16px Arial'; ctx.fillText(`最高分: ${gameState.highScore}`, canvas.width / 2, 80); } } function drawGameOver() { ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#FFF'; ctx.font = '36px Arial'; ctx.textAlign = 'center'; ctx.fillText('游戏结束', canvas.width / 2, canvas.height / 2 - 40); ctx.font = '24px Arial'; ctx.fillText(`最终得分: ${gameState.score}`, canvas.width / 2, canvas.height / 2); ctx.fillText('点击或按空格键重新开始', canvas.width / 2, canvas.height / 2 + 60); }

5. 性能优化与进阶技巧

完成基础版本后,我们可以考虑一些优化和增强功能。

5.1 性能优化建议

  • 减少重绘区域:只重绘发生变化的部分而非整个画布
  • 对象池模式:重用管道对象而非频繁创建销毁
  • 节流事件处理:防止快速连续跳跃
// 对象池示例 const pipePool = []; const POOL_SIZE = 10; function initPipePool() { for (let i = 0; i < POOL_SIZE; i++) { pipePool.push(createPipeObject()); } } function getPipeFromPool() { if (pipePool.length > 0) { return pipePool.pop(); } return createPipeObject(); } function returnPipeToPool(pipe) { if (pipePool.length < POOL_SIZE) { resetPipe(pipe); // 重置管道状态 pipePool.push(pipe); } }

5.2 游戏难度调节

随着游戏进行,可以逐渐增加难度:

function updateDifficulty() { // 每得5分增加难度 if (gameState.score > 0 && gameState.score % 5 === 0) { gameState.pipeInterval = Math.max(800, gameState.pipeInterval - 50); gameState.gravity = Math.min(0.8, gameState.gravity + 0.02); } } // 在gameLoop中调用 function gameLoop() { // ...其他代码... updateDifficulty(); // ...其他代码... }

5.3 添加视觉效果

增强游戏体验的视觉效果:

function drawPipes() { for (const pipe of gameState.pipes) { // 管道渐变效果 const topGradient = ctx.createLinearGradient( pipe.x, 0, pipe.x + pipe.width, 0 ); topGradient.addColorStop(0, '#2E7D32'); topGradient.addColorStop(1, '#4CAF50'); ctx.fillStyle = topGradient; ctx.fillRect(pipe.x, 0, pipe.width, pipe.topHeight); // 管道边缘效果 ctx.strokeStyle = '#1B5E20'; ctx.lineWidth = 2; ctx.strokeRect(pipe.x, 0, pipe.width, pipe.topHeight); // 底部管道同理... } }
http://www.gsyq.cn/news/1499925.html

相关文章:

  • go: Coroutines Pattern
  • 别再傻傻用真实邮箱测试了!手把手教你用Python脚本+Swaks搭建本地邮件伪造测试环境
  • 我的嵌入式数据记录仪:基于STM32F407和FreeRTOS,用SD卡实现长时间可靠存储
  • 青岛老旧小区楼顶漏水找哪家公司维修最靠谱?楼长修楼|政企共建老牌头部,专治老楼疑难漏水 - 青岛防水品牌推荐
  • 实战避坑:在RuoYi-Vue-Plus 3.5.0中集成Mybatis-Plus多租户插件,我踩过的那些坑
  • 告别电平不匹配!手把手教你用TXS0108E搞定3.3V与5V单片机通信(附电路图)
  • 专业科普・青岛买狗避坑指南:为什么本地人都推荐朋博猫舍犬舍 - 同城宠物优选基地
  • SolidWorks新手避坑指南:从草图变蓝到装配体配合,这10个常见问题我帮你踩过了
  • AT2018cow激波辐射模型解析:从X射线到光学的多波段观测
  • 2026年广东安保服务公司推荐榜单:工厂/学校/银行/商场/临时安保与安保巡逻优质企业深度解析 - 企业推荐官【官方】
  • 用StandardScaler做机器学习数据预处理?小心这个‘隐藏’的数据泄露陷阱!
  • 格兰头优质厂家选型推荐:行业深度解析、标准化选型维度与五大厂商量化测评 - 星城方舟
  • 从日志小白到分析高手:用Splunk SPL搜索语句玩转你的第一份服务器日志
  • 信号处理避坑指南:MATLAB FFT分析锤击响应时,90%的人会忽略的这3个细节
  • MuleSoft企业级AI编排:LLM生产化落地的合规底座与工程实践
  • 2026 年永州别墅建筑公司哪家好?6 个月完工零加价的真实建房案例分享 - GrowthUME
  • 别光看Backbone了!手把手带你拆解YOLOv5的Detect模块(附源码逐行解读)
  • 从数学到编程:用Python画杨辉三角,顺便理解二项式定理和组合数(附可视化教程)
  • 手把手教你用TMS320F28377S的CAN模块:从邮箱配置到数据收发实战
  • 广州配眼镜不同预算怎么选,镜片分类推荐 - 配眼镜新资讯
  • ArcGIS新手避坑指南:手把手教你创建第一个Shapefile矢量文件(附完整流程)
  • 别再死记硬背了!用贪心思想图解‘过河问题’,搞定信息学奥赛OpenJudge 702题
  • 手把手教你用Logisim搞定华中科大汉字字库实验(附完整电路图与字库文件)
  • 2026武汉三新技工学校综合榜单|实力领跑,热门专业真实评测 - GrowthUME
  • 2026年 广州/东莞/广东安保公司最新推荐榜:演唱会、商场、学校、小区、医院、赛事及私人商业安保实力之选 - 品牌发掘
  • 武汉正规电线电缆回收公司排行 合规性与服务对比 - 起跑123
  • 零基础入门深度学习:从ResNet开始,一步步带你理解神经网络
  • 立创EDA原理图与PCB联动实战:用好‘更新PCB’和‘导入变更’,效率翻倍
  • 告别连点!用计算器输入%147%+开启Android开发者选项(附完整代码)
  • 2026年6月最新版克拉玛依第三方CMACNAS甲醛检测治理机构口碑名单:万清CMA检测中心等5家公司深度测评万清CMA检测中心TOP1推荐 - 一休咨询