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的實現細節(也能夠想象成帶領你們實地觀摩城管是怎麼對付小販)。
 

相關文章
相關標籤/搜索