JavaScript贪吃蛇游戏实现详解
本文详细介绍使用JavaScript实现经典贪吃蛇游戏的完整过程。内容涵盖HTML结构搭建、CSS样式布局以及核心JS逻辑,包括键盘事件监听、边界与碰撞检测、食物随机生成及蛇身跟随算法。文中特别解析了定时器冲突、数组索引越界等常见开发陷阱及解决方案,适合前端初学者参考学习。
本文分享了一段使用JavaScript编写的贪吃蛇游戏完整代码,供读者参考学习。
在编码之前,需要明确贪吃蛇游戏应具备的核心功能模块。
首先,游戏需支持通过键盘的方向键(上、下、左、右)来控制蛇的移动方向。
其次,必须实现边界检测机制:当蛇头移动至游戏区域边界之外时,判定为游戏结束。
第三,需实现碰撞检测逻辑:当蛇头与食物块发生位置重叠时,触发特定事件。
第四,当蛇吃到食物后,游戏得分应增加1分。
以下是具体的代码实现部分。
HTML结构部分。
<div class="container">
<!--小蛇移动的操场-->
<div id="playground">
<!--小蛇-->
<div id="snake"></div>
<!--食物-->
<div id="food"></div>
</div>
<!--记录得分-->
<div id="menu">
<div>得分<span id="score"></span></div>
</div>
</div>
CSS样式部分。
<style>
* {
padding: 0;
margin: 0;
}
.container {
width: 800px;
height: 600px;
margin: 0 auto;
}
#playground {
width: 650px;
height: 100%;
background-color: cadetblue;
float: left;
position: relative;
}
#menu {
width: 150px;
height: 100%;
background-color: darkcyan;
float: left;
}
#snake {
width: 20px;
height: 20px;
background-color: #d3e0d7;
position: absolute;
left: 0;
top: 0;
}
#food {
width: 20px;
height: 20px;
background-color: #027766;
position: absolute;
}
.body {
width: 20px;
height: 20px;
background-color: #178b9a;
position: absolute;
;
top: 0;
left: 0;
}
#score {
font-size: 30px;
font-weight: bold;
color: black;
}
#menu div {
font-size: 20px;
font-weight: bold;
margin-left: 20px;
}
#hqx {
width: 100px;
height: 50px;
margin: 0 auto;
}
#inp {
border: 0;
width: 100px;
height: 50px;
background-color: coral;
}
</style>
JavaScript核心功能实现部分。
第一步是获取页面中的DOM节点,并定义所需的全局变量。
//获取节点
var snake = document.getElementById("snake");
var food = document.getElementById("food");
var playground = document.getElementById("playground");
var score = document.getElementById('score');
// var inp = document.getElementById('inp')
/*设置全局变量*/
var timer;
var arr = []; //存放蛇的位置的数组
var num = 0; //数组的长度
var snakeBody; //每次吃调一个食物,增加的身体
第二步是设置键盘事件监听,并处理边界碰撞逻辑,若发生越界则游戏结束。在实现过程中,曾遇到使用if语句配合return跳出事件会导致后续代码无法执行的问题,最终改用switch-case结构并配合break语句解决了该逻辑阻塞问题。
//设置按键事件
document.onkeydown = function (e) {
var evt = window.evnet || e;
switch (evt.keyCode) {
case 37: //左
clearInterval(timer);
timer = window.setInterval(runLeft, 10); //向左移动的时间器
function runLeft() {
if (snake.offsetLeft > 0) {
snake.style.left = snake.offsetLeft - 1 + 'px'; //实现自己动
snake.style.top = snake.offsetTop + 'px'; //高度不变
arr.push([snake.offsetLeft, snake.offsetTop]); //每移动1px,就将位置存进数组中
num++; //相应的数组长度加1
} else {
clearInterval(timer); //清除定时器
alert('you die') //弹出失败信息
document.onkeydown = null //结束按键
}
}
break; //结束当前按键
case 38: //上
clearInterval(timer);
timer = window.setInterval(runTop, 10);
function runTop() {
if (snake.offsetTop > 0) {
snake.style.top = snake.offsetTop - 1 + 'px';
snake.style.left = snake.offsetLeft + 'px';
arr.push([snake.offsetLeft, snake.offsetTop]);
num++;
} else {
clearInterval(timer);
alert('you die')
document.onkeydown = null
}
}
break;
case 39: //右
clearInterval(timer);
timer = window.setInterval(runRight, 10);
function runRight() {
if (snake.offsetLeft < 630) {
snake.style.left = snake.offsetLeft + 1 + 'px';
snake.style.top = snake.offsetTop + 'px';
arr.push([snake.offsetLeft, snake.offsetTop]);
num++;
} else {
clearInterval(timer);
alert('you die')
document.onkeydown = null
}
}
break;
case 40: //下
clearInterval(timer);
timer = window.setInterval(runBottom, 10);
function runBottom() {
if (snake.offsetTop < 580) {
snake.style.top = snake.offsetTop + 1 + 'px';
snake.style.left = snake.offsetLeft + 'px';
arr.push([snake.offsetLeft, snake.offsetTop]);
num++;
} else {
clearInterval(timer);
alert('you die')
document.onkeydown = null
}
}
break;
}
第三步是封装一个函数,用于随机生成食物的位置。在初次编写时,因遗漏了CSS单位导致样式未生效,经排查后补全了单位设置。
function pos() {
food.style.left = parseInt(Math.random() * 630) + 'px';
food.style.top = parseInt(Math.random() * 580) + 'px';
}
第四步是封装碰撞判定函数。当检测到蛇头与食物重合时,使食物消失并增加蛇身长度。此处曾遇到定时器管理问题:由于清除定时器的标识符与创建定时器的标识符不一致,导致蛇身出现上下左右抖动现象,经过多次调试修正定时器逻辑后解决了该Bug。
var timer1 = setInterval(eat, 10); //设置一个碰撞的时间器
function eat() {
snakeCrashFood(snake, food); //传入参数
function snakeCrashFood(obj1, obj2) {
var obj1Left = obj1.offsetLeft;
var obj1Width = obj1.offsetWidth + obj1.offsetLeft;
var obj1Top = obj1.offsetTop;
var obj1Height = obj1.offsetHeight + obj1.offsetTop;
var obj2Left = obj2.offsetLeft;
var obj2Width = obj2.offsetWidth + obj2.offsetLeft;
var obj2Top = obj2.offsetTop;
var obj2Height = obj2.offsetHeight + obj2.offsetTop;
if (!((obj1Width < obj2Left) || (obj2Width < obj1Left) || (obj1Height < obj2Top) || (
obj2Height < obj1Top))) {
snakeBody = document.createElement("div"); //生成新的div
snakeBody.setAttribute("class", "body"); //给新的div添加类名
playground.appendChild(snakeBody); //添加一节新的身体
pos(); //让食物重新随机出现
setInterval(follow, 10); //动态地改变新的身体的位置
}
}
}
第五步是实现蛇身跟随逻辑。通过获取蛇身数组,计算新身体段相对于上一身体段的位置(偏移量为20)。初期因初始num值为0且place为20,导致数组索引从负数开始从而引发越界错误。在导师指导下,增加了索引有效性判定逻辑,成功解决了该问题。
function follow() {
/*获得增加的身体的数组*/
var bodyNum = document.getElementsByClassName("body");
score.innerHTML = bodyNum.length;
var place = 0;
/*数组每移动1px,新的身体的位置就是相对于前一个身体的第20个数组的位置,后面依次加等*/
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1")
// console.log("arr : ==" + arr)
// console.log("num : ==" + num) //2
for (var i = 0; i < bodyNum.length; i++) {
// console.log("bodyNum.length : ==" + bodyNum.length)
place += 20;
// console.log("place : ==" + place)//20
// console.log("num - place : ==" + (num - place))//-18
// bodyNum[i].style.left = arr[num - place][0] + 'px';
// bodyNum[i].style.top = arr[num - place][1] + 'px';
if (num - place > 0) {
bodyNum[i].style.left = arr[num - place][0] + 'px';
bodyNum[i].style.top = arr[num - place][1] + 'px';
}
}
// console.log("num555 : ==" + num)
// console.log("===========================================")
}
以上就是本文的全部内容,希望对读者的学习有所帮助,也欢迎大家继续支持本站。
JS怎么实现页面强制刷新
2026-11-05 14:53:38
点击JS链接直接运行不下载?前端实现.js文件强制下载的三种方法
2026-11-05 12:12:23
怎么封装一个通用的JS创建元素辅助函数
2026-11-05 11:41:41
Swiper.js 怎么监听滑动结束并获取当前索引
2026-11-05 10:57:15
HTML、CSS和JS的关系:Web前端三件套怎么协作
2026-11-04 15:41:40
