前言
贪吃蛇算是小游戏里面比较好写的,没有什么难点,基本上需要实现的功能,都能很顺利的用代码敲出来。
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:
<style>
table {
margin: 0px auto;
border-collapse: collapse;
border: 5px solid black;
}
td {
border-radius: 50%;
width: 40px;
height: 40px;
border: none
}
.bgc1 {
background-color: black;
}
.bgc2 {
background: url(./pngsucai_512252_f77802.png) no-repeat;
background-size: 100%;
z-index: 1;
}
.bgc3 {
background: url(./92034446ddc7a91edec038f2d69415fd.jpeg)no-repeat;
background-size: 100%;
z-index: 9;
}
.defen {
font-size: 30px;
position: absolute;
top: 40%;
right: 20px;
}
</style>
<body>
<table></table>
<div class="defen">
键盘上下左右控制,Enter键暂停
<br>
<br>
得分
<br>
<span>0</span>
</div>
<script src="./贪吃蛇2.0.js"></script>
</body>
游戏元素的话一共有 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()
2、移动功能
移动功能要分两个步骤来写。一个是蛇头的移动,一个是身体的移动。贪吃蛇的身体它不是一个整理,是不能写成一块的。蛇头动的时候,身体它得扭来扭去,这才像个蛇。
蛇头移动很简单,上下左右键对应着蛇头 X 和 Y 两个值的加减。X 和 Y 超出范围,代码就会报错。就可以直接用 try catch 来判断边界。报错了就说明出界了,直接走 catch 的游戏结束。
注意: 这个游戏唯一麻烦一点的地方来了,怎么让蛇身体能扭起来。相通一个逻辑,这个问题就迎刃而解了。
蛇身体怎么移动,是身体里的每个元素都往前移动一格吗,显然不是。仔细观察你会发现,蛇移动时,身体的中间部分其实是不动的。动的只有最前端和最末端的两格。也就是说蛇移动时,其实就是把身体最末端的格子移动到了身体最前端,其他的都不需要动。前面声明的身体元素数组就是这个时候用的。把身体的最后一个元素移动到头部,同时数组里的最后一个元素也要移动到最前面去。
//上下左右移动函数
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()
}
}
写在最后
游戏到这里就写完了,代码虽然看起来多,但是并没有什么难的地方,想到就能写。唯一麻烦一点的就是那个身体的移动,相通了就很简单了。