2.Service层
如前所述,Service层是Control层与Model层之间桥接的一层,它拥有所有要在屏幕上显示的实体(除了背景)的引用
我们知道,当游戏运行时,随时都可能发生碰撞,随时都可能发生飞行物的消失。而我们要想判定飞行物的状态,就必须把所有的飞行物都遍历一遍方可,那么我们要把所有的没消失的飞行物都放在一起。但是如果这些飞行物放在一起都放在control层里,control层会显得很庞大,又要处理飞机的销毁,又要给返回图片。然后如果我们想通过服务器根据条件获得新的飞机,我们是不是又要在control层中添加事件,添加代码。那control层会显得很乱。所以,保险起见,我们在control层和model层之间桥接了一个service层,它就只负责处理飞行物销毁和子弹发射等已有的事件,提供一些可供control层访问的接口(事件其实就是方法),并把图像返回到control层去,将来如果我们从服务器得到更多的飞机要来,那就把这些飞机给传到control层,然后由control层来调用service的接口就好啦。也就是把control层分成两部分,一部分处理业务,一部分处理显示。
我们已经介绍了Model层,我们知道model层的方法是由service来调用的,Model层的所有方法(除了访问其私有变量的方法外)均会返回一个array,而调用方法返回的这个array是如何被service抓取又是如何被service处理的呢?我们又如何来设计这个service层呢?
首先要有一个飞行物的数组来存放所有的飞行物,同时要有一个数组来缓存事件,另外一个数组来缓存图片。然后要有一个接口的方法update,之后要有一个draw方法来返回给control层更新图像的图片和坐标,还要有一些事件来处理Model返回的事件,其流程为:
update:1.遍历model,先判断model是否有可能发生碰撞,如果有(即碰撞体积有重合的部分),那么就调用model的碰撞方法,将返回的结果缓存到注册表中,然后调用draw方法(因为有可能新增飞行物,飞行物消失等改变飞行物数组的事件出现因此不可以一并处理,设想这样:如果一个飞行物已经消失了,还调用它的移动方法是没有意义的 )2.遍历model调用它们的移动方法,更新它们的坐标,将返回的内容缓存到注册表中(因为如果出了边界也有可能出现消失,发射炮弹也有可能新增飞行物)。
draw:将注册表的事件逐个取出,执行,并返回一个图像的数组
下面给出service的代码
service.js
/* *后台控制层类,完成model层和view层的控制和数据传递服务,它通过不停地调用自己的私有数组的特有方法,收集这些方法返回的 *事件,放到注册表中,最后一并处理:调注册表中的事件,事件会更新自己的私有数组内容,并将欲传递给view层显示的图片信息缓存。 */ var service=function(){ var registry=[];//存放注册时间的列表,是model层返回的内容 var bulletNum=0;//只是暂时存储子弹的数目。 var flies=[];//飞行物的列表,私有数组,遍历访问其方法,返回的事件就存储在registry中 var images=[];//用于缓存显示图片的数组:{img,x,y} var is_array = function(value) {//判断值是否为数组 return value && typeof value === 'object' && value.constructor === Array; } function registEvent(regist){//注册事件,参数为事件数组/事件对象 if(regist) { // 如果是数组,则调用concat方法把方法名和参数推进注册表中 if(is_array(regist)){ registry=registry.concat(regist); } else{ //如果这个方法名和数组返回了不可以识别的值,则什么都不做否则用push方法压入注册表。 if(typeof regist!=='undefined'){ registry.push(regist); } } } } function peng(fly1,fly2){//碰撞判断 //sqr1,sqr2表示碰撞体的左上角坐标和右下角坐标。即,从图片的中心开始,上下左右均为碰撞边长的1/2 var sqr1={ x1:fly1.x()+fly1.width()/2-fly1.cflctSqr()/2, x2:fly1.x()+fly1.cflctSqr()/2+fly1.width()/2, y1:fly1.y()+fly1.height()/2-fly1.cflctSqr()/2, y2:fly1.y()+fly1.cflctSqr()/2+fly1.height()/2 }; var sqr2={ x1:fly2.x()+fly2.width()/2-fly2.cflctSqr()/2, x2:fly2.x()+fly2.cflctSqr()/2+fly2.width()/2, y1:fly2.y()+fly2.height()/2-fly2.cflctSqr()/2, y2:fly2.y()+fly1.cflctSqr()+fly2.height()/2 }; var x1=sqr1.x1<sqr2.x1?sqr1.x1:sqr2.x1; var x2=sqr1.x2>sqr2.x2?sqr1.x2:sqr2.x2; var y1=sqr1.y1>sqr2.y1?sqr1.y1:sqr2.y1; var y2=sqr1.y2<sqr2.y2?sqr1.y2:sqr2.y2; var sqr=fly1.cflctSqr()+fly2.cflctSqr();//是碰撞边长之和 if((x2-x1)<sqr&&(y1-y2)<sqr){//如果最右边的横坐标减去最左边的横坐标小于两个飞行物碰撞边长之和,而且纵坐标也如此,那么碰撞成立。 //if((typeof fly1.isbullet !== 'undefined' && typeof fly2.isbullet==='undefined')||(typeof fly2.isbullet !== 'undefined' && typeof fly1.isbullet==='undefined')){ //var ale="sqr1:x1:"+sqr1.x1+"y1:"+sqr1.y1+"x2:"+sqr1.x2+"y2:"+sqr1.y2+" "; //ale+="fly1:"+"x:"+fly1.x()+"y:"+fly1.y()+""+fly1.width(); //alert(ale); //} return true; } return false; } return {//上面的都是私有方法和变量,下面是返回值。 total:function(){//求出飞行物的总数,在添加新的飞行物时会用到这个函数。 return flies.length; }, spliceflies:function(ndex){//删除一个对应序列号的飞行物,并更新飞行物数组中被改变下表的元素的序号。 flies.splice(ndex,1); var i; for(i=ndex;i<flies.length;i++) { flies[i].setIndex(i);//从删除的那个起,数组元素的序列号均与数组下标保持一致 } }, conflict:function(){//判断是否有碰撞的必要,并检测是否碰撞成功 var length=flies.length;//飞行物的个数 var i=0;//遍历被碰者 var j=0;//遍历碰撞发起者确保飞行物中所有的元素均两两配过对 if(length>=2){//小于2不会发生碰撞 for(i=0;i<length;i+=1){ for(j=i+1;j<length;j+=1){//自己也不会跟自己碰撞,自己之前的已经判定过了。 if(flies[i].target() !== flies[j].target()){//如果目标不同,就是敌人,就有‘血拼’或者拼血的必要 if(peng(flies[i],flies[j])){//判断它们是否确实会发生碰撞 var regist=flies[i].onConflict(flies[j]);//调用飞行物的碰撞方法。 registEvent(regist);//把碰撞方法的返回事件压入注册表中 // var q; //if(regist){ //for(q=0;q<regist.length;q++){ // alert(regist[q].func+" "+regist[q].params[0]+" "+regist[q].params[1]); //}} } } } } } }, update:function(){//更新,先看是否会发生碰撞,如果碰撞,让碰撞应该消失的消失,然后再更新仍存在于flies中的元素的位置。 //registry= getSameNameAndInitParams(registry,'disapear','func'); images.length=0;//图像清空 this.conflict();//碰撞判断 this.draw();//把注册表中的事件逐个取出,看是否需要更新flies数组(包括添加,删除) var length=flies.length; var i=0; for(i=0;i<length;i++){//然后根据每个飞行物的移动方法不同,分别改变各个飞行物的坐标 var regist=flies[i].onMove(); registEvent(regist);//注册移动后的事件,主要是把图片压到缓存区去。 } }, disapear:function(){//消失事件,碰撞成功导致飞行物损毁,就调用这个事件 var len=arguments.length; var i; for(i=0;i<len;i+=1){ //alert("splice"); //flies.splice(arguments[i],1); this.spliceflies(arguments[i]);//把应该消失的飞行物从飞行物数组中移除。 } //alert(flies.length); }, explore:function(img,x,y){//爆炸,所有飞行物通过碰撞消失之前都会爆炸 //alert("explore"); this.drawimg(img,x,y); }, drawimg:function(img,x,y){//把飞行物的图片压入缓存 images.push({img:arguments[0],x:arguments[1],y:arguments[2]}); }, draw:function(){//就是把注册表中的事件一个个取出然后执行,最后返回缓存区的图像。 //var q; //for(q=0;q<registry.length;q++){ // var s; // if(registry[q].func !== 'drawimg'){ // for(s=0;s<registry[q].params.length;s+=1){alert(registry[q].func+" "+registry[q].params[s]); // } //}} var handle=registry.pop(); while(typeof handle !== 'undefined'){ var fuc=handle.func; //alert(fuc); //if(fuc=='disapear'){ //alert(flies.length); //} fuc=this[fuc]; fuc.apply(this,handle.params);//第一个参数:上下文,第二个参数:传入函数的参数 handle=registry.pop(); } return images; }, shoot:function(spec){//事件:射击,飞行物数组中压入选手的炮弹,这个选手的炮弹 bullet=playerbullet(spec); bullet.setIndex(this.total()); flies.push(bullet); bulletNum+=1; }, reduceBulet:function(){//就是改变bulletNum这个变量的值。 bulletNum-=1; //alert(bulletNum); }, newfly:function(flier){//新飞行物 //alert(flier.index()); flier.setIndex(this.total()); flies.push(flier); }, newplane:function(){//这个以及下面的几个方法都是可有可无的,只是作为例子来看。 var plane={ x:Math.random()*CANVAS_WIDTH, y:Math.random()*CANVAS_HEIGHT, hp:10, index:flies.length, exploreImg:getImg("img/blasts3.png"), img:getImg("img/dplayerplane.png"), target:0, conflictSquare:20, speedX:5, speedY:5, movex:-1, movey:-1 }; var pl=fly(plane); flies.push(pl); }, newenemy:function(){ var plane={ x:Math.random()*CANVAS_WIDTH, y:Math.random()*CANVAS_HEIGHT, hp:1, index:flies.length, exploreImg:getImg("img/blasts3.png"), img:getImg("img/amay.png"), target:1, conflictSquare:7,//碰撞体积 speedX:5, speedY:5, movex:1, movey:-1 }; var pl=fly(plane); flies.push(pl); }, newbullets:function(){ var plane={ x:Math.random()*CANVAS_WIDTH, y:Math.random()*CANVAS_HEIGHT, hp:1, index:flies.length, exploreImg:getImg("img/bossbullet2.png"), img:getImg("img/bossbullet3.png"), target:1, conflictSquare:2,//碰撞体积 speedX:5, speedY:-5, movex:1, movey:-1 }; var pl=bullets(plane); flies.push(pl); } }; };
background.js
/*背景图片的类,其参数如下:backParam:{ speedY:5; imgHeight:4即背景图片1帧的高度;}*/ var $background=function(backParam){ var totalHeight;//图片总高度 var imgWidth;//图片宽度 var speedPrcnt=backParam.imgHeight/CANVAS_HEIGHT;//图片高度与画布高度的比例,移动背景时根据这个比率求出背景图片的纵坐标(背景移动速度=比率*画布移动实际速度) var imgY=0;//背景图片的纵坐标 var img;//背景图片,通过getImg(url)方法得到,该方法调用了var img=new Image();img.src=url; return { //设置背景图片,包括图片的URL,图片高度和图片宽度。 setImg:function(url,totalHeights,imgWidths){ img=getImg(url); totalHeight=totalHeights; imgWidth=imgWidths; }, update:function(){//更新背景图片的坐标(不是画布的坐标,画布坐标不变的。),planestage里有个update方法,会调用这个。 imgY=imgY+(backParam.speedY)*speedPrcnt;//背景图片的纵坐标为之前的纵坐标加上纵坐标每帧移动数 if(imgY>totalHeight){//当背景图片的纵坐标大于其实际高度时,纵坐标归零;当纵坐标小于0时,纵坐标变为最大的高度这个是当背景朝不同方向运动时会调用。 imgY=0; }else if(imgY<0){ imgY=totalHeight; } return; }, draw:function(canvas){//画的动作,planestage有同样名称的方法,会调用这个方法。 canvas.drawImage(img,0,imgY,imgWidth,backParam.imgHeight,0,0,CANVAS_WIDTH,CANVAS_HEIGHT);//在画布上绘出背景图形参数表示的意思分别为:图片,图片横坐标,图片纵坐标,一帧对应的背景图片宽度,一帧对应的背景图片高度,画布横坐标,画布纵坐标,画布宽度,画布高度。 } }; }
注释还算清晰吧。
control层接收service层的draw方法返回的图片数组,并调用canvas的方法来更新图片的显示。我们先来看看Control层的代码
var CANVAS_WIDTH=800;//全局变量:画布宽度 var CANVAS_HEIGHT=500;//全局变量:画布高度 var $planestage=function(){//飞行台,目前把背景和service分开,service是调用飞行物注册事件的,而背景没那么多方法。 var canvas;//画布,用来显示图形。 var that=this;//上下文,用来取代this var detailArray;//数组,用来将背景和service等拥有update和draw方法的数据压入,这样控制台就可以通过遍历来调用了 function initDetails(){ detailArray=detailArray||[];//做初始化,数组如未定义,则初始化为一个数组 } return { push:function(s){//将背景/service等具有update和draw方法的对象压入detailArray中。 initDetails(); detailArray.push(s); }, update:function(){//更新:清空画布,遍历detailArray,调用每个元素的update方法, canvas.clearRect(0,0,CANVAS_WIDTH,CANVAS_HEIGHT); initDetails(); $.each(detailArray,function(i,item){ item.update(); }); }, draw:function(){//画,在画布上显示图片 $.each(detailArray,function(i,item){ var imges=item.draw(canvas);//如果是背景那么背景会直接在canvas上画,如果是service,service的这个方法返回了包含很多图像对象的数组。 if(typeof imges !== 'undefined'){//item是service var len=imges.length; var i=0; for(i=0;i<len;i+=1) {//遍历,绘画。 canvas.drawImage(imges[i].img,imges[i].x,imges[i].y) } } }); }, start:function(){//30帧/s的循环调用update和draw方法。 var FPS = 30; var that=this; setInterval(function() { that.update(); that.draw(); }, 1000/FPS); }, stop:function(){ }, pause:function(){ }, initCanvas:function(){//初始化canvas元素,宽度和高度是上面的全局变量,将其添加到body元素,注意canvas是context的引用 var canvasElement = $("<canvas width='" + CANVAS_WIDTH + "' height='" + CANVAS_HEIGHT + "'></canvas>"); canvas = canvasElement.get(0).getContext("2d"); canvasElement.appendTo('body'); } }; }(); /** * *一个全局方法,通过url得到图片。 */ function getImg(url){ var img=new Image() img.src=url; return img; } /** *一下内容好删掉了,只是作为测试的内容。 * */ var $text=function(){ var textX = 50; var textY = 50; return { update:function() { textX += 1; textY += 1; }, draw:function(canvas) { canvas.fillStyle = "#000"; canvas.fillText("Sup Bro!", textX, textY); } }; }();
使用的时候,只要把background和service这两个service压入到control层的detailArray数组中,然后调用这两个service对应的方法来增加Model或者改变背景,就实现了大体的内容啦这个control层目前只能本地运行,扩展的话,此处只是练手,以后有时间再说吧。最后给出调用这些内容的网页:
<!DOCTYPE HTML > <html> <head> <title> New Document </title> <meta name="Generator" content="EditPlus"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <script type="text/javascript" src="jquery-1.3.1.js"></script> <script type="text/javascript" src="js/fly.js"></script> <script type="text/javascript" src="js/bullets.js"></script> <script type="text/javascript" src="js/movition.js"></script> <script type="text/javascript" src="js/background.js"></script> <script type="text/javascript" src="js/planestage.js"></script> <script type="text/javascript" src="js/service.js"></script> <script type="text/javascript" src="js/friendplane.js"></script> <script type="text/javascript"> /*$myplane=function(){ var movex=0; var movey=0; $(document).bind('keydown',function(event){ switch (event.which) { case 37: movex=-1; movey=0; break; case 38: movex=0; movey=-1; break; case 39: movex=1; movey=0; break; case 40: movex=0; movey=1; break; } }); return { init:function(){ movex=0; movey=0; }, x:function(){ return movex; }, y:function(){ return movey; } }; }();*/ $(function(){ var svc=service(); svc.newenemy(); svc.newenemy(); svc.newenemy(); svc.newenemy(); var bullet={ x:1, y:CANVAS_HEIGHT-5, hp:1, index:svc.total(), exploreImg:getImg("img/blasts3.png"), img:getImg("img/mybullet2.png"), target:0, conflictSquare:20, speedX:5, speedY:10, movex:0, movey:0}; /*var flier=fly({ x:Math.random()*CANVAS_WIDTH, y:Math.random()*CANVAS_HEIGHT, hp:10, index:svc.total(), exploreImg:getImg("img/blasts3.png"), img:getImg("img/mybullet1.png"), target:0, conflictSquare:4, speedX:5, speedY:5, movex:0, movey:0 }); flier.onMove=function(x,y){ //if(x!=0||y!=0){ this.judgeBundle(); this.move($myplane.x(),$myplane.y()); //$myplane.init(); return {type:"drawimg",func:"drawimg",params:[flier.img(),flier.x(),flier.y()]}; //} //return; }; */ var flier=playerplane({ x:Math.random()*CANVAS_WIDTH, y:Math.random()*CANVAS_HEIGHT, hp:100, index:svc.total(), exploreImg:getImg("img/blasts3.png"), img:getImg("img/dplayerplane.png"), target:0, conflictSquare:5, speedX:5, speedY:5, movex:0, movey:0, bullet:bullet }); svc.newfly(flier); for(i=0;i<20;i++) { svc.newbullets(); } var backParam={ speedY:-5, imgHeight:250 }; $planestage.initCanvas(); var back=$background(backParam); back.setImg("img/back_img.png",2500,470); $planestage.push(back); $planestage.push(svc); $planestage.start(); }); </script> </head> <body> </body> </html>
至于view层,就是HTML5的Canvas,这里就不多介绍,API文档很全很全。最后给个截图
代码在这里:
博客园自己的文件下载可能有问题,我分享到百度网盘了
http://pan.baidu.com/share/link?shareid=496950718&uk=604524374
软件开发,其乐无穷啊~~大家,enjoy it!~~~~