最近项目上线,近一个星期没更博了,今天来写一个经典的游戏案例——贪吃蛇。在这个简单的案例里可以体会javaScript 面向对象开发相关模式,学习使用面向对象的方式分析问题。
1.功能实现
1.1 搭建页面:放一个容器盛放游戏场景 div#map,设置样式
1
3 background-color:#000;
4 width:1500px;
5 height:700px;
6 position:relative;
7 left:0;
8 top:0;
9 }
10
1.2 分析对象:食物对象、蛇对象、游戏对象
1.3 创建食物对象Food
⑴ 属性:位置(x,y)、大小(width、height)、颜色(color)
1 //创建Food的构造函数,并设置属性
2 functionFood(width,height,bgColor) {3 //食物的宽度和高度(像素)
4 this.width=width||10;5 this.height=height||10;6 //食物的颜色
7 this.bgColor=bgColor||"white";8 }
⑵ 方法:render() 随机创建一个食物对象,并输出到map上
1 //通过原型设置render方法,实现随机产生食物对象,并渲染到map上
2 Food.prototype.render=function(map) {3 remove(map);4 //随机食物的位置,map.宽度/food.宽度,总共有多少分food的宽度,随机一下。然后再乘以food的宽度
5 this.x=Math.floor(Math.random()*(map.offsetWidth/this.width))*this.width;
6 this.y=Math.floor(Math.random()*(map.offsetHeight/this.height))*this.height;
7 //动态创建食物对应的div
8 var newDiv=document.createElement("div");9 newDiv.style.position="absolute";10 newDiv.style.left=this.x+"px";11 newDiv.style.top=this.y+"px";12 newDiv.style.backgroundColor=this.bgColor;13 newDiv.style.width=this.width+"px";14 newDiv.style.height=this.height+"px";15 map.appendChild(newDiv);16 li.push(newDiv);17 }
1.4 创建蛇对象Snake
⑴ 属性:大小(width、height)、颜色(color)、方向(direction)、身体数组对象(body)
1 //Snake构造函数
2 functionSnake(width,height,bgColor,direction) {3 //设置每一个蛇节的宽度
4 this.width=width||10;5 this.height=height||10;6 this.bgColor=bgColor||"white";7 //蛇的运动方向
8 this.direction=direction||"right";9 //蛇的每一部分, 第一部分是蛇头
10 this.body=[11 {x:3,y:1},12 {x:2,y:1},13 {x:1,y:1}14 ];15 }
⑵ 方法:render() 把蛇渲染到map上
1 //render方法,原理与渲染食物相同
2 Snake.prototype.render=function(map) {3 remove(map);4 for (var i = 0; i < this.body.length; i++) {5 var newDiv=document.createElement("div");6 newDiv.style.position="absolute";7 newDiv.style.left=this.body[i].x*this.width+"px";8 newDiv.style.top=this.body[i].y*this.height+"px";9 newDiv.style.width=this.width+"px";10 newDiv.style.height=this.height+"px";11 newDiv.style.backgroundColor=this.bgColor;12 map.appendChild(newDiv);13 list.push(newDiv);14 }15 }
1.5 创建游戏对象Game(用来管理游戏中的所有对象和开始游戏)
⑴ 属性:food、snake、map
1 //Game构造函数
2 functionGame(map) {3 this.map=map;4 this.snake=newSnake();5 this.food=newFood();6 that=this;7 }
⑵ 方法:start() 开始游戏(绘制所有游戏对象)
1 //开始游戏,渲染食物对象和蛇对象
2 Game.prototype.startGame=function() {3 this.food.render(this.map);4 this.snake.render(this.map);5 autoMove();6 keyBind();7 }
//在自调用函数中暴露Game对象
window.Game=Game;
2.游戏逻辑
2.1 蛇的move方法
⑴ 在蛇对象(snake.js)中,在Snake的原型上新增move方法
⑵ 让蛇移动起来,把蛇身体的每一部分往前移动一下
⑶ 蛇头部分根据不同的方向决定 往哪里移动
1 Snake.prototype.move=function(food,map) {2 //让蛇身体的每一部分往前移动一下
3 for (var i = this.body.length-1; i >0; i--){4 this.body[i].x=this.body[i-1].x;5 this.body[i].y=this.body[i-1].y;6 }7 //根据移动的方向,决定蛇头如何处理
8 switch (this.direction) {9 case "left":10 this.body[0].x--;11 break;12 case "right":13 this.body[0].x++;14 break;15 case "up":16 this.body[0].y--;17 break;18 case "down":19 this.body[0].y++;20 break;21 default:22 break;23 }24 }
//在game中测试
this.snake.move(this.food, this.map);this.snake.render(this.map);
2.2 让蛇自己动起来
⑴ 在game.js中 添加autoMove的私有方法,开启定时器调用蛇的move和render方法,让蛇动起来(私有方法即不能被外部访问的方法,使用自调用函数包裹)
1 functionautoMove() {2 var timeId=setInterval(function() {3 this.snake.move(this.food,this.map);4 this.snake.render(this.map);5 //判断蛇是否撞墙
6 var snakeHeadX=this.snake.body[0].x*this.snake.width;7 var snakeHeadY=this.snake.body[0].y*this.snake.width;8 if(snakeHeadX<0 || snakeHeadY<0 || snakeHeadX>=this.map.offsetWidth || snakeHeadY>=this.map.offsetHeight){9 clearInterval(timeId);10 alert("Game over!");11 }12 }.bind(that),50);13 }
⑵ 在snake中添加删除蛇的私有方法,在render中调用
1 functionremove(map) {2 for (var i = 0; i < list.length; i++) {3 map.removeChild(list[i]);4 }5 list.length=0;6 }
⑶ 在game中通过键盘控制蛇的移动方向
1 functionkeyBind() {2 window.onkeydown=function(e) {3 e=e||window.event;4 e.keyCode= e.keyCode|| e.charCode||e.which;5 //console.log(e.keyCode);
6 switch(e.keyCode) {7 case 37:8 if(this.snake.direction!="right"){9 this.snake.direction="left";10 }11 break;12 case 38:13 if (this.snake.direction != "down") {14 this.snake.direction = "up";15 }16 break;17 case 39:18 if (this.snake.direction != "left") {19 this.snake.direction = "right";20 }21 break;22 case 40:23 if (this.snake.direction != "up") {24 this.snake.direction = "down";25 }26 break;27 default:28 break;29 }30 }.bind(that);31 }
⑷ 在start方法中调用keyBind()
2.3 判断蛇是否吃到食物
在Snake的move方法中添加判断
1 //在移动的过程中判断蛇是否吃到食物
2 var snakeHeadX=this.body[0].x*this.width;3 var snakeHeadY=this.body[0].y*this.height;4 var snakeTile=this.body[this.body.length-1];5 //如果蛇头和食物的位置重合代表吃到食物
6 if(snakeHeadX==food.x && snakeHeadY==food.y){7 //吃到食物,往蛇节的最后加一节
8 this.body.push({9 //食物的坐标是像素,蛇的坐标是几个宽度,进行转换
10 x:snakeTile.x,11 y:snakeTile.y12 });13 //把现在的食物对象删除,并重新随机渲染一个食物对象
14 food.render(map);15 }
★ ★ 自调用函数的参数
1 (function(window, undefined) {2 var document =window.document;3 }(window, undefined))
⑴ 传入window对象:代码压缩的时候,可以把function (window) 压缩成 function (w)
⑵ 传入undefined:把undefined作为函数的参数(当前案例没有使用) ,防止undefined 被重新赋值,因为在有的老版本的浏览器中 undefined可以被重新赋值
★ ★ 关于自调用函数的问题
⑴ 如果存在多个自调用函数要用分号分割,否则语法错误
1 //下面代码会报错
2 (function() {3 }())4
5 (function() {6 }())7 //所以代码规范中会建议在自调用函数之前加上分号
8 //下面代码没有问题
9 ;(function() {10 }())11
12 ;(function() {13 }())
⑵ 当自调用函数前面有函数声明时,会把自调用函数作为参数
1 //所以建议自调用函数前,加上;
2 var a = function() {3 alert(‘11‘);4 }5
6 (function() {7 alert(‘22‘);8 }())