用HTML5 Canvas和JavaScript打造复古贪吃蛇游戏(完整代码+详细注释)

用HTML5 Canvas和JavaScript打造复古贪吃蛇游戏(完整代码+详细注释) 用HTML5 Canvas和JavaScript打造复古贪吃蛇游戏完整代码详细注释还记得那些在诺基亚手机上玩贪吃蛇的午后吗这款诞生于1976年的经典游戏如今依然散发着独特的魅力。本文将带你从零开始用现代前端技术重现这份怀旧体验。不同于简单的代码搬运我们会深入剖析每个模块的设计思路让你真正掌握游戏开发的精髓。1. 游戏架构设计在动手写代码前我们需要明确游戏的核心组件。贪吃蛇游戏本质上是一个状态机包含以下几个关键部分游戏世界基于网格的二维空间实体对象蛇身、食物、障碍物控制系统键盘事件监听游戏规则移动机制、碰撞检测、得分计算// 基础配置常量 const CONFIG { GRID_SIZE: 20, // 每个网格的像素大小 FPS: 10, // 帧率控制 INITIAL_LENGTH: 3 // 初始蛇身长度 };提示使用常量集中管理配置参数后期调整游戏难度时会非常方便2. Canvas绘图基础HTML5 Canvas是我们的画布需要先建立坐标系系统。这里采用网格化设计将画布划分为N×N的网格单元const canvas document.getElementById(gameCanvas); const ctx canvas.getContext(2d); // 计算网格数量 const gridCount canvas.width / CONFIG.GRID_SIZE;绘制函数需要处理三种图形元素蛇身由多个相连的网格组成食物随机出现在空白网格的红色方块背景纯色填充的棋盘效果function drawCell(x, y, color) { ctx.fillStyle color; ctx.fillRect( x * CONFIG.GRID_SIZE, y * CONFIG.GRID_SIZE, CONFIG.GRID_SIZE, CONFIG.GRID_SIZE ); }3. 游戏逻辑实现3.1 蛇的移动算法蛇的移动本质是队列操作在头部添加新位置尾部移除旧位置除非吃到食物。我们使用数组存储蛇身坐标let snake [ {x: 10, y: 10}, {x: 9, y: 10}, {x: 8, y: 10} ]; function moveSnake() { const head {...snake[0]}; // 复制头部 // 根据当前方向更新头部位置 head.x direction.x; head.y direction.y; snake.unshift(head); // 添加新头部 if (!hasEatenFood()) { snake.pop(); // 移除尾部 } }3.2 碰撞检测系统需要检测三种碰撞情况碰撞类型检测条件游戏结果边界碰撞x0 或 x≥gridCount游戏结束自碰头部坐标存在于蛇身数组游戏结束食物碰撞头部坐标食物坐标得分增加function checkCollision() { const head snake[0]; // 边界检测 if (head.x 0 || head.x gridCount || head.y 0 || head.y gridCount) { return wall; } // 自碰检测跳过头部 for (let i 1; i snake.length; i) { if (snake[i].x head.x snake[i].y head.y) { return self; } } // 食物检测 if (head.x food.x head.y food.y) { return food; } return null; }4. 游戏流程控制4.1 主游戏循环使用requestAnimationFrame实现平滑的游戏循环let lastRenderTime 0; function main(currentTime) { if (gameOver) return; window.requestAnimationFrame(main); const secondsSinceLastRender (currentTime - lastRenderTime) / 1000; if (secondsSinceLastRender 1 / CONFIG.FPS) return; lastRenderTime currentTime; update(); draw(); } function update() { moveSnake(); const collision checkCollision(); if (collision food) { generateFood(); increaseScore(); } else if (collision) { endGame(); } }4.2 用户输入处理通过键盘事件改变移动方向注意禁止180°转向let direction { x: 1, y: 0 }; window.addEventListener(keydown, e { switch (e.key) { case ArrowUp: if (direction.y 0) direction { x: 0, y: -1 }; break; case ArrowDown: if (direction.y 0) direction { x: 0, y: 1 }; break; case ArrowLeft: if (direction.x 0) direction { x: -1, y: 0 }; break; case ArrowRight: if (direction.x 0) direction { x: 1, y: 0 }; break; } });5. 进阶优化技巧5.1 性能优化方案双缓冲技术使用离屏Canvas预渲染脏矩形重绘只更新变化的部分区域节流控制限制最高帧率避免过度消耗资源// 离屏Canvas示例 const offscreenCanvas document.createElement(canvas); offscreenCanvas.width canvas.width; offscreenCanvas.height canvas.height; const offscreenCtx offscreenCanvas.getContext(2d); function renderToOffscreen() { // 所有绘制操作先在离屏Canvas完成 offscreenCtx.fillStyle #000; offscreenCtx.fillRect(0, 0, offscreenCanvas.width, offscreenCanvas.height); // 绘制蛇和食物... } function draw() { // 将离屏内容一次性绘制到主Canvas ctx.drawImage(offscreenCanvas, 0, 0); }5.2 游戏性增强加速机制随着分数增加逐渐提高移动速度特殊食物不同颜色的食物有不同效果关卡设计添加障碍物增加挑战性// 特殊食物类型 const FOOD_TYPES { NORMAL: { color: #FF0000, score: 10 }, GOLDEN: { color: #FFD700, score: 50 }, SPEED: { color: #00FFFF, effect: () { gameSpeed - 20; } } }; let currentFoodType FOOD_TYPES.NORMAL; function generateFood() { // 10%概率生成特殊食物 if (Math.random() 0.1) { const specialTypes Object.values(FOOD_TYPES).slice(1); currentFoodType specialTypes[Math.floor(Math.random() * specialTypes.length)]; } else { currentFoodType FOOD_TYPES.NORMAL; } // 生成不在蛇身上的随机位置 do { food { x: Math.floor(Math.random() * gridCount), y: Math.floor(Math.random() * gridCount) }; } while (snake.some(segment segment.x food.x segment.y food.y)); }6. 完整代码实现以下是整合所有模块的完整实现包含详细注释!DOCTYPE html html head title复古贪吃蛇/title style body { display: flex; flex-direction: column; align-items: center; background: #222; color: #eee; } canvas { border: 2px solid #444; margin: 20px; } .controls { margin: 10px; text-align: center; } /style /head body h1复古贪吃蛇/h1 div classscore得分: span idscore0/span/div canvas idgameCanvas width400 height400/canvas div classcontrols p方向键控制移动 | 空格键暂停/p /div script // 初始化游戏常量 const GRID_SIZE 20; const GRID_COUNT 20; const FPS 10; // 获取DOM元素 const canvas document.getElementById(gameCanvas); const ctx canvas.getContext(2d); const scoreElement document.getElementById(score); // 游戏状态 let snake []; let food {}; let direction { x: 1, y: 0 }; let score 0; let gameOver false; let isPaused false; let lastRenderTime 0; // 初始化游戏 function initGame() { const center Math.floor(GRID_COUNT / 2); snake Array.from({ length: 3 }, (_, i) ({ x: center - i, y: center })); generateFood(); score 0; gameOver false; scoreElement.textContent score; } // 生成食物 function generateFood() { do { food { x: Math.floor(Math.random() * GRID_COUNT), y: Math.floor(Math.random() * GRID_COUNT) }; } while (snake.some(segment segment.x food.x segment.y food.y)); } // 主游戏循环 function gameLoop(currentTime) { if (gameOver) return; window.requestAnimationFrame(gameLoop); const secondsSinceLastRender (currentTime - lastRenderTime) / 1000; if (secondsSinceLastRender 1 / FPS || isPaused) return; lastRenderTime currentTime; update(); draw(); } // 更新游戏状态 function update() { // 移动蛇 const head { ...snake[0] }; head.x direction.x; head.y direction.y; // 碰撞检测 if (head.x 0 || head.x GRID_COUNT || head.y 0 || head.y GRID_COUNT || snake.some(segment segment.x head.x segment.y head.y)) { gameOver true; return; } snake.unshift(head); // 食物检测 if (head.x food.x head.y food.y) { score 10; scoreElement.textContent score; generateFood(); } else { snake.pop(); } } // 绘制游戏 function draw() { // 清空画布 ctx.fillStyle #000; ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制食物 ctx.fillStyle #f00; ctx.fillRect( food.x * GRID_SIZE, food.y * GRID_SIZE, GRID_SIZE, GRID_SIZE ); // 绘制蛇 ctx.fillStyle #0f0; snake.forEach(segment { ctx.fillRect( segment.x * GRID_SIZE, segment.y * GRID_SIZE, GRID_SIZE, GRID_SIZE ); }); // 游戏结束提示 if (gameOver) { ctx.fillStyle rgba(0, 0, 0, 0.75); 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); ctx.font 20px Arial; ctx.fillText(按R键重新开始, canvas.width/2, canvas.height/2 40); } } // 键盘控制 window.addEventListener(keydown, e { if (gameOver e.key.toLowerCase() r) { initGame(); gameLoop(); return; } if (e.key ) { isPaused !isPaused; return; } switch (e.key) { case ArrowUp: if (direction.y 0) direction { x: 0, y: -1 }; break; case ArrowDown: if (direction.y 0) direction { x: 0, y: 1 }; break; case ArrowLeft: if (direction.x 0) direction { x: -1, y: 0 }; break; case ArrowRight: if (direction.x 0) direction { x: 1, y: 0 }; break; } }); // 启动游戏 initGame(); gameLoop(); /script /body /html在实际调试时发现控制蛇移动的流畅度与帧率设置密切相关。当FPS设置为10时既能保证游戏响应速度又不会让蛇移动得过快。如果想让游戏更具挑战性可以尝试将FPS提高到15同时减小网格尺寸增加复杂度。