news 2026/4/15 21:16:30

从零构建H5贪吃蛇游戏:HTML+CSS+JavaScript实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建H5贪吃蛇游戏:HTML+CSS+JavaScript实战解析

1. 准备工作:搭建基础HTML结构

第一次接触前端开发时,我最头疼的就是不知道从哪开始。后来发现,就像盖房子要先打地基一样,做网页游戏也得先搭建好HTML骨架。这个贪吃蛇游戏只需要最基本的HTML结构,完全不用担心复杂。

我们先创建一个标准的HTML5文档框架。建议直接用VS Code新建文件,保存为snake.html。这里有个小技巧:输入!然后按Tab键,编辑器会自动生成HTML5基础模板,省去手动输入的麻烦。我刚开始学的时候不知道这个功能,每次都傻乎乎地手敲DOCTYPE声明。

游戏界面我们使用canvas元素来实现,这是HTML5专门为图形处理提供的"画板"。设置画布尺寸时要注意,宽900px高600px是为了适配常见的显示器分辨率。记得给canvas加个边框,这样游戏区域看起来更清晰。下面是我调整过多次后觉得最顺眼的样式:

<!DOCTYPE html> <html> <head> <title>贪吃蛇</title> <meta charset="UTF-8"> <style> body { display: flex; justify-content: center; background: #f0f0f0; } .game-container { margin-top: 50px; border: 10px solid #333; border-radius: 5px; box-shadow: 0 0 20px rgba(0,0,0,0.2); } canvas { background: #aad751; display: block; } </style> </head> <body> <div class="game-container"> <canvas id="gameCanvas" width="600" height="600"></canvas> </div> <script src="game.js"></script> </body> </html>

这里有几个实用细节:body使用flex布局让游戏区自动居中;给容器加阴影增强立体感;canvas背景用浅绿色模拟经典贪吃蛇游戏的草地效果。这些视觉优化虽然不影响功能,但能让游戏看起来更专业。

2. 游戏界面绘制:Canvas基础运用

刚开始用canvas时,我总把它想象成一块真正的画布。ctx就像是手中的画笔,fillRect就是画方块的命令。贪吃蛇游戏的核心就是不断地用方块拼出蛇身和食物。

我们先获取canvas的绘图上下文。这里有个坑要注意:必须在页面完全加载后才能获取canvas元素,否则会报null错误。我当初就因为把script标签放在head里调试了半天。

// 等页面加载完成再执行 window.onload = function() { const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // 网格背景绘制 function drawGrid() { ctx.strokeStyle = '#8aad5a'; ctx.lineWidth = 1; // 竖线 for(let i = 0; i <= 600; i += 20) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, 600); ctx.stroke(); } // 横线 for(let j = 0; j <= 600; j += 20) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(600, j); ctx.stroke(); } } drawGrid(); };

蛇身和食物都是用fillRect方法绘制的方块。我建议把每个方块设为20x20像素,这样600x600的画布正好分成30x30的网格。这个尺寸计算起来方便,玩起来也不会觉得太拥挤。

绘制食物时可以用fillStyle设置不同颜色。我习惯用红色表示食物,深绿色表示蛇身,蛇头则用更亮的颜色区分。这样玩家一眼就能看清游戏元素:

// 绘制食物 function drawFood() { ctx.fillStyle = 'red'; ctx.fillRect(food.x * 20, food.y * 20, 20, 20); } // 绘制蛇 function drawSnake() { ctx.fillStyle = 'green'; snake.forEach((segment, index) => { if(index === snake.length - 1) { ctx.fillStyle = 'lightgreen'; // 蛇头特殊颜色 } ctx.fillRect(segment.x * 20, segment.y * 20, 20, 20); }); }

3. 游戏逻辑实现:蛇的移动与控制

让蛇动起来是游戏最核心的部分。我最初实现时遇到了两个问题:一是蛇身移动不连贯,二是按键响应有延迟。后来发现是因为没处理好游戏循环和键盘事件的配合。

蛇的移动原理其实很简单:在移动方向添加一个新蛇头,去掉尾部一节。用数组表示蛇身的话,就是push新坐标后shift掉第一个元素:

let snake = [ {x: 10, y: 10}, {x: 11, y: 10}, {x: 12, y: 10} ]; let direction = 'right'; function moveSnake() { const head = {...snake[snake.length - 1]}; switch(direction) { case 'up': head.y -= 1; break; case 'down': head.y += 1; break; case 'left': head.x -= 1; break; case 'right': head.x += 1; break; } snake.push(head); snake.shift(); }

控制蛇转向要监听键盘事件。这里有个重要细节:要防止180度急转弯,比如正在向右移动时不能直接转向左。我加了个方向锁解决了这个问题:

let nextDirection = 'right'; document.addEventListener('keydown', e => { switch(e.key) { case 'ArrowUp': if(direction !== 'down') nextDirection = 'up'; break; case 'ArrowDown': if(direction !== 'up') nextDirection = 'down'; break; case 'ArrowLeft': if(direction !== 'right') nextDirection = 'left'; break; case 'ArrowRight': if(direction !== 'left') nextDirection = 'right'; break; } }); // 在游戏循环中更新方向 function gameLoop() { direction = nextDirection; moveSnake(); // 其他逻辑... }

游戏循环建议用requestAnimationFrame实现,它比setInterval更流畅,会自动匹配屏幕刷新率。我设置的150ms移动一次速度适中,初学者可以调整这个值改变游戏难度:

let lastRenderTime = 0; const SPEED = 150; // 毫秒 function main(currentTime) { window.requestAnimationFrame(main); if(currentTime - lastRenderTime < SPEED) return; lastRenderTime = currentTime; updateGame(); drawGame(); } window.requestAnimationFrame(main);

4. 游戏机制完善:碰撞检测与得分

完整的游戏需要能吃食物变长、撞墙/撞自己结束游戏等功能。这些都属于碰撞检测的范畴,是游戏开发中最常见的需求之一。

检测蛇是否吃到食物很简单,只需判断蛇头坐标是否与食物坐标重合。我最初实现的食物生成没有考虑避开蛇身,导致食物可能出现在蛇肚子里,后来加了个校验逻辑:

let food = {x: 5, y: 5}; function generateFood() { let newFood; do { newFood = { x: Math.floor(Math.random() * 30), y: Math.floor(Math.random() * 30) }; } while(snake.some(segment => segment.x === newFood.x && segment.y === newFood.y )); food = newFood; } function checkEatFood() { const head = snake[snake.length - 1]; if(head.x === food.x && head.y === food.y) { snake.unshift({...snake[0]}); // 在尾部添加一节 generateFood(); updateScore(); } }

死亡检测包括撞墙和撞自己两种情况。撞墙检测就是判断蛇头是否超出画布边界,撞自己则是检查蛇头坐标是否与任何一节蛇身重合:

function checkDeath() { const head = snake[snake.length - 1]; // 撞墙检测 if(head.x < 0 || head.x >= 30 || head.y < 0 || head.y >= 30) { gameOver(); return; } // 撞自己检测(跳过蛇头) for(let i = 0; i < snake.length - 1; i++) { if(head.x === snake[i].x && head.y === snake[i].y) { gameOver(); return; } } } function gameOver() { alert(`游戏结束!得分:${snake.length - 3}`); // 重置游戏状态 snake = [ {x: 10, y: 10}, {x: 11, y: 10}, {x: 12, y: 10} ]; direction = 'right'; nextDirection = 'right'; generateFood(); }

得分系统可以简单用蛇身长度表示。我建议初始长度为3,所以实际得分是长度减3。如果想更专业,可以添加localStorage存储最高分:

let highScore = localStorage.getItem('snakeHighScore') || 0; function updateScore() { const currentScore = snake.length - 3; if(currentScore > highScore) { highScore = currentScore; localStorage.setItem('snakeHighScore', highScore); } document.getElementById('score').innerText = `当前得分:${currentScore} | 最高分:${highScore}`; }

5. 优化与扩展:让游戏更专业

基础功能完成后,可以添加一些优化让游戏体验更好。我总结了几点最实用的改进方案:

首先是游戏暂停功能。通过添加一个状态变量,可以在游戏循环中判断是否应该更新游戏状态:

let isPaused = false; document.addEventListener('keydown', e => { if(e.key === ' ') isPaused = !isPaused; // 空格键暂停/继续 }); function updateGame() { if(isPaused) return; // 正常游戏逻辑... }

其次是游戏开始界面。很多初学者会直接开始游戏,但添加一个开始界面能让体验更完整:

function drawStartScreen() { ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.fillRect(0, 0, 600, 600); ctx.fillStyle = 'white'; ctx.font = '40px Arial'; ctx.textAlign = 'center'; ctx.fillText('贪吃蛇', 300, 200); ctx.font = '20px Arial'; ctx.fillText('按任意键开始游戏', 300, 300); ctx.fillText(`最高分:${highScore}`, 300, 350); } let gameStarted = false; document.addEventListener('keydown', () => { if(!gameStarted) { gameStarted = true; window.requestAnimationFrame(main); } });

游戏难度可以随着分数增加而提高。我的做法是每得5分就加快游戏速度:

function updateGame() { const baseSpeed = 150; const speedIncrease = Math.floor((snake.length - 3) / 5) * 10; const currentSpeed = Math.max(baseSpeed - speedIncrease, 50); if(currentTime - lastRenderTime < currentSpeed) return; // ... }

最后是移动端适配。虽然贪吃蛇更适合键盘操作,但添加触摸控制可以让手机也能玩:

// 添加触摸控制按钮 <div class="controls"> <button id="up">↑</button> <button id="left">←</button> <button id="right">→</button> <button id="down">↓</button> </div> // 按钮事件监听 document.getElementById('up').addEventListener('touchstart', () => { if(direction !== 'down') nextDirection = 'up'; }); // 其他方向类似...

这些优化不是必须的,但能让你的贪吃蛇项目从"能玩"升级到"好玩"。我建议初学者先完成基础版本,再逐步添加这些功能,这样可以更好地理解每个改进的意义。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 21:05:36

ComfyUI融合WAN2.1:单图驱动LoRA炼成IP角色全场景通用模型

1. 从单图到全场景&#xff1a;WAN2.1LoRA技术组合揭秘 当你手里只有一张IP角色设计图&#xff0c;却需要它在不同风格、角度和光影条件下保持特征一致时&#xff0c;传统方法往往会让你陷入反复调试的泥潭。最近我在一个动漫周边开发项目中&#xff0c;就遇到了主角形象在周边…

作者头像 李华
网站建设 2026/4/15 21:00:10

新手避坑指南:超声波探伤仪A扫波形图到底怎么看?从杂波识别到缺陷定级的实战解析

新手避坑指南&#xff1a;超声波探伤仪A扫波形图到底怎么看&#xff1f;从杂波识别到缺陷定级的实战解析 第一次面对超声波探伤仪屏幕上跳动的波形时&#xff0c;那种茫然感我至今记忆犹新。屏幕上那些高低起伏的尖峰就像一道难以破解的密码&#xff0c;让人无从下手。作为过来…

作者头像 李华