flappy pig小游戏源码分析(1)——主程序初探

闲逛github发现一个javascript原生实现的小游戏,源码写的很清晰,适合想提升水平的同窗观摩学习。读通源码后,我决定写一系列的博客来分析源码,从总体架构到具体实现细节来帮助一些想提升水平的朋友。源码地址为:https://github.com/keenwon/flappy-pigjavascript

须要提醒你们的是,个人分析模式是,先给出源码,加上注释让你们通读一遍,而后分解源码逐步分析。前端

下载了做者的源码后先看一下目录结构:java

其中做者使用了Grunt进行了打包,会使用的grunt的小伙伴一看这个目录确定一目了然,若是你历来没有使用过任何前端构建工具,别担忧,你只用关注src文件夹就能够了,打开src文件夹,内容以下:git

这就是游戏的所有组成部分了,咱们只关心具体的控制逻辑,因此直接进入js文件夹:github

其中game.js是游戏的主程序,其余的分别承担必定的职责,好比pig.js负责跳动小猪的行为和属性、pillar.js负责柱子的移动、util.js包含一些工具方法等。这里咱们要学习的是一种分模块的思想,让一个模块负责一程序的一个部分,提供接口供其它程序使用,这样接触了耦合,同时让程序便于维护。
咱们第一篇分析就从gama.js下手,初步的分解一下做者的思路。
让咱们先看看主程序的所有代码,其中我会用绿色的字体表示注释,让你们看得更清楚:

var flappy = (function (self) {
    'use strict';//开启严格模式,新手能够暂时忽视

    var controller = self.controller,//获取控制者对象,以后详细介绍
        option = self.option,//获取配置值,以后详细介绍
        pig = self.pig,//获取小猪模块,以后详细介绍
        pillar = self.pillar,//获取柱子模块,以后详细介绍
        pos = self.position,//获取位置模块,以后详细介绍
        util = self.util,//获取工具模块,以后详细介绍
        $ = self.util.$;//从util.js中咱们能够看出,$方法能够经过id获取DOM元素

    //主程序
    self.game = {//给self对象添加game属性,该属性指向一个对象
        init: function () {//game对象的init方法 var t = this;//this指向函数调用者,非特殊状况下(不使用call、apply或者直接调用),通常咱们就先认为指向了game对象,由于从后面咱们也能够看到,做者也是经过flappy.game.init()调用的。

            t._isStart = false;//game有一个isStart属性,用于标识游戏是否开始,初始值为false
            t._isEnd = false;//game有一个isEnd属性,用于表示游戏是否结束,初始值为false
            t._timer = null;//game对象有一个定时器,初始化为null

            pig.init(t.fall, t);//调用pig模块的init方法,将game.fall方法和game对象传递过去
            pillar.init();//调用pillar模块的init方法
            pos.init(t.hit, t);//调用pos模块的init方法,将game.hit方法和game对象传递过去

            t.addKeyListener();//将this指向game,给game对象添加键盘监听
        },
        addKeyListener: function () {//监听键盘事件
            var t = this;//this指向函数调用者,从上面能够看到调用者是game
            document.onkeydown = function (e) {//监听键盘按下事件
                e = e || window.event;//获取事件对象,兼容IE
                var currKey = e.keyCode || e.which || e.charCode;//获取按了哪个按键,兼容各家浏览器
                if (currKey == 32) {//若是按下了空格
                    if (!t._isEnd) {//若是游戏没有结束
                        t.jump();//那么调用game.jump()方法
                    } else {
                        window.location.reload();//若是游戏已经结束,按空格后刷新页面,从新开始
                    }
                    util.preventDefaultEvent(e);//阻止事件的默认行为,具体细节在util.js中,我会在相应章节分析
                }
            };
        },
        jump: function () {//这里就是game.jump()方法
            var t = this;//指向game对象
            if (!t._isStart) {//若是游戏没有开始
                $('start').style.display = 'none';//将游戏开始界面隐藏
                t._createTimer(function () {//调用game._createTimer方法,建立定时器,每二十毫秒执行一次
                    pig.start();//调用pig模块的start方法,让小猪开始移动,具体细节在pig.js中,我会在相应章节分析
                    pillar.move();//,调用pillar模块的move方法,让柱子移动,具体细节在pillar.js中,我会在相应章节分析
                    pos.judge();//调用pos模块的judge方法,判断位置,具体细节在position.js中,我会在相应章节分析
                    $('score').innerHTML = pillar.currentId + 1;//设置记分板分数
                });
                t._isStart = true;//设置游戏状态为已开始
            } else {
                pig.jump();//若是游戏已经开始,那么直接调用pig.jump方法
            }
        },
        hit: function () {//game对象的hit方法
            var t = this;

            t.over();//调用game.over方法,游戏结束
            pig.hit();//调用pig模块的hit方法,具体细节在pig.js中,我会在相应章节分析
        },
        fall: function () {//game对象的fall方法
            var t = this;

            t.over();//调用game.over方法,游戏结束
            pig.fall();//调用pig模块的fall方法,具体细节在pig.js中,我会在相应章节分析
        },
        over: function () {//game对象的over方法,负责结束游戏
            var t = this;//获取game对象
            clearInterval(t._timer);//取消计时器
            t._isEnd = true;//将标识游戏是否结束的变量设置为true
            $('end').style.display = 'block';//游戏结束的提示显示出来
        },
        _createTimer: function (fn) {//game对象的_createTimer方法,建立定时器
            var t = this;//获取game对象

            t._timer = setInterval(fn, option.frequency);//实现定时器,频率为配置项中的frequency属性,具体细节在option.js中,我会在相应章节介绍
        }
    };

    flappy.init = function () {//暴露接口
        self.game.init();//game.init()函数中this指向game
    };

    return self;//返回self对象,实际上也就是给本来的flappy对象加了点东西

})(flappy || {});

