用三大件实现“贪吃蛇”小游戏

新的起点

这是我的第一篇博客,记载着我自学前端之后根据自己的一瞬想法而写成的‘东西’。虽然它很粗糙,但是它承载着我对前端的无限想象,是我前端之路的一个重要里程碑。
时刻回望过去,继续憧憬未来。
念念不忘,不求回响。

前言

作为一个前端小白,在b站学习完pink老师讲的动画原理之后,我脑中就有了写一个小游戏的想法。然后发现贪吃蛇好像还挺好实现的,然后就动手开始试着写了一下。

点击试玩⚡

实现过程

用 HTML 和 CSS 构建场景和物件

HTML 部分

我的做法就是让整个页面由div标签构成,场地是个div,目标物是个div,蛇头和蛇身都是由一个个的div构成。我加入了障碍物的设定,就是每过一段时间就会在地图上生成一个障碍物。所有的蛇身会被放入类名为allBody的div中,所有的障碍物会被放入类名为allBarrier的div中。
完整 HTML 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake</title>
<link rel="stylesheet" href="./snake.css">
</head>

<body>
<div class="wall">
<div class="remind"></div>
<div class="ground">
<div class="target"></div>
<div class="head"></div>
<div class="allBody"></div>
<div class="allBarrier"></div>
</div>
</div>
<script src="./snake.js"></script>
</body>

</html>

CSS 部分

墙,就是除了整个页面除去内部场地的部分,所以给墙加上绝对定位,给提示和场地加上相对定位。而其他部分在场地内,加上绝对定位。
完整 CSS 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
.wall {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: greenyellow;
}

.remind {
position: relative;
left: 0px;
top: 0px;
width: 50px;
height: 50px;
background-color: pink;
}

.ground {
position: relative;
left: 50px;
top: 0;
width: 0;
height: 0;
background-color: white;
}

.head {
position: absolute;
left: 0;
top: 0;
width: 50px;
height: 50px;
background-color: blue;
}

.body {
position: absolute;
left: 0;
top: 0;
width: 50px;
height: 50px;
background-color: aquamarine;
}

.barrier {
position: absolute;
left: 0;
top: 0;
width: 50px;
height: 50px;
background-color: gray;
}

.target {
position: absolute;
left: 0;
top: 0;
width: 50px;
height: 50px;
background-color: red;
}

用 JavaScript 控制各种功能的实现

声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//获取对应的div
let wall = document.querySelector('.wall');
let remind = document.querySelector('.remind');
let ground = document.querySelector('.ground');
let target = document.querySelector('.target');
let head = document.querySelector('.head');
let allBody = document.querySelector('.allBody');
let allBarrier = document.querySelector('.allBarrier');
//通过createElement的方式实现newBody和newBarrier的生成
let newBody = document.createElement('div');
let newBarrier = document.createElement('div');
//将所有蛇身和障碍物选取来进行操作
let body = allBody.querySelectorAll('.body');
let barrier = allBarrier.querySelectorAll('.barrier');
//头,身,目标物,障碍物的位置
let headX = randomX();
let headY = randomY();
let bodyX = head.offsetLeft;
let bodyY = head.offsetTop;
let targetX = randomX();
let targetY = randomY();
let barrierX = randomX();
let barrierY = randomY();
//用来记录数据(吃到的个数和使用的时间),通过公式得出评分
let time = 0;
let times = 0;
let score = 0;
//用来控制按下按钮,即开始游戏后才开始计时
let flag = true;
//用来控制有蛇身时,不能直接往反方向走
let turnUp = true;
let turnLeft = true;
let turnDown = true;
let turnRight = true;
let single = true;

随机生成函数得到随机值

随机获取 a~b 之间的一个整数:Math.round(Math.random() * ( b - a ) + a);
我们要生成是从场所内出现并且间隔为50的随机值

1
2
3
4
5
6
7
8
//随机函数
function randomX() {
return Math.round(Math.random() * (wall.offsetWidth / 50 - 4) + 1) * 50;
}

function randomY() {
return Math.round(Math.random() * (wall.offsetHeight / 50 - 4) + 1) * 50;
}

蛇头的生成和控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//生成函数
function setHead() {
headX = randomX();
headY = randomY();
head.style.left = headX + 'px';
head.style.top = headY + 'px';
}

//控制函数:
//当接收到键盘指令后进行go函数
function go(dir) {
turn(dir);
clearInterval(head.timer);
judge(dir);
head.timer = setInterval(function () {
judge(dir);
}, 100);
}
//判断方向来控制蛇头的方向
function judge(dir) {
body = allBody.querySelectorAll('.body');
barrier = allBarrier.querySelectorAll('.barrier');
followHead();
switch (dir) {
case 'up': head.style.top = head.offsetTop - 50 + 'px'; break;
case 'left': head.style.left = head.offsetLeft - 50 + 'px'; break;
case 'down': head.style.top = head.offsetTop + 50 + 'px'; break;
case 'right': head.style.left = head.offsetLeft + 50 + 'px'; break;
}
isGetTarget(dir);
isDead(dir);
}

障碍物的生成

