游戏截图
目标
游戏的目的是用来体会JavaScript高级语法的使用,暂时不需要具备抽象对象的能力。 使用面向对象的方式分析问题,需要一个漫长的积累过程。
开发工具
VS Code
项目文件搭建
下面我们正式开始贪吃蛇游戏制作。
搭建项目文件目录
搭建页面
放一个容器盛放游戏场景div#map,并设置样式。在index.html中代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div class="map" id="map"></div>
</body>
</html>
在css文件夹中新建index.css文件并编辑,写入如下代码:
* {
margin: 0;
padding: 0;
}
.map {
position: relative;
width: 800px;
height: 600px;
background-color: lightgray;
}
在index.html中使用Alt + B快捷键用浏览器打开, 生成游戏背景图如下:
分析游戏需要创建的对象
看到的对象:
- 食物(food)
- 蛇(snake) 看不到的对象:
- 游戏逻辑(Game)等
创建食物对象
添加工具方法对象。在js文件夹中添加tools.js并写入如下代码:
(function () {
// 制作一个工具对象,内部添加多种工具的方法
var Tools = {
// 获取一个范围内部的随机整数
getRandom: function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
},
// 获取随机颜色的方法
getColor: function () {
// rgb(r,g,b) 三个色值的颜色可以随机获取 0-255 之间的数字
// 获取三个色值
var r = this.getRandom(0, 255);
var g = this.getRandom(0, 255);
var b = this.getRandom(0, 255);
// 返回一个 颜色值
return "rgb(" + r + "," + g + "," + b + ")";
}
};
window.Tools = Tools;
})();
在js文件夹中添加foods.js文件并打开编辑,写入如下代码:
创建食物对象
function Food(option) {
// 避免传入的参数数据类型不对,或者没有传参
option = option instanceof Object ? option : {};
// 传入的数据可能是类似数组等对象,所以需要进一步判断
this.width = option.width || 20;
this.height = option.height || 20;
this.x = option.x || 0;
this.y = option.y || 0;
this.color = option.color || "green";
// 增加一个属性,存储将来这个对象渲染出来的所有 div 元素
this.elements = [];
}
在页面进行渲染
// 渲染一个元素到页面之上,需要添加到原型对象的方法中
Food.prototype.render = function (map) {
// 创建一个新的 div 元素
var ele = document.createElement("div");
// 添加对应的样式
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.left = this.x + "px";
ele.style.top = this.y + "px";
ele.style.backgroundColor = this.color;
// 让新元素添加到指定的父级中
map.appendChild(ele);
// 将新元素添加的 数组中,方便后期调用删除
this.elements.push(ele);
};
测试, 还是在foods.js中写入如下代码。
// 获取map元素
var map = document.getElementById("map");
// 测试
var food = new Food();
food.render(map);
然后在index.html中引入所有js文件,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div class="map" id="map"></div>
<script src="js/tools.js"></script>
<script src="js/foods.js"></script>
</body>
</html>
Alt+B快捷键用默认浏览器打开,一个食物对象就渲染出来了。
实现食物对象位置随机变化
现在食物位置固定,想让它位置随机变动,需定义元素为绝对定位。在foods.js中定义全局变量var ps = "absolute";。 食物对象位置水平随机分析:使用随机函数,其left值在(0,n-1)* w进行随机,n是一行能放下的食物个数,w是食物对象的宽度。高度随机同理,相关代码如下:
this.x = Tools.getRandom(0, map.clientWidth / this.width - 1) * this.width;
this.y = Tools.getRandom(0, map.clientHeight / this.height - 1) * this.height;
于是,foods.js内容修改如下:
// 全局的变量
var ps = "absolute";
function Food(option) {
// 避免传入的参数数据类型不对,或者没有传参
option = option instanceof Object ? option : {};
// 传入的数据可能是类似数组等对象,所以需要进一步判断
this.width = option.width || 20;
this.height = option.height || 20;
this.x = option.x || 0;
this.y = option.y || 0;
this.color = option.color || "green";
// 增加一个属性,存储将来这个对象渲染出来的所有 div 元素
this.elements = [];
}
// 渲染一个元素到页面之上,需要添加到原型对象的方法中
Food.prototype.render = function (map) {
// 创建一个新的 div 元素
var ele = document.createElement("div");
// 每次设置样式之前,都随机获取一个 x 和 y 的值
this.x = Tools.getRandom(0, map.clientWidth / this.width - 1) * this.width;
this.y = Tools.getRandom(0, map.clientHeight / this.height - 1) * this.height;
// 添加对应的样式
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.left = this.x + "px";
ele.style.top = this.y + "px";
ele.style.backgroundColor = this.color;
ele.style.position = ps;
// 让新元素添加到指定的父级中
map.appendChild(ele);
// 将新元素添加的 数组中,方便后期调用删除
this.elements.push(ele);
};
// 获取map元素
var map = document.getElementById("map");
// 测试
var food = new Food();
food.render(map);
这样就实现了食物每次位置随机的功能。
食物删除方法
食物被蛇吃掉会消失,并在另一个地方重新生成。在foods.js中加入以下代码:
// 删除一个食物 div 元素
Food.prototype.remove = function (map, i) {
// 可以通过一些方法获取要被删除的食物的下标
// 将元素 从 html结构中删除
map.removeChild(this.elements[i]);
// 将元素 从 数组中删除
this.elements.splice(i, 1);
};
// 测试,两秒后删除食物
setTimeout(function () {
food.remove(map, 0)
},2000)
重新生成食物只需要再次调用food.render(map);即可。
自调用函数关住作用域
现在Food对象、tools对象基本创建完成,但是它们包括其方法都是全局变量,这容易造成变量污染,不容易管理。所以我们需要用自调用函数方法来对它们进行再一次封装,也就是(function (){Food})();和(function (){tools})();使它们作用域从全局变成局部。同时设置全局变量来指向被封装起来的Food对象和tools对象。于是foods.js内容如下:
// 需要去缩小定义 构造函数的作用
// 匿名函数,自调用函数,IIFE,关住作用域
(function () {
// 全局的变量
var ps = "absolute";
// 创建 食物 的构造函数
function Food(option) {
// 避免传入的参数数据类型不对,或者没有传参
option = option instanceof Object ? option : {};
// 传入的数据可能是类似数组等对象,所以需要进一步判断
this.width = option.width || 20;
this.height = option.height || 20;
this.x = option.x || 0;
this.y = option.y || 0;
this.color = option.color || "green";
// 增加一个属性,存储将来这个对象渲染出来的所有 div 元素
this.elements = [];
}
// 渲染一个元素到页面之上,需要添加到原型对象的方法中
Food.prototype.render = function (map) {
// 创建一个新的 div 元素
var ele = document.createElement("div");
// 每次设置样式之前,都随机获取一个 x 和 y 的值
this.x = Tools.getRandom(0, map.clientWidth / this.width - 1) * this.width;
this.y = Tools.getRandom(0, map.clientHeight / this.height - 1) * this.height;
// 添加对应的样式
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.left = this.x + "px";
ele.style.top = this.y + "px";
ele.style.backgroundColor = this.color;
ele.style.position = ps;
// 让新元素添加到指定的父级中
map.appendChild(ele);
// 将新元素添加的 数组中,方便后期调用删除
this.elements.push(ele);
};
// 删除一个食物 div 元素
Food.prototype.remove = function (map, i) {
// 可以通过一些方法获取要被删除的食物的下标
// 将元素 从 html结构中删除
map.removeChild(this.elements[i]);
// 将元素 从 数组中删除
this.elements.splice(i, 1);
};
// 利用 window 对象暴露 Food 函数可以给外部使用
window.Food = Food;
})();
// 需要想办法在外面调用到这个 Food 函数
tools.js内容如下:
(function () {
// 制作一个工具对象,内部添加多种工具的方法
var Tools = {
// 获取一个范围内部的随机整数
getRandom: function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
},
// 获取随机颜色的方法
getColor: function () {
// rgb(r,g,b) 三个色值的颜色可以随机获取 0-255 之间的数字
// 获取三个色值
var r = this.getRandom(0, 255);
var g = this.getRandom(0, 255);
var b = this.getRandom(0, 255);
// 返回一个 颜色值
return "rgb(" + r + "," + g + "," + b + ")";
}
};
window.Tools = Tools;
})();
创建蛇对象
分析需求:蛇出生一共有三节蛇节,两节身子和一节蛇头,出生时位置固定(地图左上角)。有运动方向,定时运动步长等属性。渲染方法,运动方法,吃食物方法等。吃完食物,蛇身增加需相应长度。
开始创建:
创建Snake的构造函数,并设置属性
width 蛇节的宽度默认20
height 蛇节的高度默认20
body 数组,蛇的头部和身体,第一个位置是蛇头
direction蛇运动的方向默认right可以是left/top/bottom
通过原型设置方法
render 随机创建一个蛇对象,并输出到map上
通过自调用函数,进行封装:通过window暴露Snake对象
在js文件夹下新建snake.js文件,内容如下。
// 使用自调用函数关住作用域
(function () {
// 全局变量
var ps = "absolute";
// 创建 蛇 的构造函数
function Snake(option) {
// 避免传入的参数数据类型不对,或者没有传参
option = option instanceof Object ? option : {};
// 给对象添加属性
// 设置蛇节的宽度和高度属性
this.width = option.width || 20;
this.height = option.height || 20;
// 设置蛇身的数据
this.body = [
{x: 3,y: 2,color: "red"},
{x: 2,y: 2,color: "blue"},
{x: 1,y: 2,color: "blue"}
];
// 设置蛇移动的方向,还可以设置为 left 、top 、bottom
this.direction = "right";
// 添加一个元素的数组,存储所有渲染的 div
this.elements = [];
}
// 添加一个将元素渲染到页面上的方法
Snake.prototype.render = function (map) {
// 生成对应个数的 div 元素
// 遍历数组
for (var i = 0,len = this.body.length ; i < len ; i++) {
// 根据数组的每一项的数据生成一个新的 div 元素
var piece = this.body[i];
// 创建新元素
var ele = document.createElement("div");
// 添加样式
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.left = piece.x * this.width + "px";
ele.style.top = piece.y * this.height + "px";
ele.style.position = ps;
ele.style.backgroundColor = piece.color;
// 渲染到指定的父级内部
map.appendChild(ele);
// 将添加的新元素存在数组里
this.elements.push(ele);
}
};
// 通过 window 暴露构造函数
window.Snake = Snake;
})();
//测试
var map = document.getElementById("map");
var snake = new Snake();
snake.render(map);
然后在index.html中进行引用添加:<script src="js/snake.js"></script>运行得到渲染的蛇对象。
创建游戏对象
创建Game的构造函数,并设置属性
food
snake
map
通过原型设置方法
start 开始游戏(绘制所有游戏对象,渲染食物对象和蛇对象)
通过自调用函数,进行封装,通过window暴露Game对象
在js文件夹中新建文件game.js,并在index.html中进行引用添加:<script src="js/game.js"></script>
注释掉原来测试的代码。game.js内容如下:
// 自调用函数封闭作用域
(function () {
// 定义一个全局变量,存储 this
var that;
// 创建一个 游戏 的构造函数
function Game(map) {
// 设置三个属性,存储 食物、 蛇、地图
this.food = new Food();
this.snake = new Snake();
this.map = map;
that = this;
}
// 添加一个游戏开始的方法,方法内初始化蛇和食物
Game.prototype.start = function () {
// 1.添加蛇和食物到 地图上
this.food.render(this.map);
this.food.render(this.map);
this.food.render(this.map);
this.snake.render(this.map);
}
// 将构造函数通过 window 暴露
window.Game = Game;
})();
//测试
var map = document.getElementById("map");
game = new Game();
game.start();
在index.html中Alt+B快捷键使用默认浏览器运行。
给蛇添加运动方法
分析:蛇运动一格的时候,蛇身每节都会运动到上一个部分的位置。所以我们可以先从后往前处理蛇身的运动,然后再处理蛇头的运动。
在snake.js的自调用函数中添加如下运动方法:
// 添加 蛇 运动的方法
Snake.prototype.move = function () {
// 蛇身的每一节都要变成上一节的位置
// 循环需要从最后一项开始,为了避免前面的数据提前发生变化
for (var i = this.body.length - 1 ; i > 0 ; i--) {
this.body[i].x = this.body[i-1].x;
this.body[i].y = this.body[i-1].y;
}
// 存储一下蛇头的数据
var head = this.body[0];
// 蛇头要根据方向发生位置变化
switch (this.direction) {
case "right":
head.x += 1;
break;
case "left":
head.x -= 1;
break;
case "top":
head.y -= 1;
break;
case "bottom":
head.y += 1;
}
使用时调用snake.move()再进行渲染snake.render(),即可实现运动。但是这样,我们渲染了两次蛇对象,相当于第二条蛇压盖在了第一条蛇的身上,上一次渲染的结果并没有消失,蛇看上去多了节尾巴,所以我们要删除上一次的渲染。 在snake.js中添加如下删除方法:
// 删除上一次渲染的蛇的所有div元素
Snake.prototype.remove = function (map) {
// 遍历数组删除所有元素
// 将元素从html结构中删掉
for (var i = this.elements.length - 1 ; i >= 0 ; i--) {
map.removeChild(this.elements[i]);
}
// 数组也需要进行清空
this.elements = [];
}
在game.js中重写start方法:
// 添加一个游戏开始的方法,方法内初始化蛇和食物
Game.prototype.start = function () {
// 1.添加蛇和食物到 地图上
this.food.render(this.map);
this.food.render(this.map);
this.food.render(this.map);
this.snake.render(this.map);
this.snake.move();
this.snake.remove(this.map);
this.snake.render(this.map);
}
保存,在index.html中Alt+B运行,就会发现蛇向右移动了一格。
相当于snake调用一次move()、remove()、render()方法三连,就能让蛇对象移动一格。
游戏逻辑书写
分析:将蛇和食物渲染到map上后,需要,
1.让蛇自动运动起来 runSnake();而且运动过程中,吃到食物会增加蛇身,撞到墙壁会游戏结束。
2 通过上下左右箭头控制蛇的运动方向 bindKey();
game.js内容修改如下:
// 自调用函数封闭作用域
(function () {
// 定义一个全局变量,存储 this
var that;
// 创建一个 游戏 的构造函数
function Game(map) {
// 设置三个属性,存储 食物、 蛇、地图
this.food = new Food();
this.snake = new Snake();
this.map = map;
that = this;
}
// 添加一个游戏开始的方法,方法内初始化蛇和食物
Game.prototype.start = function () {
// 1.添加蛇和食物到 地图上
this.food.render(this.map);
this.food.render(this.map);
this.food.render(this.map);
this.snake.render(this.map);
// 2.让游戏逻辑开始
// 2.1 让蛇自动运动起来
runSnake();
// 2.2 通过上下左右箭头控制蛇的运动方向
bindKey();
}
// 封装一个私有函数,控制上下左右按键更改的方向
function bindKey() {
// 给文档绑定键盘按下事件
document.onkeydown = function (e) {
// console.log(e.keyCode);
// 键盘的编码
// 37 -- left
// 38 -- top
// 39 -- right
// 40 -- bottom
switch (e.keyCode) {
case 37:
that.snake.direction = "left";
break;
case 38:
that.snake.direction = "top";
break;
case 39:
that.snake.direction = "right";
break;
case 40:
that.snake.direction = "bottom";
break;
}
};
}
// 封装一个私有函数,这个函数只能在模块内部进行调用
function runSnake() {
// 开启一个定时器,让蛇连续运动起来
var timer = setInterval(function () {
// 定时器函数内部的 this 指向的是 window
// 让蛇运动起来
that.snake.move();
// 删掉上一次的蛇
that.snake.remove(that.map);
// 渲染新位置的蛇
that.snake.render(that.map);
// 记录一下最大的位置
var maxX = that.map.offsetWidth / that.snake.width;
var maxY = that.map.offsetHeight / that.snake.height;
// 找到当前蛇头的位置
var headX = that.snake.body[0].x;
var headY = that.snake.body[0].y;
// 每一次蛇走到新的位置,都要判断一下是否吃到食物了
// 2.3 判断蛇头与食物是否碰撞,吃掉食物 ,让自己增加一节
// 记录一下食物的坐标
// var foodX = that.food.x;
// var foodY = that.food.y;
// 获取蛇头的具体坐标位置,px值
var hX = headX * that.snake.width;
var hY = headY * that.snake.height;
// 判断
// 将食物的数组中每一个都要进行对比,谁被吃掉,删除自己,渲染一个新的元素
for (var i = 0 ; i < that.food.elements.length ; i++) {
if (that.food.elements[i].offsetLeft === hX && that.food.elements[i].offsetTop === hY) {
// 吃到了食物
// 让食物删除,然后渲染一个新的食物
that.food.remove(that.map,i);
that.food.render(that.map);
// 添加一个新的蛇节
var last = that.snake.body[that.snake.body.length - 1];
that.snake.body.push({
x: last.x,
y: last.y,
color: last.color
});
}
}
// 每移动一次,都要判断是否出了地图,游戏是否结束
// 2.4 判断是否超出地图范围,结束游戏
// 进行判断
if (headX < 0 || headX >= maxX || headY < 0 || headY >= maxY) {
// 停止定时器
clearInterval(timer);
// 弹出提醒
alert("Game over");
}
}, 150);
}
// 将构造函数通过 window 暴露
window.Game = Game;
})();
//测试
var map = document.getElementById("map");
game = new Game(map);
game.start();
至此,游戏功能基本制作完成。下面就是一些无功能实现的优化措施。
代码优化
全部使用自调用函数
我们需要将game.js中的测试部分代码单独封装到一个js文件中,并且以自调用函数的方式使用。于是,我们在js文件夹中新建main.js文件,内容如下:
// 使用自调用函数关住作用域
(function () {
var map = document.getElementById("map");
var game = new Game(map);
game.start();
})();
在index.html中进行引用添加:<script src="js/main.js"></script>
这样,每个js文件都是自调用函数,各自有各自的功能和作用域。
自调用函数的参数
在自调用函数中,我们为了将构造函数通过 window 暴露,使用了window变量。解释器在解析时,每次都会跳出作用域去寻找全局变量window,效率较低,而且在压缩代码时没办法像其他变量一样能被压缩,于是我们需要给自调用函数传入参数window。因为在ie8中undefined可以被重写赋予新值的问题,我们还要传入参数undefined,在自调用匿名函数的作用域内,确保 undefined 是真的未定义。
于是,所有自调用函数都变成了如下形式:
(function (window,undefined) {
})(window,undefined);
js代码压缩
在js文件夹中新建一个index.js,里面内容是按顺序将tools.js、foods.js、snake.js、game.js、main.js内容全部集中在一起:
// 将所有的模块代码要按照一定得顺序引入
// ======================Tools============================
;(function (window,undefined) {
// 制作一个工具对象,内部添加多种工具的方法
var Tools = {
// 获取一个范围内部的随机整数
getRandom: function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
},
// 获取随机颜色的方法
getColor: function () {
// rgb(r,g,b) 三个色值的颜色可以随机获取 0-255 之间的数字
// 获取三个色值
var r = this.getRandom(0, 255);
var g = this.getRandom(0, 255);
var b = this.getRandom(0, 255);
// 返回一个 颜色值
return "rgb(" + r + "," + g + "," + b + ")";
}
};
window.Tools = Tools;
})(window,undefined)
// ==================Food===========================
;(function (window,undefined) {
// 全局的变量
var ps = "absolute";
// 创建 食物 的构造函数
function Food(option) {
// 避免传入的参数数据类型不对,或者没有传参
option = option instanceof Object ? option : {};
// 传入的数据可能是类似数组等对象,所以需要进一步判断
this.width = option.width || 20;
this.height = option.height || 20;
this.x = option.x || 0;
this.y = option.y || 0;
this.color = option.color || "green";
// 增加一个属性,存储将来这个对象渲染出来的所有 div 元素
this.elements = [];
}
// 渲染一个元素到页面之上,需要添加到原型对象的方法中
Food.prototype.render = function (map) {
// 创建一个新的 div 元素
var ele = document.createElement("div");
// 每次设置样式之前,都随机获取一个 x 和 y 的值
this.x = Tools.getRandom(0, map.clientWidth / this.width - 1) * this.width;
this.y = Tools.getRandom(0, map.clientHeight / this.height - 1) * this.height;
// 添加对应的样式
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.left = this.x + "px";
ele.style.top = this.y + "px";
ele.style.backgroundColor = this.color;
ele.style.position = ps;
// 让新元素添加到指定的父级中
map.appendChild(ele);
// 将新元素添加的 数组中,方便后期调用删除
this.elements.push(ele);
};
// 删除一个食物 div 元素
Food.prototype.remove = function (map, i) {
// 可以通过一些方法获取要被删除的食物的下标
// 将元素 从 html结构中删除
map.removeChild(this.elements[i]);
// 将元素 从 数组中删除
this.elements.splice(i, 1);
};
// 利用 window 对象暴露 Food 函数可以给外部使用
window.Food = Food;
})(window,undefined)
// ===================Snake=============================
;(function (window,undefined) {
// 全局变量
var ps = "absolute";
// 创建 蛇 的构造函数
function Snake(option) {
// 避免传入的参数数据类型不对,或者没有传参
option = option instanceof Object ? option : {};
// 给对象添加属性
// 设置蛇节的宽度和高度属性
this.width = option.width || 20;
this.height = option.height || 20;
// 设置蛇身的数据
this.body = [
{x: 3,y: 2,color: "red"},
{x: 2,y: 2,color: "blue"},
{x: 1,y: 2,color: "blue"}
];
// 设置蛇移动的方向,还可以设置为 left 、top 、bottom
this.direction = "right";
// 添加一个元素的数组,存储所有渲染的 div
this.elements = [];
}
// 添加一个将元素渲染到页面上的方法
Snake.prototype.render = function (map) {
// 生成对应个数的 div 元素
// 遍历数组
for (var i = 0,len = this.body.length ; i < len ; i++) {
// 根据数组的每一项的数据生成一个新的 div 元素
var piece = this.body[i];
// 创建新元素
var ele = document.createElement("div");
// 添加样式
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.left = piece.x * this.width + "px";
ele.style.top = piece.y * this.height + "px";
ele.style.position = ps;
ele.style.backgroundColor = piece.color;
// 渲染到指定的父级内部
map.appendChild(ele);
// 将添加的新元素存在数组里
this.elements.push(ele);
}
};
// 添加 蛇 运动的方法
Snake.prototype.move = function () {
// 蛇身的每一节都要变成上一节的位置
// 循环需要从最后一项开始,为了避免前面的数据提前发生变化
for (var i = this.body.length - 1 ; i > 0 ; i--) {
this.body[i].x = this.body[i-1].x;
this.body[i].y = this.body[i-1].y;
}
// 存储一下蛇头的数据
var head = this.body[0];
// 蛇头要根据方向发生位置变化
switch (this.direction) {
case "right":
head.x += 1;
break;
case "left":
head.x -= 1;
break;
case "top":
head.y -= 1;
break;
case "bottom":
head.y += 1;
}
};
// 删除上一次渲染的蛇的所有div元素
Snake.prototype.remove = function (map) {
// 遍历数组删除所有元素
// 将元素从html结构中删掉
for (var i = this.elements.length - 1 ; i >= 0 ; i--) {
map.removeChild(this.elements[i]);
}
// 数组也需要进行清空
this.elements = [];
}
// 通过 window 暴露构造函数
window.Snake = Snake;
})(window,undefined)
// ========================Game===================
;(function (window,undefined) {
// 定义一个全局变量,存储 this
var that;
// 创建一个 游戏 的构造函数
function Game(map) {
// 设置三个属性,存储 食物、 蛇、地图
this.food = new Food();
this.snake = new Snake();
this.map = map;
that = this;
}
// 添加一个游戏开始的方法,方法内初始化蛇和食物
Game.prototype.start = function () {
// 1.添加蛇和食物到 地图上
this.food.render(this.map);
this.food.render(this.map);
this.food.render(this.map);
this.snake.render(this.map);
// 2.让游戏逻辑开始
// 2.1 让蛇自动运动起来
runSnake();
// 2.2 通过上下左右箭头控制蛇的运动方向
bindKey();
}
// 封装一个私有函数,控制上下左右按键更改的方向
function bindKey() {
// 给文档绑定键盘按下事件
document.onkeydown = function (e) {
// console.log(e.keyCode);
// 键盘的编码
// 37 -- left
// 38 -- top
// 39 -- right
// 40 -- bottom
switch (e.keyCode) {
case 37:
that.snake.direction = "left";
break;
case 38:
that.snake.direction = "top";
break;
case 39:
that.snake.direction = "right";
break;
case 40:
that.snake.direction = "bottom";
break;
}
};
}
// 封装一个私有函数,这个函数只能在模块内部进行调用
function runSnake() {
// 开启一个定时器,让蛇连续运动起来
var timer = setInterval(function () {
// 定时器函数内部的 this 指向的是 window
// 让蛇运动起来
that.snake.move();
// 删掉上一次的蛇
that.snake.remove(that.map);
// 渲染新位置的蛇
that.snake.render(that.map);
// 记录一下最大的位置
var maxX = that.map.offsetWidth / that.snake.width;
var maxY = that.map.offsetHeight / that.snake.height;
// 找到当前蛇头的位置
var headX = that.snake.body[0].x;
var headY = that.snake.body[0].y;
// 每一次蛇走到新的位置,都要判断一下是否吃到食物了
// 2.3 判断蛇头与食物是否碰撞,吃掉食物 ,让自己增加一节
// 记录一下食物的坐标
// var foodX = that.food.x;
// var foodY = that.food.y;
// 获取蛇头的具体坐标位置,px值
var hX = headX * that.snake.width;
var hY = headY * that.snake.height;
// 判断
// 将食物的数组中每一个都要进行对比,谁被吃掉,删除自己,渲染一个新的元素
for (var i = 0 ; i < that.food.elements.length ; i++) {
if (that.food.elements[i].offsetLeft === hX && that.food.elements[i].offsetTop === hY) {
// 吃到了食物
// 让食物删除,然后渲染一个新的食物
that.food.remove(that.map,i);
that.food.render(that.map);
// 添加一个新的蛇节
var last = that.snake.body[that.snake.body.length - 1];
that.snake.body.push({
x: last.x,
y: last.y,
color: last.color
});
}
}
// 每移动一次,都要判断是否出了地图,游戏是否结束
// 2.4 判断是否超出地图范围,结束游戏
// 进行判断
if (headX < 0 || headX >= maxX || headY < 0 || headY >= maxY) {
// 停止定时器
clearInterval(timer);
// 弹出提醒
alert("Game over");
}
}, 150);
}
// 将构造函数通过 window 暴露
window.Game = Game;
})(window,undefined)
// ========================== Main =========================
;(function (window,undefined) {
var map = document.getElementById("map");
var game = new Game(map);
game.start();
})(window,undefined)
在搜索引擎中搜索“代码压缩”,即可找到相关的在线压缩工具,可以对代码进行去除注释,空格等代码来压缩,还可以进行标识符混淆。压缩后传输速度会有所提升。我们将index.js中的内容进行压缩。
在js文件夹中新建一个index.min.js文件,内容就是压缩后的代码:
(function(){var a={getRandom:function(c,b){c=Math.ceil(c);b=Math.floor(b);return Math.floor(Math.random()*(b-c+1))+c},getColor:function(){var e=this.getRandom(0,255);var d=this.getRandom(0,255);var c=this.getRandom(0,255);return"rgb("+e+","+d+","+c+")"}};window.Tools=a})();(function(){var b="absolute";function a(c){c=c instanceof Object?c:{};this.width=c.width||20;this.height=c.height||20;this.x=c.x||0;this.y=c.y||0;this.color=c.color||"green";this.elements=[]}a.prototype.render=function(d){var c=document.createElement("div");this.x=Tools.getRandom(0,d.clientWidth/this.width-1)*this.width;this.y=Tools.getRandom(0,d.clientHeight/this.height-1)*this.height;c.style.width=this.width+"px";c.style.height=this.height+"px";c.style.left=this.x+"px";c.style.top=this.y+"px";c.style.backgroundColor=this.color;c.style.position=b;d.appendChild(c);this.elements.push(c)};a.prototype.remove=function(d,c){d.removeChild(this.elements[c]);this.elements.splice(c,1)};window.Food=a})();(function(){var b="absolute";function a(c){c=c instanceof Object?c:{};this.width=c.width||20;this.height=c.height||20;this.body=[{x:3,y:2,color:"red"},{x:2,y:2,color:"blue"},{x:1,y:2,color:"blue"}];this.direction="right";this.elements=[]}a.prototype.render=function(g){for(var d=0,c=this.body.length;d<c;d++){var e=this.body[d];var f=document.createElement("div");f.style.width=this.width+"px";f.style.height=this.height+"px";f.style.left=e.x*this.width+"px";f.style.top=e.y*this.height+"px";f.style.position=b;f.style.backgroundColor=e.color;g.appendChild(f);this.elements.push(f)}};a.prototype.move=function(){for(var d=this.body.length-1;d>0;d--){this.body[d].x=this.body[d-1].x;this.body[d].y=this.body[d-1].y}var c=this.body[0];switch(this.direction){case"right":c.x+=1;break;case"left":c.x-=1;break;case"top":c.y-=1;break;case"bottom":c.y+=1}};a.prototype.remove=function(d){for(var c=this.elements.length-1;c>=0;c--){d.removeChild(this.elements[c])}this.elements=[]};window.Snake=a})();(function(){var c;function d(e){this.food=new Food();this.snake=new Snake();this.map=e;c=this}d.prototype.start=function(){this.food.render(this.map);this.food.render(this.map);this.food.render(this.map);this.snake.render(this.map);b();a()};function a(){document.onkeydown=function(f){switch(f.keyCode){case 37:c.snake.direction="left";break;case 38:c.snake.direction="top";break;case 39:c.snake.direction="right";break;case 40:c.snake.direction="bottom";break}}}function b(){var e=setInterval(function(){c.snake.move();c.snake.remove(c.map);c.snake.render(c.map);var l=c.map.offsetWidth/c.snake.width;var j=c.map.offsetHeight/c.snake.height;var f=c.snake.body[0].x;var n=c.snake.body[0].y;var m=f*c.snake.width;var k=n*c.snake.height;for(var g=0;g<c.food.elements.length;g++){if(c.food.elements[g].offsetLeft===m&&c.food.elements[g].offsetTop===k){c.food.remove(c.map,g);c.food.render(c.map);var h=c.snake.body[c.snake.body.length-1];c.snake.body.push({x:h.x,y:h.y,color:h.color})}}if(f<0||f>=l||n<0||n>=j){clearInterval(e);alert("Game over")}},150)}window.Game=d})();(function(){var b=document.getElementById("map");var a=new Game(b);a.start()})();
后期会有打包工具压缩,而不用手动压缩了。我们现在是为了了解这个压缩过程,培养这个意识。
index.html中就只要引入index.min.js一个js文件即可,其内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div class="map" id="map"></div>
<!-- 引入多个 js 文件 -->
<!-- <script src="js/tools.js"></script>
<script src="js/food.js"></script>
<script src="js/snake.js"></script>
<script src="js/game.js"></script>
<script src="js/main.js"></script> -->
<!-- 为了进行性能优化,需要减少浏览器发送 HTTP 请求的次数 -->
<!-- <script src="js/index.js"></script> -->
<script src="js/index.min.js"></script>
</body>
</html>
使用Alt+B快捷键运行,得到一样的游戏效果。
啊啊啊,我技术好菜!!!