以上就是game.js的所有代码和个人注释,可能看完注释后读者仍然有一种雾里看花的感受,不要担忧,我不会用单单用注释来糊弄大家,只是大家能够经过注释大体明白做者每句话的意思。
好,咱们来真正的开始解剖它吧。
首先要看看做者的这个用法,相信这也是新手迷茫的地方:
var flappy  = (function(self){
  //其余实现细节
})(flappy || {});
若是你通读了做者的源码后,你能够很容易的理解到这里做者的意思是,给传进来的flappy对象增长相应的模块,由于咱们的程序分红了不少不一样的模块,可是做为一个有机的总体,程序须要有机的结合在一块儿,若是仍是不太懂,没事咱们写一个小小的测试:
这段代码的执行结果是这样的:
这里咱们须要学习的有如下几个知识点:
第一,预声明
javascript程序在执行前在执行前会有一次预声明,处理funtion声明和var声明,其中var的变量会被赋值为undefined,详细的过程这里展开,不懂的同窗自行谷歌或百度。
第二,匿名函数自执行
var fn = (function(){})();
这里是javascripter编程中常用的技巧,它做用在方方面面——实现私有属性、避免污染全局、各类设计模式中或多或少也会使用到。
在了解以上两点的基础上咱们来一步一步分析执行过程,首先预编译,flappy的值被设置为undefined;而后代码正式开始执行,执行一个匿名函数, 参数中有一个逻辑或判断,若是flappy不是undefined或者null则使用flappy做为参数,若是flappy是undefined或者null则使用空对象做为参数,显然咱们的flappy此时的值是undefined,因此这里空对象{}被做为参数传入,也就是说self指向一个空对象,
给这个空对象添加了一个name属性,而后返回这个对象的引用给flappy,也就是说如今flappy的值指向一个含有name属性的对象了;再而后,故伎重演,执行匿名函数,不过此时参数中flappy既不是undefined或者null,而是一个含有name属性的对象,匿名函数执行的结果是给这个对象添加了一个age属性并返回该对象的引用个flappy,因此结果咱们看到个对象又多了一个age属性。
这里搞懂了,就不难理解做者在主程序干了些啥了,咱们接下来用伪代码来描述一下主程序:
他们在形式上同属于一个对象,然而在逻辑上他们的关系是这样的:
也就是说使用者只须要调用flappy.init方法,其余的实现细节对于使用者来讲是透明的。flappy方法也只有一个直接下级——flappy.game对象,其余的模块对于flappy.init来讲是透明的。flappy.game对象就很忙了,他负责作具体的事情,全部的细节都由它来调度。
咱们形象的形容一下这个过程,市长(也就是上图中的使用者)经过电话(也就是上图中国的flappy.init())对当地公安局局长(图中的flappy.game)说:“咱们要整顿市容”,因而公安局中赶忙联系了城管部门(负责对小摊小贩的清理)、清洁部门(负责把墙上贴的小广告清除掉)、消防部门(负责检查城市内的不合格消防设施)等等,具体怎么作就由各部分本身去完成。
这就是主程序的骨架,从中咱们并无看到任何实现细节,咱们须要的是创建起一个总体的架构,特别是对于不少渴望进阶的朋友,总以为本身虽然掌握了基础可是不知道运用在哪里,总有雾里看花的感受。实际上,时刻记住,程序的出现时为了解决现实中存在的一些问题,因此抽象出现实中的关系才是写程序的核心,学会了这一点,才算上走上了programmer的路子,不然就仍是个coder。
好了,这一节的内容是一个概览,理清主程序的框架和思路,下一节我会给你们来分析pig.js的实现细节(也能够想象成带领你们实地观摩城管是怎么对付小贩)。
 

相关文章
相关标签/搜索