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


    本着好记性不如烂博客以及分享成功的喜悦和分享失败的苦楚,今天我来分享下一个练手项目:打飞机游戏~从小就自己想做游戏,可是一直没有机会。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

     

    这里我们先给出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     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

    bullets.js:

     1 /*子弹类,继承了fly类,增加了Function类的method方法,给Object增加了superior方法使其可以调用父类的方法。,增加了isbullet方法。
     2 *改写了onConflict方法使子弹之间不能发生碰撞。
     3 */
     4 var bullets=function(spec){
     5         var isbullet=true;//是否子弹(父类没有)
     6     var that=fly(spec);//子弹类继承fly类
     7     Function.prototype.method = function(name,func){//为Function增加method,调用模式为:function1.method('xxx',function(){});
     8         if(!this.prototype[name])  {
     9             this.prototype[name] = func;
    10         }
    11         return this;    
    12     }
    13     Object.method('superior',function(name){//调用上面的method方法,给Object增加父类的内容。
    14                         var that=this;
    15                          var method = that[name];
    16                         return function(){
    17                                     return method.apply(that,arguments);//第一个参数为上下文,第二个参数为传递的参数。
    18                           };
    19             });
    20     var super_onConflict=that.superior('onConflict');//父类的onConflict方法
    21     that.onConflict=function(other){
    22         if(typeof other.isbullet === 'function' && other.isbullet()){//如果碰撞双方均为子弹,不发生碰撞。
    23             return;
    24         }
    25         super_onConflict(other);
    26     }
    27     that.isbullet=function(){
    28         return true;//该方法仅仅表明这个类是子弹类。。。
    29     };
    30     return that;
    31 };
    32 var playerbullet=function(spec){//选手子弹类,发出子弹就新给一个子弹类
    33     var that=bullets(spec);//子弹类继承bullets类,好多方法父类都实现了,子类就改个onMove方法就行了
    34     
    35     that.onMove=function(){
    36         if(that.y()<0||that.y()>CANVAS_HEIGHT||that.x()<0||that.x()>CANVAS_WIDTH){//这里只判定如果超出边界,就让它消失,其他的方法父类已经实现了。
    37             //alert("outbundle"+that.x()+that.y());
    38             //alert(that.index());
    39             return [{func:"disapear",params:[that.index()]},{func:"reduceBulet",params:[1]}];
    40             
    41         }
    42         //that.judgeBundle();
    43         that.move(0,-1);//移动垂直向上
    44         
    45         return {type:"drawimg",func:"drawimg",params:[that.img(),that.x(),that.y()]};//如果没超出边界,更新它在画布上的位置
    46     
    47     };
    48     return that;//这个变量就是父类的引用。子类可以对父类进行扩展,就是通过这样的方式。
    49     };
    bullets.js

    friendplane.js:

    //这个全局变量用来响应按键,上下左右,改变它的私有变量,并具有返回私有变量的方法。外部不可访问其私有变量。
    $myplane=function(){
    var movex=0;//x轴的方向
    var movey=0;//y轴方向
    //var status=0;
    $(document).bind('keydown',function(event){//按键按下事件
        if(event.which){
         switch (event.which) {
           case 37://左边按下
            movex=-1;
            //movey=0;
            status=0;
            break;
        case 38://上边按下
            
            movey=-1;
            //status=0;
            break;
        case 39://右边
            
            movex=1;
            //movey=0;
            //status=0;
            break;
        case 40://下边
            //movex=0;
            
            
            movey=1;
            
            //status=0;
            break;
        //case 17://control按下(先不处理)
            //status=1;
            //break;
        default:    //默认
            movex=0;
            movey=0;
            status=0;
            break;
        }
        
    }
    });
    //键盘按上事件
    $(document).bind('keyup',function(event){
              if(event.which){
         switch (event.which) {
           case 37://左边
            
        case 39://右边    
            movex=0;
            break;
        case 38://上边
        case 40://下边
            movey=0;
            break;
        default:    //默认
            
            break;
        }
        }
        });
    return {
        init:function(){
            movex=0;
            movey=0;
            status=0;
        },
        x:function(){
            return movex;    
        },
        y:function(){
            return movey;
        },
        status:function(){
            return status;
        }
        };
    }();
    
    var playerplane=function(spec){//选手飞机
        var shootTimes=0;//这个用来做频率的次数统计,多少帧发一个子弹,后面会给它递增,然后求模,然后又初始化为1
        var frq=spec.bullet.frq;//如果子弹中有频率字面量,那么这个频率能确定,否则给一个不会看花眼的频率
        var frqcy=Math.floor((typeof frq === 'undefined')?20:frq);
    
        if(typeof Object.beget != 'function'){//这个步骤完成了Object的beget方法,新建立Object就不用new Object()了,那简直弱爆了,只要调用Object.beget('xxx')就直接实
                            //例化了,又复制了'xxx'这个类的所有的键值对。这个方法得自《javascript语言精粹》一书
        Object.beget=function(o){
                    var F=function(){};
                    F.prototype=o;
                    return new F();
            };
        }
    
        //var specblt=Object.beget(spec.bullet);
        //var specblt=spec.bullet;
        spec.x=0;//选手飞机的横坐标初始为0,或者任意值
        spec.y=CANVAS_HEIGHT;//选手飞机的纵坐标为最下面
        var that=fly(spec);//选手也是fly的子类。
        that.shoot=function(){//不过选手能发炮弹,fly就不可以
        var specblt=Object.beget(spec.bullet);//这个是调用了Object刚刚初始化的原型,复制一个bullet的实例
        specblt.x=that.x()+(that.width()-specblt.img.width)/2;//然后bullet最好是在正中央发出去,当然如果你不想这么做,也没关系。
        specblt.y=that.y()-(that.height()-specblt.img.height)/2;
        return {func:"shoot",params:[specblt]};//给个发射事件交给后台去处理吧。
    
        };
        that.onMove=function(x,y){//选手飞机不需要来回跳来跳去的,只要根据键盘响应进行移动就好啦,还是调用了move方法。
        shootTimes++;
        if(shootTimes==frqcy)
        {
            shootTimes=1;
        }
        
        that.move($myplane.x(),$myplane.y());
        that.judgeBundle();
        //$myplane.init();
        //alert(shootTimes%frqcy);
        if(shootTimes%frqcy === 1){
        
        return [{type:"drawimg",func:"drawimg",params:[that.img(),that.x(),that.y()]},that.shoot()];//如果这个帧已经过了预定的次数,就发射一颗炮弹并根据需求改变自己的位置
        }
        return {type:"drawimg",func:"drawimg",params:[that.img(),that.x(),that.y()]};//否则就改变自己的位置就好啦。
        
        };
        
        return that;
        };
    friendplane.js

    注意:这里的对象实例化形式是:

    var objct = function(spec){//spec为私有变量不可全局访问因此这种方式有良好的封装性
           return {//此处返回了一个对象,对象中的内容以字面量:属性值的形式
             oo:function(){
                 return spec.oo;
                },
               xx:function(){
                  return spec.xx;
               }
            };
    };
    View Code

    我们得到父类方法的引用的形式是:

    Function.prototype.method = function(name,func){//为Function增加method,调用模式为:function1.method('xxx',function(){});
            if(!this.prototype[name])  {
                this.prototype[name] = func;
            }
            return this;    
        }
        Object.method('superior',function(name){//调用上面的method方法,给Object增加父类的内容。
                            var that=this;
                             var method = that[name];
                            return function(){
                                        return method.apply(that,arguments);//第一个参数为上下文,第二个参数为传递的参数。
                              };
                });
        var super_onConflict=that.superior('onConflict');//父类的onConflict方法

    这种形式是书上写的,对于这种形式,我有些不懂得地方:我们在Function的原型中链接了一个method方法,又在Object调用method方法那么按照书上所说:“Function的prototype(原型链)是隐藏链接到Object的,而javascript在查找原型时,是一级一级查找的,如果当前级别的原型字面量可以找到就返回这个字面量对应的值,否则就根据原型链查找上一级的原型直到找到为止,而如果在Object中都找不到原型就返回'undefined'”这里我在Object中岂不是根本就找不到method这个字面量对应的内容了?这岂不是相互矛盾?而程序却明显没有报错。希望有人能够解我困惑,指我迷津。

    有关这种形式,推荐一本书:《javascript语言精粹》(Douglas Crockford著,赵泽欣译)后面会给出一些此书中自己认为比较重要的笔记

    恩,Model层就说到这里罢,这个Model层是可以扩展的,只要按照bullets.js的形式,可以有无数个子类的哦。根据抽丝剥茧的原理,下一篇将介绍打灰机service层的实现。

  • 相关阅读:
    java设计模式-----3、抽象工厂模式
    java设计模式-----2、工厂方法模式
    java设计模式-----1、简单工厂模式
    解决windows10和ubuntu16.04双系统下时间不对的问题
    个人第二次作业-熟悉使用工具成绩汇总
    第二周助教小结——工作步入正轨
    第一周助教小结——助教工作初体验
    助教培训总结——熟练掌握GitHub及Git的使用方法
    助教培训总结——原型设计工具及练习
    助教培训总结——对学生任务博客的评分练习
  • 原文地址:https://www.cnblogs.com/saajireign/p/3162323.html
Copyright © 2020-2023  润新知