本着好记性不如烂博客以及分享成功的喜悦和分享失败的苦楚,今天我来分享下一个练手项目:打飞机游戏~从小就自己想做游戏,可是一直没有机会。HTML5给了我们这个平台,这个平台可以有很多以前想都不敢想的东西存在,让我们怀着感激与敬畏的心情,开始我们HTML5的旅程。

1 需求:

这个不多说了,玩儿过雷电的同学都知道,有己机,敌机,还有子弹,当然BOSS也算但是BOSS也是敌机了。

2 分析:

2.1 开发工具: 采用HTML5+JS来完成,JS面向对象虽然有点复杂,但是很好用。采用Function作为对象的载体,返回的是Object(后面会详细讲到,这里只是先说说开发工具),采用HTML5的canvas对象作为表现层。

2.2 游戏原理:大家都喜欢打灰机吧,可是谁知道打灰机是怎么实现的呢?且容我抽丝剥茧,宽衣解带,沐浴更衣,慢慢道来~ 大体的思路是,图片一帧一帧地更新自己在canvas上的位置,利用人眼的缓存实现动画的效果。在更新图片之前更新坐标,在更新坐标之前更新状态。

2.3 游戏架构: 采用mvc模式,这种模式应该大家都不陌生,Model层就是在屏幕上显示的元素它的实体类,实体类有自己的方法包括更新自己的坐标,发生碰撞时的反应等,它也有自己的私有变量,比如坐标,图片,爆炸效果图片,碰撞体积,等等。view层就是显示层,它只根据返回的图片更新到画布上与业务无关。Service层,Control层与Model层之间的一层,它拥有所有要在屏幕上显示的实体(除了背景)的引用,处理Model层返回的事件,并缓存View层上要显示的内容,经过处理后返回要更新的图片信息给Control层。Control层,它会处理游戏中的事件,连接view层,并且会更新游戏的进度。它调用service,得到返回结果更新view层。

思路介绍完了,下面将介绍打灰机的具体实现了。

3.Model层

从己机,敌机,子弹中提取它们的共同点:它们都可以飞,,,,因此我把它们的父类叫fly,然后竭尽脑汁去想它们其他的共同点:它们都可以爆炸,它们会发生碰撞,它们都会移动,它们都可能会超出边界。目前我想到的就这么多了。但是它们的私有变量却都是大同小异的。它们都有一个hp属性代表血值,有个x代表x坐标,有个y代表y坐标,有个图片的引用表示要在屏幕上显示的图片,有个图片的引用表示要显示爆炸的图片,有个目标表示自己的愿景:是要消灭向上飞的呢还是要消灭向下飞的还是除了自己要消灭一切,不管怎么说得有个愿景。因此,把这个类抽象出来作为所有要表现在屏幕上的飞行物的父类:fly。我们看下它的继承关系(由于没太多接触过类图,画的粗糙,大家对付着过吧,聊胜于无。)右边的是属性和方法,属性是一个javascript中的Object

HTML5学习+javascript学习:打飞机游戏简介以及Model层

 

这里我们先给出fly和bullet的代码,其他的就自己扩展

