面向对象案例——贪吃蛇游戏

最近项目上线,近一个星期没更博了,今天来写一个经典的游戏案例——贪吃蛇。在这个简单的案例里可以体会javaScript 面向对象开发相关模式,学习使用面向对象的方式分析问题。

1.功能实现

1.1 搭建页面:放一个容器盛放游戏场景 div#map,设置样式

<div class="map" id="map"></div>
 1 <style>
 2         #map{
 3             background-color: #000;
 4             width: 1500px;
 5             height: 700px;
 6             position: relative;
 7             left: 0;
 8             top: 0;
 9         }
10 </style>

1.2 分析对象:食物对象、蛇对象、游戏对象

1.3 创建食物对象Food

⑴ 属性:位置(x,y)、大小(width、height)、颜色(color)

1 // 创建Food的构造函数,并设置属性
2 function Food(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 function Snake(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 function Game(map) { 
3     this.map=map;
4     this.snake=new Snake();
5     this.food=new Food();
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 function autoMove() { 
 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 function remove(map) {
2     for (var i = 0; i < list.length; i++) {
3         map.removeChild(list[i]);
4     }
5     list.length=0;
6 }

⑶ 在game中通过键盘控制蛇的移动方向

 1 function keyBind() { 
 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.y
12     });
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 }())

猜你喜欢

转载自www.cnblogs.com/linqb/p/9420750.html