- 游戏的目的是用来体会 JavaScript 高级语法的使用,暂时不需要具备抽象对象的能力。使用面向对象的方式分析问题,需要一个漫长的积累过程。
搭建页面
- 一个容器盛放游戏场景 div#stage,设置样式.
分析对象
- 食物对象 Food
- 蛇对象 Snake
- 游戏对象 Game
创建工具类
创建食物对象
- 创建 Food 的构造函数,并设置属性
- 位置 x、y(实际位置)
- 宽度和高度 width、height
- 颜色
- 食物集合
- 定位:绝对定位
- 通过原型设置方法
- render 随机渲染一个食物对象,并添加到stage上
- 通过自调用函数,进行封装,并通过window暴露Food构造函数。
创建蛇对象
- 创建 Snake 的构造函数,并设置属性
- 宽度和高度 width、height
- body 数组,蛇的头部和身体,第一个位置是蛇头
- direction 蛇运动的方向
- 定位:绝对定位
- 通过原型设置方法
- render 随机创建一个蛇对象,并添加到stage上
- 通过自调用函数,进行封装。并通过window暴露Snake构造函数。
创建游戏对象
- 创建 Game 的构造函数,并设置属性
- 通过原型设置方法
- start 开始游戏(绘制所有游戏对象,渲染食物对象和蛇对象)
- 通过自调用函数,进行封装,通过 window 暴露 Game 构造函数
游戏逻辑
游戏逻辑1
- 蛇的move方法
- 在蛇对象 (snake.js) 中,在 Snake 的原型上新增 move 方法
- 1.让蛇移动起来,把蛇身体的每一部分往前移动一下
- 2.蛇头部分根据不同的方向决定 往哪里移动(+1)
游戏逻辑2
- 让蛇自己动起来
- 在 snake 中添加删除蛇的私有方法,在 render 中调用。
- 在 game.js 中添加 runSnake 的私有方法,开启定时器调用蛇的 move 和 render 方法,让蛇动起来。
- 判断蛇是否撞墙。
- 在 game.js 中添加 bindKey 的私有方法,通过键盘控制蛇的移动方向,在 start 方法中调用 bindKey。
游戏逻辑3
- 判断蛇是否吃到食物
- 在 Snake 的 move 方法中判断在移动的过程中蛇是否吃到食物。
- 如果蛇头和食物的位置重合代表吃到食物。
- 食物的坐标是像素,蛇的坐标是几个宽度,需要进行转换
- 吃到食物,往蛇节的最后加一节
- 最后把现在的食物对象删除,并重新随机渲染一个食物对象
- 升级为多个食物时,需要循环遍历查看是否重合,再随机渲染一个食物对象
其他处理
其他处理1
- 将测试执行的代码,整合到main.js文件中,并设置其为自调用函数,使js脱离html。
- 注意:引用js文件名的时候,要注意顺序,后引用的可以使用之前引用的文件,反之不行。
其他处理2
- 运行游戏后,在控制台network下可以看到HTTP向服务器请求的文件数
- 浏览器的性能优化中,包含一个减少发送http请求的条数,每多发送一个http请求,就要多在服务器里面请求一条数据,然后再去返回。
- 解决方法:将所有的js文件整合到一个js文件中,命名为:index.js
- 注意:在将js文件整合时,需要注意:每个js文件的先后顺序
其他处理3
- js代码压缩优化,可以使传输时效率更快
- 搜索:代码压缩
- 压缩后文件命名:index.min.js
- 在vscode中,查看选项卡 -> 自动切换换行
其他处理4
其他处理5
其他处理6
- 在遍历数组时,如果正向遍历,循环删除数组中的值,会出错
- 解决方法,如下
// 从舞台中清除蛇
for(var i = this.elements.length - 1; i >= 0; i--){
stage.removeChild(this.elements[i]);
}
// 清空数组
this.elements = [];
源码
index.html文件
<!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>贪吃蛇游戏</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div class="stage" id = "stage"></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.min.js"></script>
</body>
</html>
<!-- 疑问:为什么清除定时器之后,还会往前走一格 -->
index.css文件
/* 清除默认样式 */
* {
margin: 0;
padding: 0;
}
/* 给舞台添加样式 */
.stage {
width: 800px;
height: 600px;
background-color: lightgray;
position: relative;
}
index.js文件
// 整合所有的js文件
// =========================工具类=========================
// 制作一个工具对象,内部添加多种工具的方法
(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)
var r = this.getRandom(0,255);
var g = this.getRandom(0,255);
var b = this.getRandom(0,255);
// 返回的时候:进行拼接
var rgb = "rgb(" + r + "," + g + "," + b + ")";
// 返回颜色值
return rgb;
}
}
window.Tools = Tools;
})(window,undefined);
// ===========================food食物=======================
(function (window,undefined){
// 分析食物:位置x,y 宽高 颜色
function Food(option){
// 判断用户输入的是否正确
// option = option || {};
option = option instanceof Object ? option : {};
// 传入的数据可能是类似数组等对象,所以需要进一步判断
this.x = option.x || 0;
this.y = option.y || 0;
this.width = option.width || 20;
this.height = option.height || 20;
this.color = option.color || "blue";
// 如果后期难度加大,相当于一次出现三个小方块,就需要保存下
this.elements = [];
}
// 定义全局变量
// 将函数内部赋值后,不会改变的值,放在外面声明下更好
var bs = "absolute";
// 共享的方法:随机变换位置、颜色
// 渲染一个元素到页面上,需要添加到原型对象的方法中
Food.prototype.render = function(stage){
// 创建一个新的div元素
var ele = document.createElement("div");
// 每次设置样式之前,都随机获取一个x和y的值
this.x = Tools.getRandom(0,stage.clientWidth / this.width - 1) * this.width;
this.y = Tools.getRandom(0,stage.clientHeight / this.height - 1) * this.height;
// 添加对应的样式
ele.style.left = this.x + "px";
ele.style.top = this.y + "px";
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.backgroundColor = Tools.getColor();
ele.style.position = bs;
// 将新元素添加到父元素
stage.appendChild(ele);
// 将新元素添加到数组中,方便后期进行调用删除
this.elements.push(ele);
}
// 当贪吃蛇碰到一个食物的时候,就应该让其消失
// 即:从数组中删除
Food.prototype.remove = function (stage,i){
//从stage舞台上将其移除
stage.removeChild(this.elements[i]);
//从数组中将其移除
this.elements.splice(i,1);
}
// 利用window对象暴露Food函数可以给外部使用
// 相当于在外部找到了一个指针,指向它
window.Food = Food;
})(window,undefined);
// ===================蛇=====================
// 自调用函数
(function (window,undefined){
var bs = "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"}
];
this.position = bs;
this.direction = "right";
this.elements = [];
}
// 渲染出来蛇对象
Snake.prototype.render = function (stage){
// 优化写法:没必要定义变量,因为for循环,第一句原本就只执行一次
// var len = this.body.length;
for(var i = 0,len = this.body.length;i < len; i++){
// 创建一个对象
var ele = document.createElement("div");
// 赋样式值
ele.style.width = this.width + "px";
ele.style.height = this.height + "px";
ele.style.left = this.body[i].x * this.width + "px";
ele.style.top = this.body[i].y * this.height + "px";
ele.style.backgroundColor = this.body[i].color;
ele.style.position = this.position;
// 添加到舞台上
stage.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];
// console.log(this.direction);
switch(this.direction){
case "right":
head.x++;
break;
case "left":
head.x--;
break;
case "top":
head.y--;
break;
case "bottom":
head.y++;
break;
}
}
// 移除之前渲染的蛇
Snake.prototype.remove = function (stage){
// 从舞台中清除蛇
for(var i = this.elements.length - 1; i >= 0; i--){
stage.removeChild(this.elements[i]);
}
// 清空数组
this.elements = [];
}
// 通过window暴露属性
window.Snake = Snake;
})(window,undefined);
// =====================游戏=======================
// 自调用函数
(function (window,undefined){
// 全局变量:存储当前的game实例对象
var that;
function Game(stage){
// 食物
this.food = new Food();
// 蛇
this.snake = new Snake();
// 舞台
this.stage = stage;
that = this;
}
// 游戏逻辑
// 1.蛇往前走(定时器)
// 2.走到边界,游戏结束
// 3.吃到食物
// 4.食物消失,蛇变长
Game.prototype.start = function (){
this.food.render(this.stage);
this.food.render(this.stage);
this.food.render(this.stage);
this.snake.render(this.stage);
// 1.蛇往前走(定时器)
snakeRun();
// 3.吃到食物
bindKey();
}
// 通过按键控制蛇吃食物
function bindKey() {
// 给文档绑定键盘按下事件
document.onkeydown = function (e){
// console.log(e.keyCode);
// 上:38 下:40
// 右:39 左:37
// var dir = that.snake.direction;
switch(e.keyCode){
case 38:
that.snake.direction = "top";
break;
case 40:
that.snake.direction = "bottom";
break;
case 39:
that.snake.direction = "right";
break;
case 37:
that.snake.direction = "left";
break;
}
};
}
// 蛇运动
function snakeRun(){
var timer = setInterval(function (){
// 1.蛇往前走(定时器)
// this.snake.move();
// 移除之前渲染的蛇
// this.snake.remove(this.stage);
// 渲染蛇
// this.snake.render(this.stage);
// 此时出现了问题:上一次渲染的蛇和这次渲染的重叠了
// 正常来说,应该:这次渲染后,上次的就应该消失
// 直接将代码粘贴过来执行,行不通,因为此时的this不再是game实例对象
that.snake.move();
that.snake.remove(that.stage);
that.snake.render(that.stage);
// 每次移动都需要判断下,是否到达边界
var maxX = that.stage.offsetWidth / that.snake.width;
var maxY = that.stage.offsetHeight / that.snake.height;
// 待判断的数值
var headX = that.snake.body[0].x;
var headY = that.snake.body[0].y;
// 每次移动一个新位置的时候,都需要判断下是否吃到了食物
// 即:蛇头和食物的位置是否一致
// var foodX = that.food.x;
// var foodY = that.food.y;
var hX = headX * that.snake.width;
var hY = headY * that.snake.height;
// 有三个食物,因此需要for循环遍历
for(var i = 0; i < that.food.elements.length; i++){
if(that.food.elements[i].offsetLeft == hX && that.food.elements[i].offsetTop == hY){
// 清除食物
// 问题:当食物的数量增加时,就不能固定传下标0
that.food.remove(that.stage,i);
// 再随机生成一个食物
that.food.render(that.stage);
// 添加一个新的蛇节
var last = that.snake.body[that.snake.body.length - 1];
that.snake.body.push({
x:last.x,
y:last.y,
color:last.color
});
}
}
// 2.走到边界,游戏结束
if(headX < 0 || headX >= maxX || headY < 0 || headY >= maxY){
// 停止定时器
clearInterval(timer);
alert("Game over");
}
},150);
}
// 将构造函数通过window暴露
window.Game = Game;
})(window,undefined);
// ==================main================
// 将执行代码放在单独的js页面中
(function (window,undefined){
var stage = document.getElementById("stage");
var game = new Game(stage);
game.start();
})(window,undefined);
// 压缩文件会:1.去除注释、空格、换行 2.简化标识符
发表评论
还没有评论,快来抢沙发吧!