fly.js:

  1 /*飞行物类,不管敌机,boss,己机,还是子弹,都是飞行物,它们都有改变位置,碰撞,越界判定的方法,也都有碰撞体积,图像,坐标,序号和hp等属性。具体参数如下:
  2 *var spec={
  3     x:1,画布中的横坐标
  4     y:CANVAS_HEIGHT-5,//画布中的纵坐标
  5     hp:1,//飞行物的血量
  6     index:svc.total(),//序列号
  7     exploreImg:getImg("img/blasts3.png"),//爆炸的图片
  8     img:getImg("img/mybullet2.png"),//自身图片
  9     target:0,//目标
 10     conflictSquare:20,//碰撞体积
 11     speedX:5,//X方向的速度
 12     speedY:2,//Y方向的速度
 13     movex:0,//X轴移动的方向
 14     movey:0//Y轴移动的方向
 15     };
 16 *这个对象完全可以用json来代替!可扩展性就不多说了。
 17 *它大多数方法(除了对私有变量访问的方法)均返回数组格式为:[{func:'',params:[]},{func:'',params:[]}]看到了什么?很像json吧?不过不是用json来调用的,
 18 *它是在控制层(service)接收,然后把有用的信息注册到一个array中来管理,根据func这个字段的不同,调用不同的方法。对象有index,删除的时候就可以很方便的删掉了。
 19 *整体来说,这个fly类是所有用到的模型的父类,
 20 *这种以方法(function)来构建对象的形式是安全地,因为它返回的是有很多属性的对象(return{a:function(){},b:function(){},,,,};),对象的所有内容均可访问其私有变量,
 21 *但是脚本不能在外部访问其自身私有变量。这对于透明性和扩展性都是有好处的。
 22 */
 23 var fly=function(spec){
 24     
 25     var imgWidth=spec.img.width;//图片的宽度,该变量是私有变量,当fly这个函数完蛋了,它的私有变量仍然可以访问,神奇吧,这就是javascript的魅力。
 26         var imgHeight=spec.img.height;//图片的高度
 27     return {
 28     move:function(directionX,directionY){//根据方向来移动0,1表示Y轴向下。1,0表示X轴向右,依次类推
 29         
 30     
 31         spec.x=(directionX||0)*spec.speedX+spec.x;//水平移动,任何移动的类型,只要不要太变态,应该都能调用这个方法来改变坐标,如果有不能调用的再说。
 32             
 33         spec.y=(directionY||0)*spec.speedY+spec.y;//垂直移动,其他同上。
 34 
 35         },
 36     hpChange:function(newHp){//碰撞了,血量改变了或者飞机挂掉了就要用到这个方法改变私有变量的值
 37         spec.hp=newHp;
 38     },
 39     x:function(){//得到画布中的x坐标
 40         return spec.x;
 41     },
 42     y:function(){//得到画布中的y坐标
 43         return spec.y;
 44     },
 45     hp:function(){//血量,访问私有属性血量要用到这个方法
 46         return spec.hp;
 47     },
 48     index:function(){//这个序列属性是用来删除自己的,使用array的splice方法,然后我们再做些手脚,就可以让index和array的序列相同啦。
 49         return spec.index;
 50     },
 51     setIndex:function(ndex){//这个方法用来改变自身的序列
 52         spec.index=ndex;
 53     },
 54     exploreImg:function(){//得到爆炸的图片,如果爆炸事件发生,图片被缓存,之后与其他内容一起画到画布上去。
 55         return spec.exploreImg;
 56     },
 57     img:function(){//得到自身图片,之后在控制层(service)缓存
 58         return spec.img;
 59     },
 60     target:function(){//得到敌对目标的编号,敌飞行物和友飞行物当然不能一样了。
 61         return spec.target;
 62     },
 63     width:function(){//图片的宽度
 64         return spec.img.width;
 65     },
 66     height:function(){//图片的高度
 67         return spec.img.height;
 68     },
 69     cflctSqr:function(){//碰撞体积(是一个正方形,这个方法得到碰撞体积的边长)
 70         if(spec.conflictSquare){
 71             return spec.conflictSquare;
 72         }
 73         return 0;
 74     },
 75     onConflict:function(other){//事件,后台判断碰撞,当碰撞发生时就调用这个方法,返回一个事件交给控制层处理。
 76         //alert(spec.hp+"  "+other.hp());
 77         if((spec.hp>0) && (other.hp()>0)){//如果碰撞接收者和碰撞发出者的hp属性都大于零,才能进行碰撞,碰撞的结果就是它们的血值相减
 78             //这又造成了有一个血值变为了零或负,或者两者同归于尽
 79             //dispear 这个事件代表消失,即如果一个飞行物被干掉了,我就让后台来处理这个被干掉的家伙的后事,把它从生龙活虎的队列中删除~
 80             //disapear函数可以接受多个参数,这个参数是一个数组,数组包含多个消失的对象(当然一般都是只会消失一个对象)。
 81             
 82             var hpo=other.hp();//hpo就是传入的参数的hp,两个飞行物碰撞,就假设一个是主动者,这样好分析点。
 83             var hps=spec.hp;//hps是被碰的hp
 84             spec.hp -= hpo;//被碰的hp减去碰撞的hp
 85             other.hpChange(other.hp()-hps);//碰撞的hp再减去被碰的hp这两件事造成了如下的代码来判断碰撞双方的状态
 86             var returnArray=[];//返回一个事件的集合先给他初始化为一个数组
 87             //alert(spec.hp+" "+other.hp());
 88             if(spec.hp == 0 && other.hp() == 0){//如果同归于尽,两者都爆炸,两者都消失
 89             returnArray=[];
 90             returnArray.push({func:"explore",params:[other.exploreImg(),other.x(),other.y()]});//碰撞发起的爆炸事件
 91             returnArray.push({func:"explore",params:[spec.exploreImg,spec.x,spec.y]});//碰撞接受者的爆炸事件
 92             returnArray.push({func:"disapear",params:[spec.index]});//碰撞接收者的消失事件入栈
 93             returnArray.push({func:"disapear",params:[other.index()]});//碰撞发起者的消失事件入栈
 94             
 95             
 96             return returnArray;
 97             
 98             
 99             }else if((spec.hp <=0) && (other.hp()>0)){//如果碰撞发起者它的hp远比接受者要大,那么接收者爆炸,消失
100             returnArray=[];
101             returnArray.push({func:"explore",params:[spec.exploreImg,spec.x,spec.y]});
102             returnArray.push({func:"disapear",params:[spec.index]});
103             return returnArray;
104 
105             }else if((spec.hp > 0) && (other.hp()<=0)){//如果碰撞发起者它的hp小于接受者,那么碰撞发起者爆炸,消失。
106             returnArray=[];
107             returnArray.push({func:"explore",params:[other.exploreImg(),other.x(),other.y()]});
108             returnArray.push({func:"disapear",params:[other.index()]});
109             return returnArray;
110             }else{//这种情况我不知道为什么可以发生,因此不打算处理这种情况,但为了保险起见返回一个undefined。
111             return ;    
112             }
113         }
114         
115     },
116     judgeBundle:function(){//超出边界把它揪回来。
117         
118         if(spec.x<=0){//如果横坐标超出了左边的界限,揪回来。
119             spec.x=0;
120         }else if(spec.x>=(CANVAS_WIDTH-imgWidth)){//如果横坐标大于画布边界与图片边界的差就是到了边缘
121             
122             spec.x=CANVAS_WIDTH-imgWidth;//让它等于边界的值
123             
124         }
125         if(spec.y<=0){//和x轴类同。
126             spec.y=0;
127         }else if(spec.y>=CANVAS_HEIGHT-imgHeight){
128             spec.y=CANVAS_HEIGHT-imgHeight;
129         }
130     },
131     onMove:function(){//onMove:移动方法->judgeBundle->move,此处只是循环的移动。,当然也可以自己定义移动方法,但要在子类中定义了。
132         
133         if(spec.y>CANVAS_HEIGHT-imgHeight||spec.y<0){//如果到了最上边,或者最下边就向相反方向移动
134             spec.movey=-spec.movey;
135             
136         }
137         if(spec.x>(CANVAS_WIDTH-imgWidth)||spec.x<0){//类似于上面的判断
138             spec.movex=-spec.movex;
139             
140         }
141         this.judgeBundle();//判断是否超出边界
142         this.move(spec.movex,spec.movey);//移动
143         
144         return {type:"drawimg",func:"drawimg",params:[spec.img,spec.x,spec.y]};//移动了,把移动事件返回去,缓存图片和坐标,最后一起画出来。
145     }
146     };
147     };
fly.js

相关文章:

  • 2021-11-05
  • 2021-08-17
  • 2021-06-03
  • 2022-12-23
  • 2022-12-23
  • 2021-06-07
  • 2021-09-05
  • 2021-08-10
猜你喜欢
  • 2022-12-23
  • 2021-07-22
  • 2021-08-18
  • 2021-12-31
  • 2021-10-24
  • 2022-12-23
  • 2021-11-24
相关资源
相似解决方案