如何用原生JS,快速写一个贪吃蛇小游戏。

语言: CN / TW / HK

前言

贪吃蛇算是小游戏里面比较好写的,没有什么难点,基本上需要实现的功能,都能很顺利的用代码敲出来。

1655299027951.gif

1.绘制游戏区域和游戏元素

仍然是用16*16的二维数组来绘制,对这个数组进行遍历。第一层遍历的时候创建tr,第二层遍历的时候创建td。然后添加一些CSS样式,游戏区域就写好了。

let arr = [ [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], ] //渲染游戏区域 const renderTable = () => { document.querySelector('table').innerHTML = '' arr.forEach(item => { //第一层遍历创建tr let tr = document.createElement('tr') item.forEach(item2 => { //第二层遍历创建td let td = document.createElement('td') tr.appendChild(td) }) document.querySelector('table').appendChild(tr) }) } renderTable() CSS&HTML

```

键盘上下左右控制,Enter键暂停

得分
0

``` 游戏元素的话一共有3个,蛇头,身体和苹果。就用3个构造函数来生成坐标,以及给对应坐标的那个对象里面添加不同的属性。用构造函数写既方便查找,也方便修改。然后写个渲染函数,格子里面对应的不同的属性,就渲染出不同的样式。

//渲染样式的函数 const renderColor = () => { arr.forEach((item, index) => { const trArr = document.querySelectorAll('tr') item.forEach((item2, index2) => { //头部渲染 if (item2.head === 1) { trArr[index].querySelectorAll('td')[index2].classList.add('bgc3') } //身体渲染成黑色 else if (item2.body === 1) { trArr[index].querySelectorAll('td')[index2].classList.remove('bgc3') trArr[index].querySelectorAll('td')[index2].classList.remove('bgc2') trArr[index].querySelectorAll('td')[index2].classList.add('bgc1') } //苹果渲染 else if (item2.apple === 1) { trArr[index].querySelectorAll('td')[index2].classList.add('bgc2') } //如果是其他的值,就把这个样式清空 else { trArr[index].querySelectorAll('td')[index2].className = '' } }) }) } //蛇头构造函数 function Head(x, y) { this.x = x this.y = y this.render = function (a) { arr[this.y][this.x].head = a } } //身体构造函数 function Body(x, y) { this.x = x this.y = y this.render = function (a) { arr[this.y][this.x].body = a } } //苹果构造函数 function Apple(x, y) { this.x = x this.y = y this.render = function (a) { arr[this.y][this.x].apple = a } } //依次渲染默认出现的头部,身体和苹果 let a = new Head(10, 10) a.render(1) //声明一个存放身体元素的数组,移动以及判断获胜都需要用到这个数组的功能 let bodyArr = [] let b = new Body(10, 11) bodyArr.push(b) b.render(1) let c = new Apple(5, 5) c.render(1) renderColor()

Snipaste_2022-06-15_20-59-49.jpg 图片是我在网上随便找的

2.移动功能

移动功能要分两个步骤来写。一个是蛇头的移动,一个是身体的移动。贪吃蛇的身体它不是一个整理,是不能写成一块的。蛇头动的时候,身体它得扭来扭去,这才像个蛇。

蛇头移动很简单,上下左右键对应着蛇头X和Y两个值的加减。X和Y超出范围,代码就会报错。就可以直接用try catch来判断边界。报错了就说明出界了,直接走catch的游戏结束。

02jpg.jpg

这个游戏唯一麻烦一点的地方来了,怎么让蛇身体能扭起来。相通一个逻辑,这个问题就迎刃而解了。

蛇身体怎么移动,是身体里的每个元素都往前移动一格吗,显然不是。仔细观察你会发现,蛇移动时,身体的中间部分其实是不动的。动的只有最前端和最末端的两格。也就是说蛇移动时,其实就是把身体最末端的格子移动到了身体最前端,其他的都不需要动。前面声明的身体元素数组就是这个时候用的。把身体的最后一个元素移动到头部,同时数组里的最后一个元素也要移动到最前面去。

03.jpg

