目录
前言
虽然在写这个项目很多东西都没有学过,但是根据这个视频的内容我还是跟着把贪吃蛇项目写了出来:web前端实战项目系列,JavaScript面相对象开发贪吃蛇(全套教程),并且一步步搞懂内部的东西。同时感谢我们的大佬的文章:JavaScript贪吃蛇给我带来了很多的帮助。贪吃蛇项目的制作我觉得更多是需要懂得制作的思路,接下来让我们寻找其中的重点,解析这个过程。
一、效果展示
首先让我们看看实际上的效果是什么样子的:贪吃蛇小游戏
这里有一个参考网站给大家看。我这里上一份我制作的贪吃蛇样子:
我们可以看到,有这么几个重大的功能:
- 点击开始游戏
- 蛇会出现,而且按照指定方向移动
- 吃到食物之后会增长,同时食物会随机出现
- 撞到墙壁会死亡,吃到自己身体会死亡
(没录进动图里面,但是有这个效果的!)- 可以暂停游戏
- 速度会越来越快
- 结束之后会刷新页面
接下来就让我们来分步骤来解说这个贪吃蛇该怎么制作。
二、制作结构
贪吃蛇可以用很多中方式移动,我们可以使用定位的方式,每次需要移动的时候,就用定位移动一段距离。又或者像我学习视频这样,用表格来制作,制作一个大的表格存储数据。首先我们需要创造一个Game.js作为一个中介者,执行其他的js。之后,我们开始字啊Game.js里面写代码。
我们首先需要准备一个可以初始化数据的函数:
function Game() {}
把我们的数据写进如,比如this.col =15,this.row=15 ,创建15列,15行这样的方式。之后如果要写需要使用的一些变量作为开关,或者其他数据的初始化,也需要写入这个Game初始化代码里面。
之后我们需要把表格添加到我们的主页面中的盒子里面:
<div id="app" class="app"></div>
那么我们需要用什么方式添加呢?这里需要提到一个过去的案例,以前做过一个倒三角,正三角的星星排列。那么我们放入表格需要这样的顺序:
- 创建了table
- 插入tr
- 在tr里面插入td
这个本质和我们插入星星的方式是一样的,也就是在第一行的时候我需要开始插入每一颗星星,第一行插入结束之后,才会进入下一行,这和tr里面插入td本质是一样的。我们来看看插入星星的时候用的代码:
var str = '';
var num1 = prompt('需要打印几行:');
var num2 = prompt('需要打印几列:');
for (var i = 1; i <= num1; i++) {
for (var j = 1; j <= num2; j++) {
str = str + '★';
}
str = str + '\n';
}
console.log(str);
我们可以看到,用两个for循环去书写,嵌套,只需要在外面的循环插入tr,里面的循环插入td,按照这样的思路,我们的函数就能构写出来:
Game.prototype.init = function() {
this.dom = document.createElement("table");
var tr, td;
//遍历行和列上树
for (var i = 0; i < this.row; i++) {
//遍历行,创建结点上树
tr = document.createElement('tr');
for (var j = 0; j < this.col; j++) {
//遍历列,创建结点上树
td = document.createElement('td');
//追加到tr
tr.appendChild(td);
}
//追加节点上树
this.dom.appendChild(tr);
}
//表格上树
document.getElementById('app').appendChild(this.dom);
}
这里需要注意的是:添加的tr需要td在全部添加到tr里面之后,再去插入,否则tr直接插入,td就没有被插入到tr里面了。最后我们把整个表格添加进去就可以获得这个结构了。可以给这个结构添加CSS样式查看它的样子。
记得要在主页面去调用这个js:
<script>
var game = new Game();
</script>
三、蛇的创造
在js的文件里面,我们再创建一个Snake.js,用来书写蛇的js代码。
我们首先创建一个蛇的对象的数组,在里面塞入我们想要的它出现的位置:
function Snake() {
// 蛇的初始化身体
this.body = [
{ "row": 3, "col": 5 },
{ "row": 3, "col": 4 },
{ "row": 3, "col": 3 },
{ "row": 3, "col": 2 }
];
}
之后,我们给蛇添加它的头和它的身体颜色。
回到Game.js,书写一个设置颜色,设置头部的代码
// 设置颜色的方法
Game.prototype.setColor = function(row, col, color) {
// 让表格的第几行第几列设置什么颜色
this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.background = color;
};
Game.prototype.setHead = function(row, col) {
var img = document.createElement('img');
img.src = 'img/snake.png';
img.classList.add('head');
this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].appendChild(img);
}
之后我们需要去渲染这个蛇的内容,需要在Snake.js里面写入:
Snake.prototype.render = function() {
// 蛇的渲染
game.setHead(this.body[0].row, this.body[0].col);
for (var i = 1; i < this.body.length; i++) {
game.setColor(this.body[i].row, this.body[i].col, '#9ccc65');
}
}
this.body[0].row, this.body[0].col是蛇的头部的位置,剩下的setcol就是根据我们的对象里写入的数组内容依次遍历,在后面染上对应长度的颜色。
这样,射的身体和头部的代码就准备完毕了,之后就是重要的运动内容。
四、运动的蛇
我们制作运动的蛇,原理是让蛇头运动起来,给td添加样式来显示蛇的样子,之后我们需要在蛇运动之前清除样式,然后改变蛇在表格中的位置,重新再一次渲染这个蛇的样子,来实现每隔一段时间时间,蛇就会朝着一个方向运动的能力。
1.运动
制作运动的蛇,首先我们需要让他动起来,那么我们就写一个代码,让他的头部可以按照一个方向移动。我们写一个switch的代码来选择方向运动:
Snake.prototype.update = function() {
switch (this.direction) {
case 'R':
// 向右
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col + 1 });
break;
}
}
但是这个时候蛇还是停止的,不会动,那么需要做什么让他动起来呢?我们用定时器,把代码放入定时器中运行:
Game.prototype.start = function() {
var self = this;
self.timer = setInterval(function() {
// 蛇的渲染
game.snake.update();
// 渲染蛇
game.snake.render();
}, 500);
}
我们这样设置肯定是不对的,因为我们还缺少一些擦除样式的代码:
Game.prototype.clear = function() {
//遍历表格,擦除画布
for (var i = 0; i < this.row; i++) {
for (var j = 0; j < this.col; j++) {
this.dom.getElementsByTagName('tr')[i].getElementsByTagName('td')[j].style.background = '';
this.dom.getElementsByTagName('tr')[i].getElementsByTagName('td')[j].innerHTML = '';
}
}
};
用这样的函数,每次代码执行的时候,先把表格擦除样式,然后再加入蛇的样式,重复这样下来,就可以制作出蛇的样子。但是光是这样还不够,因为我们的蛇会拖着长长的尾巴。这是因为我们的蛇头移动之后,长度会增加一节,二这个动作会导致对象内的数组越装越多。所以我们需要在蛇移动的时候,删除末尾的一节,保持这个蛇身体的长度,那么蛇移动的代码的完整样子就是:
Snake.prototype.update = function() {
switch (this.direction) {
case 'R':
// 向右
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col + 1 });
break;
}
this.body.pop();
}
这样就可以实现朝着一个方向运动的蛇了。来看看完整的代码:
Snake.js:
Snake.prototype.update = function() {
switch (this.direction) {
case 'R':
// 向右
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col + 1 });
break;
case 'D':
// 向下
this.body.unshift({ "row": this.body[0].row + 1, "col": this.body[0].col });
break;
case 'L':
// 向左
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col - 1 });
break;
case 'U':
// 向上
this.body.unshift({ "row": this.body[0].row - 1, "col": this.body[0].col });
break;
}
this.body.pop();
};
Snake.prototype.render = function() {
// 蛇的渲染
game.setHead(this.body[0].row, this.body[0].col);
for (var i = 1; i < this.body.length; i++) {
game.setColor(this.body[i].row, this.body[i].col, '#9ccc65');
}
}
定时器:
Game.prototype.start = function() {
var self = this;
self.timer = setInterval(function() {
// 清楚屏幕
game.clear();
// 蛇的渲染
game.snake.update();
// 渲染蛇
game.snake.render();
}, 500);
}
2.方向移动
之后,我们需要用键盘的数值来控制蛇的方向,这个时候我们需要注意三个点:
- 蛇头要跟着转动
- 蛇要跟着键盘按键改变方向
- 蛇头不会往回走
首先我们写下按键之后改变方向的代码:
//设置键盘监听事件
Game.prototype.bindEvent = function() {
var self = this;
// 键盘事件
document.onkeydown = function(event) {
switch (event.keyCode) {
case 37:
// 按下←键
self.snake.direction("L");
break;
case 38:
//按下↑键
self.snake.direction("U");
break;
case 39:
//按下→键
self.snake.direction("R");
break;
case 40:
// 按下↓键
self.snake.direction("D");
break;
}
}
}
利用时间委托,当我们按下按键的时候,判断按键的keycode数值,然后做出改变数值的效果。然后我们需要加上判断,当我们为左运动,不能按右键这样的逻辑,添加我们的判断条件:
//设置键盘监听事件
Game.prototype.bindEvent = function() {
var self = this;
// 键盘事件
document.onkeydown = function(event) {
switch (event.keyCode) {
case 37:
// 按下←键
// 先进行判断,如果当前的方向是向右移动,此事我们不能按左键
if (self.snake.direction == 'R') {
return;
}
self.snake.direction("L");
break;
case 38:
//按下↑键
// 先进行判断,如果当前的方向是向下移动,此事我们不能按上键
if (self.snake.direction == 'D') {
return;
}
self.snake.direction("U");
break;
case 39:
//按下→键
// 先进行判断,如果当前的方向是向左移动,此事我们不能按右键
if (self.snake.direction == 'L') {
return;
}
self.snake.direction("R");
break;
case 40:
// 按下↓键
// 先进行判断,如果当前的方向是向上移动,此事我们不能按下键
if (self.snake.direction == 'U') {
return;
}
self.snake.direction("D");
break;
}
}
}
这样我们确实防止了回头的情况,但是按键不是只能按一个,如果我们在更新之前,判定了正确的方向,那么这个时候我们再按回头的方向键,机会导致出现回头的情况,我们必须去避免这种bug。这里有一个方法,就是把正确方向的数据存起来,这个时候我们的蛇还是原来的方向运动,直到更新的时候,才把正确的数值传给蛇。
那么我们就需要写一个传入数据的函数:
// 蛇的方向改变,防止的是在一次渲染之前会出现调头的情况
Snake.prototype.changeDirection = function(d) {
this.willDirection = d;
}
然后添加初始化的方向和初始化的正确数值:
function Snake() {
// 蛇的初始化身体
this.body = [
{ "row": 3, "col": 5 },
{ "row": 3, "col": 4 },
{ "row": 3, "col": 3 },
{ "row": 3, "col": 2 }
];
// 信息量,设置的运动方向
this.direction = 'R';
// 即将改变的方向,目的就是为了防止原地调头的情况
this.willDirection = 'R';
}
之后给方向键代码做一些改变:
//设置键盘监听事件
Game.prototype.bindEvent = function() {
var self = this;
// 键盘事件
document.onkeydown = function(event) {
switch (event.keyCode) {
case 37:
// 按下←键
// 先进行判断,如果当前的方向是向右移动,此事我们不能按左键
if (self.snake.direction == 'R') {
return;
}
self.snake.changeDirection("L");
self.d = 'L';
break;
case 38:
//按下↑键
// 先进行判断,如果当前的方向是向下移动,此事我们不能按上键
if (self.snake.direction == 'D') {
return;
}
self.snake.changeDirection("U");
self.d = 'U';
break;
case 39:
//按下→键
// 先进行判断,如果当前的方向是向左移动,此事我们不能按右键
if (self.snake.direction == 'L') {
return;
}
self.snake.changeDirection("R");
self.d = 'R';
break;
case 40:
// 按下↓键
// 先进行判断,如果当前的方向是向上移动,此事我们不能按下键
if (self.snake.direction == 'U') {
return;
}
self.snake.changeDirection("D");
self.d = 'D';
break;
}
}
}
最后,我们给蛇的方向运动添加添加正确数值的操作:
Snake.prototype.update = function() {
// 让当前的direction接受一下willDirection
this.direction = this.willDirection;
switch (this.direction) {
case 'R':
// 向右
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col + 1 });
break;
case 'D':
// 向下
this.body.unshift({ "row": this.body[0].row + 1, "col": this.body[0].col });
break;
case 'L':
// 向左
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col - 1 });
break;
case 'U':
// 向上
this.body.unshift({ "row": this.body[0].row - 1, "col": this.body[0].col });
break;
}
this.body.pop();
};
运动的蛇的代码就完成了。之后在设置蛇头的代码里面添加你需要的转头操作:
switch (this.d) {
case 'R':
//初始方向
break;
case 'D':
img.style.transform = 'rotate(90deg)';
break;
case 'L':
img.style.transform = 'rotate(180deg)';
break;
case 'U':
img.style.transform = 'rotate(-90deg)';
break;
}
记得在Game函数里面添加初始化的方向。
五、吃下食物,越来越长
我们写一个food的js,书写一个随机生成的食物:
this.row = parseInt(Math.random() * gameSnake.row);
this.col = parseInt(Math.random() * gameSnake.col);
然后渲染出去:
Food.prototype.render = function() {
game.setHTML(this.row, this.col);
}
我们创造的食物,当他和蛇头接触的时候,我们需要做出判断:和蛇头完全重合的时候,就需要清空食物所在的位置,然后重新生成食物。
让我们看看怎么书写的代码:
function Food(gameSnake) {
// 备份this
var self = this;
//食物的位置
// 下面的do-while循环语句作用是先创建一个row和col然后判断这个数值在不在蛇身上
do {
this.row = parseInt(Math.random() * gameSnake.row);
this.col = parseInt(Math.random() * gameSnake.col);
} while ((function() {
// 遍历蛇的row和col然后和food新随机出来的row和col判断是否重合
for (var i = 0; i < gameSnake.snake.body.length; i++) {
if (gameSnake.snake.body[i].row == self.row && gameSnake.snake.body[i].col == self.col) {
return true;
}
}
return false;
})());
}
写完蛇的食物代码,我们再看看蛇应该如何增长身体。
我们知道,我们的蛇实际上是在不断增长的,为了保持他的身体长度,所以需要删除一节来保持长度。那么其实原理就很好理解了,我们只需要在蛇判定吃到食物的时候,先不删除最后一节,直到下一次的时候继续删除最后一节的内容,我们的蛇就会保持增长一节的身体继续行动,也就是增长身体的过程了。接下来让我们看看代码是怎么实现的:
// 蛇吃食物
// 判断如果当前的蛇的头部没有喝食物进行重合,就代表此时没有迟到食物,此时就进行尾巴删除,如果重合吃到了,不进行尾部删除
if (this.body[0].row == game.food.row && this.body[0].col == game.food.col) {
//创建新的食物
// 此时头部增加了,尾巴没有删除
game.food = new Food(game);
} else {
this.body.pop();
}
这段代码记得要放进update的函数里面。
六、死亡判定
蛇死亡的条件是:
- 碰撞到墙壁
- 咬到自己的身体
针对这两点,我们来构写代码:
判断蛇的头如果位置超过了我们设置的表格,就结束游戏,同时停止我们的定时器:
// 死亡的判断——撞击墙壁
if (this.body[0].col > game.col - 1 || this.body[0].row > game.row - 1 || this.body[0].col < 0 || this.body[0].row < 0) {
alert('游戏结束!'); //删除是因为当前的头赠是不合法的,因为游戏结束了
this.body.shift();
clearInterval(game.timer);
location.reload();
}
这里我们可以看到使用了shift的方法,因为当头超过表格之后,系统就会报错说你的头部已经不在表格里,找不到这个对象,所以需要把头部超出去的部分删除。同时我们用location.reload的方法刷新页面,就可以结束这个游戏了。
之后我们再去循环遍历蛇的身体,如果说头部和蛇身体的部位发生重合,那么也用一样的方式判定死亡:
// 死亡判断——咬到自己
for (var i = 1; i < this.body.length; i++) {
// 如果能够判断死亡,也就是当前的蛇的头部和身体的某个部分row和col完全重合的时候
if (this.body[0].col == this.body[i].col && this.body[0].row == this.body[i].row) {
alert('游戏结束!'); //删除是因为当前的头赠是不合法的,因为游戏结束了
this.body.shift();
clearInterval(game.timer);
location.reload();
}
}
这样我们的死亡判定也一起写好了。同样,这些过程都是update更新的时候就需要做的判定,所以都要写在update的函数里面。
七、越吃越快
蛇每次吃东西的时候速度都会越来越快,我们需要怎么做到这个过程呢?
- 首先我们给定时器设置一个非常快的帧数,让他会不停的刷新。在这个过程中,我们建立一个变量f,让他进行一个快速的自增加的算法。
- 这个时候我们知道,我们蛇的身体是会不断增加的,那么我们就设置一个变量,计算这个差值,当我们的f % 变量 == 0的时候,我们才执行和这个update函数。
什么意思呢?就是我们通过控制update函数的更新速度,来达到可以控制蛇速度的方法。假如我们的蛇开始是3,我们让30 - 3 = 27,那么不断变化的f只有 % 27 == 0的时候,才会执行,也就是每次在f为27的1的倍数,2的倍数,3的倍数的时候,才会去执行update。而吃到食物之后,长度为4,那么每次为26的1的倍数,2的倍数,3的倍数的时候,才会去执行update。这样就成功做到了加速蛇的速度的功能。让我们来看看是怎么书写的代码:
Game.prototype.start = function() {
// 帧编号
var self = this;
this.f = 0;
self.timer = setInterval(function() {
// 定时器里面的核心就是游戏渲染的本质,清楚屏幕,更新,渲染。
game.f++;
// 清楚屏幕
game.clear();
//蛇的更新速度,当蛇边长的时候,速度要加快
var during = game.snake.body.length < 30 ? 30 - game.snake.body.length : 1;
// 蛇的渲染
game.f % during == 0 && game.snake.update();
// 渲染蛇
game.snake.render();
// 渲染食物
game.food.render();
}, 20);
}
八、结尾
最后,我们来处理一下其他的细节:
首先我们开始界面是一个动图制作的按钮,然后点击之后,才会开始游戏,其实很简单,只需要把定时器装进按钮的点击事件去执行就可以了。
按钮的html和CSS代码:
<style>
.style {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0);
z-index: 1;
text-align: center;
}
.start {
display: block;
z-index: 9999;
}
.stop {
display: none;
}
button {
width: 200px;
height: 100px;
background: url('img/btn1.gif') no-repeat center -50px;
background-size: cover;
z-index: 999;
border: transparent;
margin-top: 50%;
transform: translateY(-50%);
background-color: transparent;
cursor: pointer;
z-index: 9999;
}
</style>
<body>
<h3 id="f" class="f">帧编号:0</h3>
<h3 id="score" class="score">分数:0</h3>
<div class="box">
<div class="start style">
<button></button>
</div>
<div class="style on">
<img src="img/btn4.png" alt="" class="pause">
</div>
<div id="app" class="app"></div>
</div>
<script src="js/Game.js"></script>
<script src="js/Snake.js"></script>
<script src="js/Food.js"></script>
</body>
<script>
var game = new Game();
</script>
同时里面我们看到有一个on的盒子,就是我设置的暂停按钮的盒子。这里把剩下的css代码补上:
.pause {
display: none;
position: absolute;
width: 70px;
height: 70px;
top: 50%;
left: 50%;
margin-top: -35px;
margin-left: -35px;
}
暂停按钮的内容,这里需要注意的重点就是,需要用节流阀。节流阀的变量初始化记得写到Game的函数里面。
接下来把完整的代码展示给大家,感谢大家的收看。
HTML:
<style>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
h3 {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 50px;
}
.score {
top: 20px;
}
.box {
width: 625px;
height: 625px;
margin: 20px auto;
position: relative;
}
.app {
position: relative;
background: #FCE4EC;
border: 20px solid #F8BBD0;
}
table {
border-collapse: collapse;
box-sizing: border-box;
}
td {
position: relative;
width: 25px;
height: 25px;
text-align: center;
background-color: #fce4ec;
border-radius: 15px;
background-size: 100% 100%;
}
.style {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0);
z-index: 1;
text-align: center;
}
.start {
display: block;
z-index: 9999;
}
.stop {
display: none;
}
button {
width: 200px;
height: 100px;
background: url('img/btn1.gif') no-repeat center -50px;
background-size: cover;
z-index: 999;
border: transparent;
margin-top: 50%;
transform: translateY(-50%);
background-color: transparent;
cursor: pointer;
z-index: 9999;
}
.head {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.pause {
display: none;
position: absolute;
width: 70px;
height: 70px;
top: 50%;
left: 50%;
margin-top: -35px;
margin-left: -35px;
}
</style>
</head>
<body>
<h3 id="f" class="f">帧编号:0</h3>
<h3 id="score" class="score">分数:0</h3>
<div class="box">
<div class="start style">
<button></button>
</div>
<div class="style on">
<img src="img/btn4.png" alt="" class="pause">
</div>
<div id="app" class="app"></div>
</div>
<script src="js/Game.js"></script>
<script src="js/Snake.js"></script>
<script src="js/Food.js"></script>
</body>
<script>
var game = new Game();
</script>
Game.js:
var box = document.querySelector('.style');
var btn = box.querySelector('button');
var on = document.querySelector('.on');
var pause = on.querySelector('.pause');
function Game() {
//行数
this.row = 23;
//列数
this.col = 25;
//初始化分数
this.score = 0;
// 初始化结点
this.init();
// 实例化蛇类
this.snake = new Snake();
//食物
this.food = new Food(this);
// 执行定时器任务
this.start();
// 键盘的事件监听
this.bindEvent();
//初始化蛇的头方向
this.d = 'R';
//初始化暂停
this.flag = 0;
}
Game.prototype.init = function() {
this.dom = document.createElement("table");
var tr, td;
//遍历行和列上树
for (var i = 0; i < this.row; i++) {
//遍历行,创建结点上树
tr = document.createElement('tr');
for (var j = 0; j < this.col; j++) {
//遍历列,创建结点上树
td = document.createElement('td');
//追加到tr
tr.appendChild(td);
}
//追加节点上树
this.dom.appendChild(tr);
}
//表格上树
document.getElementById('app').appendChild(this.dom);
}
Game.prototype.clear = function() {
//遍历表格,擦除画布
for (var i = 0; i < this.row; i++) {
for (var j = 0; j < this.col; j++) {
this.dom.getElementsByTagName('tr')[i].getElementsByTagName('td')[j].style.background = '';
this.dom.getElementsByTagName('tr')[i].getElementsByTagName('td')[j].innerHTML = '';
}
}
};
//设置头部
Game.prototype.setHead = function(row, col) {
var img = document.createElement('img');
img.src = 'img/snake.png';
img.classList.add('head');
this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].appendChild(img);
switch (this.d) {
case 'R':
//初始方向
break;
case 'D':
img.style.transform = 'rotate(90deg)';
break;
case 'L':
img.style.transform = 'rotate(180deg)';
break;
case 'U':
img.style.transform = 'rotate(-90deg)';
break;
}
}
// 设置颜色的方法
Game.prototype.setColor = function(row, col, color) {
// 让表格的第几行第几列设置什么颜色
this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.background = color;
};
// 渲染食物
Game.prototype.setHTML = function(row, col) {
this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.backgroundImage = 'url(./img/food2.png)';
};
//设置键盘监听事件
Game.prototype.bindEvent = function() {
var self = this;
// 键盘事件
document.onkeydown = function(event) {
switch (event.keyCode) {
case 37:
// 按下←键
// 先进行判断,如果当前的方向是向右移动,此事我们不能按左键
if (self.snake.direction == 'R') {
return;
}
self.snake.changeDirection("L");
self.d = 'L';
break;
case 38:
//按下↑键
// 先进行判断,如果当前的方向是向下移动,此事我们不能按上键
if (self.snake.direction == 'D') {
return;
}
self.snake.changeDirection("U");
self.d = 'U';
break;
case 39:
//按下→键
// 先进行判断,如果当前的方向是向左移动,此事我们不能按右键
if (self.snake.direction == 'L') {
return;
}
self.snake.changeDirection("R");
self.d = 'R';
break;
case 40:
// 按下↓键
// 先进行判断,如果当前的方向是向上移动,此事我们不能按下键
if (self.snake.direction == 'U') {
return;
}
self.snake.changeDirection("D");
self.d = 'D';
break;
}
}
}
Game.prototype.start = function() {
// 帧编号
var self = this;
this.f = 0;
btn.addEventListener('click', function() {
box.classList.add('stop');
box.classList.remove('start');
self.timer = setInterval(function() {
// 定时器里面的核心就是游戏渲染的本质,清楚屏幕,更新,渲染。
game.f++;
document.getElementById("f").innerHTML = "帧编号:" + game.f;
// 渲染分数
document.getElementById("score").innerHTML = "分数为:" + game.score;
// 清楚屏幕
game.clear();
//蛇的更新速度,当蛇边长的时候,速度要加快
var during = game.snake.body.length < 30 ? 30 - game.snake.body.length : 1;
// 蛇的渲染
console.log(during);
game.f % during == 0 && game.snake.update();
// 渲染蛇
game.snake.render();
// 渲染食物
game.food.render();
}, 20);
});
on.addEventListener('click', function() {
if (self.flag == 0) {
self.flag = 1;
pause.style.display = 'block';
clearInterval(self.timer);
} else if (self.flag == 1) {
self.flag = 0;
pause.style.display = 'none';
self.timer = setInterval(function() {
// 定时器里面的核心就是游戏渲染的本质,清楚屏幕,更新,渲染。
game.f++;
document.getElementById("f").innerHTML = "帧编号:" + game.f;
// 渲染分数
document.getElementById("score").innerHTML = "分数为:" + game.score;
// 清楚屏幕
game.clear();
//蛇的更新速度,当蛇边长的时候,速度要加快,为当前30-当前身体长度的,否则为1
var during = game.snake.body.length < 30 ? 30 - game.snake.body.length : 1;
// 蛇的渲染
game.f % during == 0 && game.snake.update();
// 渲染蛇
game.snake.render();
// 渲染食物
game.food.render();
}, 20);
}
});
}
Food.js:
function Food(gameSnake) {
// 备份this
var self = this;
//食物的位置
// 下面的do-while循环语句作用是先创建一个row和col然后判断这个数值在不在蛇身上
do {
this.row = parseInt(Math.random() * gameSnake.row);
this.col = parseInt(Math.random() * gameSnake.col);
} while ((function() {
// 遍历蛇的row和col然后和food新随机出来的row和col判断是否重合
for (var i = 0; i < gameSnake.snake.body.length; i++) {
if (gameSnake.snake.body[i].row == self.row && gameSnake.snake.body[i].col == self.col) {
return true;
}
}
return false;
})());
}
Food.prototype.render = function() {
game.setHTML(this.row, this.col);
}
Snake.js:
function Snake() {
// 蛇的初始化身体
this.body = [
{ "row": 3, "col": 5 },
{ "row": 3, "col": 4 },
{ "row": 3, "col": 3 },
{ "row": 3, "col": 2 }
];
// 信息量,设置的运动方向
this.direction = 'R';
// 即将改变的方向,目的就是为了防止原地调头的情况
this.willDirection = 'R';
}
// 蛇的运动
Snake.prototype.update = function() {
// 让当前的direction接受一下willDirection
this.direction = this.willDirection;
switch (this.direction) {
case 'R':
// 向右
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col + 1 });
break;
case 'D':
// 向下
this.body.unshift({ "row": this.body[0].row + 1, "col": this.body[0].col });
break;
case 'L':
// 向左
this.body.unshift({ "row": this.body[0].row, "col": this.body[0].col - 1 });
break;
case 'U':
// 向上
this.body.unshift({ "row": this.body[0].row - 1, "col": this.body[0].col });
break;
}
// 死亡的判断——撞击墙壁
if (this.body[0].col > game.col - 1 || this.body[0].row > game.row - 1 || this.body[0].col < 0 || this.body[0].row < 0) {
alert('游戏结束!您的得分是:' + game.score + '分'); //删除是因为当前的头赠是不合法的,因为游戏结束了
this.body.shift();
clearInterval(game.timer);
location.reload();
}
// 死亡判断——咬到自己
for (var i = 1; i < this.body.length; i++) {
// 如果能够判断死亡,也就是当前的蛇的头部和身体的某个部分row和col完全重合的时候
if (this.body[0].col == this.body[i].col && this.body[0].row == this.body[i].row) {
alert('游戏结束!您的得分是:' + game.score + '分'); //删除是因为当前的头赠是不合法的,因为游戏结束了
this.body.shift();
clearInterval(game.timer);
location.reload();
}
}
// 蛇吃食物
// 判断如果当前的蛇的头部没有喝食物进行重合,就代表此时没有迟到食物,此时就进行尾巴删除,如果重合吃到了,不进行尾部删除
if (this.body[0].row == game.food.row && this.body[0].col == game.food.col) {
//创建新的食物
// 此时头部增加了,尾巴没有删除
game.food = new Food(game);
// 加分数
game.score++;
//让帧编号归0,因为蛇会突进一下
game.f = 0;
} else {
this.body.pop();
}
};
// 蛇的方向改变,防止的是在一次渲染之前会出现调头的情况
Snake.prototype.changeDirection = function(d) {
this.willDirection = d;
}
Snake.prototype.render = function() {
// 蛇的渲染
game.setHead(this.body[0].row, this.body[0].col);
for (var i = 1; i < this.body.length; i++) {
game.setColor(this.body[i].row, this.body[i].col, '#9ccc65');
}
}