在满足不会生成到蛇身、目标物和蛇头附近200px位置的情况下生成障碍物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function setBarrier() {
newBarrier = document.createElement('div');
allBarrier.appendChild(newBarrier);
newBarrier.classList.add('barrier');
barrierX = randomX();
barrierY = randomY();
for (let i = 0; i < body.length; i++) {
body = allBody.querySelectorAll('.body');
for (let j = 0; j < barrier.length; j++) {
barrier = allBarrier.querySelectorAll('.barrier');
if (barrierX === body[i].offsetLeft && barrierY === body[i].offsetTop ||
barrierX === barrier[j].offsetLeft && barrierY === barrier[j].offsetTop ||
Math.abs((barrierX - head.offsetLeft)) < 200 ||
Math.abs((barrierX - head.offsetLeft)) < 200 ||
barrierX === target.offsetLeft && barrierY === target.offsetTop) {
barrierX = randomX();
barrierY = randomY();
i = 0;
j = 0;
}
}
}
newBarrier.style.left = barrierX + 'px';
newBarrier.style.top = barrierY + 'px';
}

目标物的生成及被吃到后的重置

在满足不会生成到蛇身、障碍物位置的情况下生成目标物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function setTarget() {
targetX = randomX();
targetY = randomY();
for (let i = 0; i < body.length; i++) {
body = allBody.querySelectorAll('.body');
for (let j = 0; j < barrier.length; j++) {
barrier = allBarrier.querySelectorAll('.barrier');
if (targetX === body[i].offsetLeft && targetY === body[i].offsetTop ||
targetX === barrier[j].offsetLeft && targetY === barrier[j].offsetTop) {
targetX = randomX();
targetY = randomY();
i = 0;
j = 0;
}
}
}
target.style.left = targetX + 'px';
target.style.top = targetY + 'px';
}

function isGetTarget(dir) {
if (targetX === head.offsetLeft && targetY === head.offsetTop) {
single = false; //吃到过目标物后,拥有蛇身,之后不能瞬间反向
times++;
setTarget();
setBody(dir);
}
}

吃到目标物后蛇身的增长

吃到目标物时根据此时的方向来决定新蛇身的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function setBody(dir) {
newBody = document.createElement('div');
allBody.appendChild(newBody);
newBody.classList.add('body');
if (dir === 'left') {
newBody.style.left = head.offsetLeft + 50 + 'px';
newBody.style.top = head.offsetTop + 'px';
}
if (dir === 'right') {
newBody.style.left = head.offsetLeft - 50 + 'px';
newBody.style.top = head.offsetTop + 'px';
}
if (dir === 'up') {
newBody.style.left = head.offsetLeft + 'px';
newBody.style.top = head.offsetTop + 50 + 'px';
}
if (dir === 'down') {
newBody.style.left = head.offsetLeft + 'px';
newBody.style.top = head.offsetTop - 50 + 'px';
}
}

控制蛇身跟着蛇头走

核心代码,通过这种方式来控制蛇身的走动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function followHead() {
body = allBody.querySelectorAll('.body');
if (body.length !== 0) {
for (let i = body.length - 1; i >= 0; i--) {
if (i === 0) {
body[i].style.left = head.offsetLeft + 'px';
body[i].style.top = head.offsetTop + 'px';
}
else {
body[i].style.left = body[i - 1].offsetLeft + 'px';
body[i].style.top = body[i - 1].offsetTop + 'px';
}
}
}
}

判断是否死亡

三种死亡情况:撞墙死、撞身死和撞障碍物死

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function isDead(dir) {
//撞墙死
switch (dir) {
case 'up': if (head.offsetTop < 0) gameOver(); break;
case 'left': if (head.offsetLeft < 0) gameOver(); break;
case 'down': if (head.offsetTop > ground.offsetHeight) gameOver(); break;
case 'right': if (head.offsetLeft > ground.offsetWidth) gameOver(); break;
}
//撞身死
for (let i = 0; i < body.length; i++) {
if (body[i].offsetLeft === head.offsetLeft &&
body[i].offsetTop === head.offsetTop)
gameOver();
}
//撞障碍物死
for (let j = 0; j < barrier.length; j++) {
if (barrier[j].offsetLeft === head.offsetLeft &&
barrier[j].offsetTop === head.offsetTop)
gameOver();
}
}

死亡后的结算和重启

这是我的计分公式:score = Math.round(times * 3.5 * (1 + 2 / (time + 1)))
欢迎提出更合理的计分公式

1
2
3
4
5
6
7
8
9
10
function gameOver() {
clearInterval(head.timer);
score = Math.round(times * 3.5 * (1 + 2 / (time + 1)));
if (score > 100) score = 100;
alert('本次你坚持了' + time + '秒,吃到了' + times + '次红方块,
系统评分为' + score + '分,再来一次吧~');
setHead();
setTarget();
window.location.reload();
}

如何控制有蛇身时,不能直接往反方向走

排己思想,先将所有方向都设置为false,然后将选定方向的反方向设为false

1
2
3
4
5
6
7
8
9
10
11
12
function turn(dir) {
turnUp = true;
turnLeft = true;
turnDown = true;
turnRight = true;
switch (dir) {
case 'up': turnDown = false; break;
case 'left': turnRight = false; break;
case 'down': turnUp = false; break;
case 'right': turnLeft = false; break;
}
}

其他

document.addEventListener(‘keydown’, function (e) {…})
和remind.addEventListener(‘click’, function () {alert(‘…’);})就不贴出来啦

完整的 JavaScript 的代码可以去 我的 Github 取。

总结

对于一个初学者来说,能自己通过思考,结合之前学过的知识来完成一个小游戏,还是蛮有成就感的事情啊。如果有初学者也想自己动手写一下这个小游戏的话,可以参考我的思路来试着完成。我的代码肯定还有很多需要优化的地方,恳请大佬们提出意见和指导 QWQ


用三大件实现“贪吃蛇”小游戏
http://howerrr.github.io/2022/05/17/用三大件实现“贪吃蛇”小游戏/
作者
Hower Lin
发布于
2022年5月17日
更新于
2025年7月17日
许可协议