//上下左右移动函数 const up = () => { //用try catch来判断是否出界 try { //把移动的函数写在try里面,如果出界了就会报错,然后走catch里的代码 //移动的时候先清除当前格子的样式 a.render(0) a.y -= 1 //然后渲染新样式 a.render(1) } catch { clearInterval(timer) alert('游戏结束!') location.reload() } //调用吃苹果函数 eat() //让数组中的最后一个元素,移动到头部刚才所在的位置 bodyArr[bodyArr.length - 1].render(0) //这个a.x,a.y+1就是头部移动前的坐标 bodyArr[bodyArr.length - 1].x = a.x bodyArr[bodyArr.length - 1].y = a.y + 1 bodyArr[bodyArr.length - 1].render(1) //把身体数组里的最后一个元素移到最开头 bodyArr.unshift(bodyArr.pop()) renderColor() //调用判断游戏结束函数 end() } const down = () => { try { a.render(0) a.y += 1 a.render(1) } catch { clearInterval(timer) alert('游戏结束!') location.reload() } eat() //让数组中的最后一个元素,移动到头部刚才所在的位置 bodyArr[bodyArr.length - 1].render(0) bodyArr[bodyArr.length - 1].x = a.x bodyArr[bodyArr.length - 1].y = a.y - 1 bodyArr[bodyArr.length - 1].render(1) bodyArr.unshift(bodyArr.pop()) renderColor() end() } const right = () => { try { a.render(0) a.x += 1 a.render(1) } catch { clearInterval(timer) alert('游戏结束!') location.reload() } eat() //让数组中的最后一个元素,移动到头部刚才所在的位置 bodyArr[bodyArr.length - 1].render(0) bodyArr[bodyArr.length - 1].x = a.x - 1 bodyArr[bodyArr.length - 1].y = a.y bodyArr[bodyArr.length - 1].render(1) bodyArr.unshift(bodyArr.pop()) renderColor() end() } const left = () => { try { a.render(0) a.x -= 1 a.render(1) } catch { clearInterval(timer) alert('游戏结束!') location.reload() } eat() //让数组中的最后一个元素,移动到头部刚才所在的位置 bodyArr[bodyArr.length - 1].render(0) bodyArr[bodyArr.length - 1].x = a.x + 1 bodyArr[bodyArr.length - 1].y = a.y bodyArr[bodyArr.length - 1].render(1) bodyArr.unshift(bodyArr.pop()) renderColor() end() }

3.写键盘事件

写键盘事件的时候要加一个判断,让这个蛇只能够相对它自身左右移动。不能掉头,也不能向前冲,向前冲很容易就撞到墙了。

let num = 1 document.addEventListener('keydown', function (e) { //flag是暂停功能的变量 if (flag) { if (e.key === 'ArrowDown') { //蛇头只能够向左或者向右移动,否则冲太快容易死。也不可以调头。 if (num == 2 || num == 4) { down() num = 3 } } else if (e.key === 'ArrowRight') { if (num == 1 || num == 3) { right() num = 2 } } else if (e.key === 'ArrowLeft') { if (num == 1 || num == 3) { left() num = 4 } } else if (e.key === 'ArrowUp') { if (num == 2 || num == 4) { up() num = 1 } } } })

4.吃苹果功能

吃苹果功能分为3个步骤

1.判断头部和苹果有没有重合,重合的话,就让这个苹果消失,让分数+1。

2.生成随机坐标来渲染苹果,判断一下这个坐标上是否与蛇身体重合了,重合的话就要重新生成坐标。

3.生成一个新的身体实例,并且把这个新实例添加到身体数组中。

//得分 let fen = 0 //吃苹果 const eat = () => { //如果头部和苹果重合了 if (arr[a.y][a.x].apple == 1) { //清除这个苹果 arr[a.y][a.x].apple = 0 //分数加1 fen++ //调用判断游戏胜利函数 win() //渲染分数 document.querySelector('.defen span').innerHTML = fen //用循环来生成新苹果,满足条件就退出循环 while (true) { const x = Math.floor(Math.random() * 16) const y = Math.floor(Math.random() * 16) //判断苹果不能出现在身体上 if (!arr[y][x].head && !arr[y][x].body) { arr[y][x].apple = 1 break } } //生成新的身体实例 let b = new Body(bodyArr[bodyArr.length - 1].x, bodyArr[bodyArr.length - 1].y) b.render(1) bodyArr.push(b) } }

5.头部碰到身体游戏失败的功能

和吃苹果的逻辑一样,就判断头部和身体是不是重合的。

//碰到身体游戏失败判定 const end = () => { //如果头部和身体重合了 if (arr[a.y][a.x].body == 1) { clearInterval(timer) alert('游戏结束!') location.reload(true) } }

6.自动移动的功能

自动移动可以通过间歇函数来实现,然后不能单纯的在间歇函数的回调里面写上下左右的某一个,要判断一下蛇当前的移动方向是什么。然后来一个分数越高速度越快的功能。

//自动向前移动功能 let timer //封装一个向前移动的函数 function move() { if (num == 1) { up() } else if (num == 2) { right() } else if (num == 3) { down() } else { left() } } //写自动移动的间歇函数 time() function time() { //来个分越高速度越快的功能 if (fen <= 20) { timer = setInterval(function () { move() }, 250) } else if (fen > 20 && fen <= 40) { clearInterval(timer) timer = setInterval(function () { move() }, 200) } else { clearInterval(timer) timer = setInterval(function () { move() }, 150) } }

7.暂停功能和判断游戏胜利

这两个比较简单,就一起说了。暂停功能就是让定时器停止,并且让flag变量变为false。这样就不能再去控制蛇了。这个游戏一共有256个格子,胜利的条件就是身体有255个元素,因为要减去一个头部。

//暂停功能 let flag = true document.addEventListener('keydown', function (e) { if (flag) { if (e.key === 'Enter') { clearInterval(timer) flag = !flag } } else { if (e.key === 'Enter') { time() flag = true } } }) //胜利条件,身体数组的元素等于255个就获胜 const win = () => { if (bodyArr.length == 255) { clearInterval(timer) alert('游戏胜利!') location.reload() } }

写在最后

游戏到这里就写完了,代码虽然看起来多,但是并没有什么难的地方,想到就能写。唯一麻烦一点的就是那个身体的移动,相通了就很简单了。码字不易,希望能留下你的赞。

04.jpg

05.jpg