用原生JS手搓一个Flappy Bird小游戏附完整源码和重力模拟详解在游戏开发的世界里Flappy Bird以其简单的机制和令人上瘾的玩法成为了经典。对于前端开发者来说用原生JavaScript实现这个小游戏不仅能巩固基础还能深入理解游戏开发的核心原理。本文将带你从零开始一步步构建一个完整的Flappy Bird游戏重点解析重力模拟、碰撞检测等关键机制。1. 游戏基础架构搭建任何游戏开发的第一步都是搭建基础架构。对于我们的Flappy Bird来说这意味着创建HTML结构、设置Canvas画布以及初始化游戏状态。首先创建一个基本的HTML文件结构!DOCTYPE html html langzh head meta charsetUTF-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 idgameCanvas width400 height600/canvas script srcgame.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); // 底部管道同理... } }