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+"\n";
//ale+="fly1:"+"x:"+fly1.x()+"y:"+fly1.y()+"width:"+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);
}
};
};

service.js

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);//在画布上绘出背景图形参数表示的意思分别为:图片,图片横坐标,图片纵坐标,一帧对应的背景图片宽度,一帧对应的背景图片高度,画布横坐标,画布纵坐标,画布宽度,画布高度。
}
};
}

background.js

注释还算清晰吧。

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);
}
};
}();

planestage.js

使用的时候,只要把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!~~~~

HTML5学习+javascript学习:打飞机游戏Service层Control层+源码的更多相关文章

  1. HTMl5/CSS3/Javascript 学习推荐资源

    HTMl5/CSS3/Javascript 学习推荐资源 前端的定义应该是数据内容的展示,在国内大家都觉得前端只是HTML+CSS+Javascript,但是实际上与展示有关的都是前端,所以Ruby/ ...

  2. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  3. 3D单机游戏《天鹰教》源码发布(二)

    3D单机游戏<天鹰教>源码发布 作者 作者: 游蓝海 博客: http://blog.csdn.net/you_lan_hai mail:     you_lan_hai@foxmail. ...

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

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

  5. 8个经典的HTML5游戏在线试玩及源码学习

    原文地址:http://www.oschina.net/news/32364/html5-games 游戏,毫无疑问是拿来供大家娱乐玩耍的,这也无可厚非,但是,今天给大家分享的8个HTML5游戏,在好 ...

  6. 比特币学习笔记(二)---在windows下调试比特币源码

    根据我一贯的学习经验,学习开源代码的话,单单看是不够的,必须一边看一边调试才能尽快理解,所以我们要想法搭建windows下bitcoin源码的调试环境. 紧接着昨天的进度,想要调试linux下的比特币 ...

  7. Spring源码学习01:IntelliJ IDEA2019.3编译Spring5.3.x源码

    目录 Spring源码学习01:IntelliJ IDEA2019.3编译Spring5.3.x源码 前言 工欲善其事必先利其器.学习和深读Spring源码一个重要的前提:编译源码到我们的本地环境.这 ...

  8. Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析

    这是关于RecyclerView的第二篇,说的是如何自定义Item动画,但是请注意,本文不包含动画的具体实现方法,只是告诉大家如何去自定义动画,如何去参考源代码. 我们知道,RecyclerView默 ...

  9. 【Python学习】记一次开源博客系统Blog_mini源码学习历程-Flask

    今天准备看看Flask框架,找到一套博客系统源码,拿来学习学习 https://github.com/xpleaf/Blog_mini 演示地址 http://140.143.205.19 技术框架 ...

随机推荐

  1. FPGA笔记-阅读.dat文件

    阅读.dat图像文件 .dat文件是matlab生成的图像文件 initial begin // Initialize Inputs CLK = 0; RST = 1; IMAGE_DATA = 0; ...

  2. MVC 6 写法

    MVC 6 一些不晓得的写法 今天在看 Scott Guthrie 的一篇博文<Introducing ASP.NET 5>,在 MVC 6 中,发现有些之前不晓得的写法,这边简单记录下, ...

  3. 国籍控件(js源码)

    国籍控件(js源码) 一直苦于没有好的国籍控件可以用,于是抽空写了一个国籍控件,现分享给大家. 主要功能和界面介绍 国籍控件主要支持中文.英文过滤以及键盘上下事件. 源码介绍 国籍控件核心是两个文件, ...

  4. C语言库函数大全及应用实例二

    原文:C语言库函数大全及应用实例二                                              [编程资料]C语言库函数大全及应用实例二 函数名: bioskey 功 能 ...

  5. 怎样在Upstart机制下的系统中加入upstart事件型的任务

    /*********************************************************************  * Author  : Samson  * Date   ...

  6. shell awk统计重复个数

    awk是一个很强大的工具,一个常见的用法就是统计一个文件中重复的列值的个数,这也是面试时面试官经常问的一个问题. 举个例子: 有个文件file.log的内容如下: http://www.sohu.co ...

  7. 深入浅出SQL Server 2008 分区函数和分区表

    原文:深入浅出SQL Server 2008 分区函数和分区表 当我们数据量比较大的时候,我们需要将大型表拆分为多个较小的表,则只访问部门数据的查询就可以更快的运行,基本原理就是,因为要扫描的数据变的 ...

  8. C# 利用 HttpWebRequest 和 HttpWebResponse 模拟登录有验证码的网站

    原文:C# 利用 HttpWebRequest 和 HttpWebResponse 模拟登录有验证码的网站 我们经常会碰到需要程序模拟登录一个网站,那如果网站需要填写验证码的要怎样模拟登录呢?这篇文章 ...

  9. 处理动态SQL语句的参数

    原文:处理动态SQL语句的参数 经常对SQL进行开发,写动态的SQL语句,是少之不了的,但是在使用动态语句中,常是因为有动态的参数的出现.参考下面代码示例: 正因为有了标记1的动态条件代码,而让SQL ...

  10. php-fpm介绍及配置

    php-fpm是什么 全称是php fastcgi process manager即php fastcgi进程管理器,相比fastcgi静态的唤起cgi,fpm能根据访问的压力动态的唤起cgi进程和销 ...