贪吃蛇游戏实现步骤详解
本文详细介绍了如何使用JavaScript一步步实现经典的贪吃蛇游戏。首先,通过HTML搭建游戏的基本结构,创建一个canvas画布作为游戏界面。然后,利用JavaScript编写游戏的核心逻辑,包括蛇的移动、食物生成、碰撞检测以及游戏循环的实现。文章深入解析了游戏循环的工作原理,探讨了`setInterval`在游戏中的应用,并详细阐述了蛇的移动和碰撞检测的实现方法,以及如何生成不与蛇身重叠的随机食物。通过本文,读者可以全面了解贪吃蛇游戏的开发过程,掌握游戏开发中的基本概念,并为后续更复杂的游戏开发打下坚实基础。
游戏的核心循环通过setInterval驱动,分为更新和绘制两个阶段。1. 更新阶段处理蛇的移动、碰撞检测和食物逻辑;2. 绘制阶段将最新状态渲染到canvas上。蛇的移动通过计算新头部位置并更新数组实现,使用unshift添加头部和pop移除尾部模拟移动效果。碰撞检测包含三种情况:撞墙(超出画布边界)、撞自己(头部与身体坐标重合)和撞食物(得分并增长蛇身)。生成食物时通过随机坐标并检查是否与蛇身重叠,若重叠则递归重新生成,确保食物出现在空闲位置。

用JavaScript实现一个简单的贪吃蛇游戏,核心在于构建一个游戏循环,在这个循环里不断更新蛇的位置、绘制游戏状态,并响应玩家的键盘输入。它是一个非常经典的入门项目,能很好地帮助你理解游戏开发中的基本概念,比如游戏循环、碰撞检测和状态管理。

解决方案

要构建一个贪吃蛇游戏,我们需要HTML来提供一个画布(canvas),CSS来简单美化一下,然后用JavaScript来处理所有的游戏逻辑。
首先,在HTML中创建一个canvas元素:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简单的贪吃蛇</title>
<style>
body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background-color: #222; }
canvas { background-color: #000; border: 2px solid #555; display: block; }
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<script src="snake.js"></script>
</body>
</html>接着,是snake.js的核心逻辑:
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const gridSize = 20; // 每个方块的大小
const tileCount = canvas.width / gridSize; // 一行/一列有多少个方块
let snake = [{ x: 10, y: 10 }]; // 蛇的初始位置
let food = {}; // 食物的位置
let dx = 0; // x方向的速度
let dy = 0; // y方向的速度
let score = 0;
let changingDirection = false; // 防止快速按键导致方向冲突
// 游戏主循环,我个人偏爱用 setInterval,在简单的游戏中它足够直观
let gameInterval;
function generateFood() {
food = {
x: Math.floor(Math.random() * tileCount),
y: Math.floor(Math.random() * tileCount)
};
// 确保食物不生成在蛇身上
for (let i = 0; i < snake.length; i++) {
if (food.x === snake[i].x && food.y === snake[i].y) {
generateFood(); // 递归调用直到找到一个空位
return;
}
}
}
function draw() {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制食物
ctx.fillStyle = 'red';
ctx.strokeStyle = 'darkred';
ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
ctx.strokeRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
// 绘制蛇
ctx.fillStyle = 'lime';
ctx.strokeStyle = 'darkgreen';
snake.forEach(segment => {
ctx.fillRect(segment.x * gridSize, segment.y * gridSize, gridSize, gridSize);
ctx.strokeRect(segment.x * gridSize, segment.y * gridSize, gridSize, gridSize);
});
}
function update() {
changingDirection = false; // 允许再次改变方向
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
// 碰撞检测
if (head.x < 0 || head.x >= tileCount ||
head.y < 0 || head.y >= tileCount ||
checkCollision(head)) {
clearInterval(gameInterval); // 游戏结束
alert(`游戏结束!得分:${score}`);
return;
}
snake.unshift(head); // 将新头部添加到蛇的数组开头
const didEatFood = head.x === food.x && head.y === food.y;
if (didEatFood) {
score += 10;
generateFood(); // 生成新食物
} else {
snake.pop(); // 如果没吃到食物,移除尾巴
}
}
function checkCollision(head) {
// 检查头部是否与身体其他部分碰撞
for (let i = 1; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
return true;
}
}
return false;
}
function changeDirection(event) {
if (changingDirection) return;
changingDirection = true;
const keyPressed = event.keyCode;
const LEFT_KEY = 37;
const RIGHT_KEY = 39;
const UP_KEY = 38;
const DOWN_KEY = 40;
const goingUp = dy === -1;
const goingDown = dy === 1;
const goingLeft = dx === -1;
const goingRight = dx === 1;
// 避免蛇立即掉头
if (keyPressed === LEFT_KEY && !goingRight) {
dx = -1;
dy = 0;
}
if (keyPressed === UP_KEY && !goingDown) {
dx = 0;
dy = -1;
}
if (keyPressed === RIGHT_KEY && !goingLeft) {
dx = 1;
dy = 0;
}
if (keyPressed === DOWN_KEY && !goingUp) {
dx = 0;
dy = 1;
}
}
// 初始化游戏
function startGame() {
generateFood();
document.addEventListener('keydown', changeDirection);
// 初始方向,让蛇开始移动
dx = 1;
dy = 0;
gameInterval = setInterval(() => {
update();
draw();
}, 100); // 100毫秒更新一次,可以调整速度
}
startGame();这个方案涵盖了游戏的基本要素:画布设置、蛇和食物的表示、绘制函数、更新游戏状态的函数以及键盘事件监听。它是一个相当基础但完整的实现。
游戏的核心循环是如何运作的?
在我看来,游戏的核心循环就像是游戏的心脏,它跳动着,驱动着整个世界的运转。在贪吃蛇这种简单的2D游戏中,这个“跳动”通常通过一个定时器来实现。你可能会看到两种主要的方式:setInterval 和 requestAnimationFrame。
对于贪吃蛇这种基于网格、状态更新相对离散的游戏,我个人更倾向于使用setInterval。它简单直观,你设置一个固定的时间间隔(比如100毫秒),然后告诉浏览器每隔这么久就执行一次我的游戏逻辑。它的好处是,你对游戏的速度有非常精确的控制,每100毫秒蛇就移动一步,不会因为帧率波动导致蛇忽快忽慢。
这个循环里通常会包含两个主要阶段:
- 更新(Update):这个阶段是纯粹的逻辑处理。蛇的位置变了没?吃到食物了没?撞墙了没?这些都在这里计算。它不涉及任何的视觉呈现,只是修改游戏内部的数据状态。比如,蛇的头部坐标会根据当前的方向进行调整,如果吃了食物,蛇的身体数组会增长,否则就移除尾部。
- 绘制(Draw):在更新完所有游戏状态后,就需要把这些新的状态“画”到屏幕上。清除旧的画面,然后根据最新的蛇和食物坐标,在画布上重新绘制它们。
所以,整个流程就是:设定一个时间间隔 -> 在每个间隔里,先更新所有游戏数据 -> 然后根据新数据重新绘制画面 -> 重复。这个循环不断进行,直到游戏结束。虽然requestAnimationFrame在动画平滑度和资源优化上更有优势,因为它会与浏览器绘制周期同步,但对于这种步进式的游戏,setInterval的固定步长反而让逻辑更清晰。当然,如果未来想做更复杂的动画效果,比如蛇的平滑过渡,那requestAnimationFrame就是更好的选择。
如何处理蛇的移动和碰撞检测?
蛇的移动和碰撞检测,是贪吃蛇游戏里最核心也最容易出错的部分。我通常会把它们看作一个紧密相连的舞蹈:蛇每一步的移动都伴随着对周围环境的“审视”,看有没有撞到什么。
蛇的移动:
蛇的移动,从逻辑上讲,并不是让整个蛇身一起平移。它更像是一个“头部先行,身体跟随”的过程。
- 确定新头部位置: 根据当前的方向(上、下、左、右),计算出蛇头即将到达的新坐标。比如,如果当前是向右移动(
dx = 1, dy = 0),那么新头部x坐标就是当前头部x坐标加1。 - 添加新头部: 将这个新计算出的头部坐标添加到蛇身体数组的最前面。现在,蛇的数组长度暂时增加了1。
- 移除旧尾部(如果没吃到食物): 如果蛇头没有碰到食物,这意味着蛇只是单纯地向前移动了一格,所以需要从蛇身体数组的末尾移除最后一个元素,从而保持蛇的长度不变。如果蛇头碰到了食物,那么就不移除尾部,这样蛇的身体长度就自然增加了一格,模拟了“吃”和“长大”的效果。
这种处理方式非常优雅,避免了复杂的循环来移动每个蛇节,而是通过数组的unshift和pop方法巧妙地实现了蛇的移动和增长。
碰撞检测:
碰撞检测是游戏逻辑中判断“发生了什么”的关键。在贪吃蛇中,主要有三种碰撞需要处理:
- 撞墙: 这是最直接的。只需要检查新计算出的蛇头坐标是否超出了画布的边界。例如,如果蛇头的x坐标小于0(超出左边界),或者大于等于
tileCount(超出右边界),那就说明撞墙了。y坐标同理。一旦撞墙,游戏就应该结束。 - 撞自己: 这是比较有趣的一种。蛇头不能碰到自己身体的任何一部分。实现方法是,在计算出新蛇头的位置后,遍历蛇身体数组(从第二个元素开始,因为第一个元素就是当前蛇头,自己不会撞自己),检查新蛇头的坐标是否与任何一个身体节的坐标重合。如果重合了,同样游戏结束。这里有个小细节,如果蛇刚开始只有一两个节,是不会发生自撞的,所以循环从
i = 1开始很关键。 - 撞食物: 这是玩家希望发生的碰撞!同样,检查新蛇头的坐标是否与食物的坐标重合。如果重合了,那么蛇就“吃”到了食物。这时候,除了增加分数,更重要的是要执行“蛇增长”的逻辑(即上面提到的,不移除蛇尾),并且在画布上生成一个新的食物。
这些碰撞检测都需要在蛇的头部移动之后、绘制之前进行,这样才能在视觉上及时反映出游戏状态的变化,并决定游戏是否继续。
如何生成随机食物并确保其不出现在蛇身上?
生成随机食物听起来简单,但要确保它不出现在蛇身上,就多了一层考量。我通常会采用一个“先生成,再检查,不合格就重来”的策略。
随机位置生成: 首先,利用
Math.random()和Math.floor()在游戏网格的范围内生成一对随机的(x, y)坐标。因为我们的游戏是基于网格的,所以生成的坐标应该是0到tileCount - 1之间的整数。food = { x: Math.floor(Math.random() * tileCount), y: Math.floor(Math.random() * tileCount) };这确保了食物会落在画布内的某个网格单元上。
检查与蛇身重叠: 生成一个潜在的食物位置后,我们需要检查这个位置是否已经被蛇占据了。我会遍历蛇的每一个身体节(包括蛇头),比较食物的
(x, y)坐标是否与任何一个蛇节的(x, y)坐标相同。for (let i = 0; i < snake.length; i++) { if (food.x === snake[i].x && food.y === snake[i].y) { // 重叠了! } }不合格则重新生成: 如果发现新生成的食物位置与蛇身重叠,那么这个位置就是无效的。这时候,最直接的办法就是重新调用生成食物的函数。这形成了一个递归或者循环,直到找到一个完全空闲的位置为止。
function generateFood() { food = { x: Math.floor(Math.random() * tileCount), y: Math.floor(Math.random() * tileCount) }; // 确保食物不生成在蛇身上 for (let i = 0; i < snake.length; i++) { if (food.x === snake[i].x && food.y === snake[i].y) { generateFood(); // 递归调用直到找到一个空位 return; // 找到重叠后,本次生成无效,直接返回等待下一次递归 } } // 如果循环结束都没有重叠,说明这个位置是合格的 }这种递归方式在蛇比较短的时候效率很高。但理论上,如果蛇非常长,几乎占据了整个屏幕,那么找到一个空闲位置可能会需要多次尝试,甚至在极端情况下(比如屏幕全被蛇占满)会陷入无限循环。不过对于简单的贪吃蛇游戏,这种情况通常不会发生,所以这种简单直接的递归方法是完全可行的。
通过这种“生成-检查-重试”的模式,我们就能确保食物总是出现在一个对玩家来说可达且有效的空闲位置上。
今天关于《贪吃蛇游戏实现步骤详解》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
Golang跨模块调用方法解析
- 上一篇
- Golang跨模块调用方法解析
- 下一篇
- OffsetDateTime时区选择技巧解析
-
- 文章 · 前端 | 3分钟前 |
- XSS与CSRF防御指南:JavaScript安全必读
- 250浏览 收藏
-
- 文章 · 前端 | 9分钟前 |
- CSS控制数据顺序方法解析
- 415浏览 收藏
-
- 文章 · 前端 | 27分钟前 | 平滑滚动 CSS自定义 JavaScript控制 布局抖动 网页滚动条优化
- 滚动条优化技巧与实现代码
- 387浏览 收藏
-
- 文章 · 前端 | 33分钟前 |
- 悬停显示提示图标怎么实现
- 460浏览 收藏
-
- 文章 · 前端 | 33分钟前 |
- WebCryptoAPI如何保护数据安全?
- 270浏览 收藏
-
- 文章 · 前端 | 36分钟前 |
- HTML中${}变量插入4种方法解析
- 483浏览 收藏
-
- 文章 · 前端 | 42分钟前 | select标签 textarea标签 HTML表单 input标签 form标签
- HTML表单标签使用与元素详解
- 132浏览 收藏
-
- 文章 · 前端 | 42分钟前 |
- 优化移动端滚动体验,解决内容溢出与导航遮挡问题
- 273浏览 收藏
-
- 文章 · 前端 | 49分钟前 |
- 前端日志系统如何结构化JS错误信息
- 181浏览 收藏
-
- 文章 · 前端 | 54分钟前 |
- 优化JS按钮状态:事件委托实现互斥点击
- 467浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3185次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3396次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3428次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4534次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3806次使用
-
- JavaScript函数定义及示例详解
- 2025-05-11 502浏览
-
- 优化用户界面体验的秘密武器:CSS开发项目经验大揭秘
- 2023-11-03 501浏览
-
- 使用微信小程序实现图片轮播特效
- 2023-11-21 501浏览
-
- 解析sessionStorage的存储能力与限制
- 2024-01-11 501浏览
-
- 探索冒泡活动对于团队合作的推动力
- 2024-01-13 501浏览

