用Phaser來制作一個html5游戲——flappy bird (二)


上一篇教程中我們完成了boot、preload、menu這三個state的制作,下面我們就要進入本游戲最核心的一個state的制作了。play這個state的代碼比較多,我不會一一進行說明,只會把一些關鍵的東西挑出來說。

我們點擊游戲菜單中的開始按鈕后,首先出現的是這個畫面:

1

 

在第一部分的教程中,我們已經制作了一個游戲菜單的場景,這個頁面也跟那個差不多,只不過這個頁面去除了游戲標題和開始按鈕,而多出了Get Ready以及提示點擊屏幕來操作游戲的兩張圖片。在這個state里,我們需要啟用物理引擎,並且要能夠響應鼠標點擊事件。

game.States.play = function(){
    this.create = function(){
        this.bg = game.add.tileSprite(0,0,game.width,game.height,'background');//背景圖,這里先不用移動,游戲開始后再動
        this.pipeGroup = game.add.group();//用於存放管道的組,后面會講到
        this.pipeGroup.enableBody = true;
        this.ground = game.add.tileSprite(0,game.height-112,game.width,112,'ground'); //地板,這里先不用移動,游戲開始后再動
        this.bird = game.add.sprite(50,150,'bird'); //
        this.bird.animations.add('fly');//添加動畫
        this.bird.animations.play('fly',12,true);//播放動畫
        this.bird.anchor.setTo(0.5, 0.5); //設置中心點
        game.physics.enable(this.bird,Phaser.Physics.ARCADE); //開啟鳥的物理系統
        this.bird.body.gravity.y = 0; //鳥的重力,未開始游戲,先讓重力為0,不然鳥會掉下來
        game.physics.enable(this.ground,Phaser.Physics.ARCADE);//開啟地面的物理系統
        this.ground.body.immovable = true; //讓地面在物理環境中固定不動

        this.readyText = game.add.image(game.width/2, 40, 'ready_text'); //get ready 文字
        this.playTip = game.add.image(game.width/2,300,'play_tip'); //提示點擊屏幕的圖片
        this.readyText.anchor.setTo(0.5, 0);
        this.playTip.anchor.setTo(0.5, 0);

        this.hasStarted = false; //游戲是否已開始
        game.time.events.loop(900, this.generatePipes, this); //利用時鍾事件來循環產生管道
        game.time.events.stop(false); //先不要啟動時鍾
        game.input.onDown.addOnce(this.statrGame, this); //點擊屏幕后正式開始游戲
    };
}

 

啟用物理系統

默認的游戲中的每個對象的物理系統是關閉的,要啟用一個對象的物理系統,可以使用 game.physics.enable() 方法

enable(object, system, debug)

 

object : 要開啟物理系統的對象,可以是單個對象,也可以是一個包含多個對象的數組

system : 要啟用的物理系統,默認為 Phaser.Physics.ARCADE,Phaser目前支持三種物理引擎,分別是Arcade ,P2 以及 Ninja。

debug : 是否開啟調試

只有開啟了對象的物理系統,該對象才具有物理特性,開啟了物理系統后,對象的body屬性指向該對象擁有的物理系統,所有與物理相關的屬性或方法都必須在body上進行操作。

 

鼠標點擊事件

Phaser中的鼠標、鍵盤、觸摸等交互事件都統一由Input對象來處理。我們需要鼠標點擊屏幕后進行響應,可以使用Input對象的onDown屬性,該屬性指向一個Phaser.Signal對象,我們可以在這個對象上綁定事件,每當鼠標按鍵下,就會觸發一個onDown的信號,如果這個onDown信號對象上綁定了事件,那么這些事件就會執行。例如:

var input = game.input; //當前游戲的input對象
var signal = input.onDown; //鼠標按下時的 Signal對象
signal.add(function(){}); //給Signal 綁定事件處理函數
signal.add(function(){}); //再綁定一個
signal.addOnce(function(){}); //綁定一個只會執行一次的事件函數

 

時鍾對象

有時我們需要定時或者每隔一段時間就執行一段代碼,在原生js中我們可以通過setTimeout和setInterval來實現。Phaser給我們提供了功能更強大的Timer對象來實現這些功能。Timer對象主要有以下幾個方法:

loop(delay, callback, callbackContext, arguments); //以指定的時間間隔無限重復執行某一個函數,直到調用了Timer對象的stop()方法才停止

repeat(delay, repeatCount, callback, callbackContext, arguments); //讓某個函數重復執行,可以指定重復的次數

 

 

當前的Timer對象我們可以通過 game.time.events 來得到,在調用了Timer對象的loop或repeat方法后,還必須調用start方法來啟動。但是我使用的Phaser 2.0.4 版本,好像不調用start方法,loop方法就自動起作用了,不知道這是不是一個bug。如上面代碼中我們用到的:

game.time.events.loop(900, this.generatePipes, this); //利用時鍾對象來重復產生管道
game.time.events.stop(false); //先讓他停止,因為即使沒調用start方法,它也會自動啟動,這應該是一個bug

 

當點擊屏幕后,就可以正式開始游戲了,我們來看看點擊屏幕事件綁定的 this.startGame 函數做了什么。

this.statrGame = function(){
    this.gameSpeed = 200; //游戲速度
    this.gameIsOver = false; //游戲是否已結束的標志
    this.hasHitGround = false; //是否已碰撞到地面的標志
    this.hasStarted = true; //游戲是否已經開始的標志
    this.score = 0; //初始得分
    this.bg.autoScroll(-(this.gameSpeed/10),0); //讓背景開始移動
    this.ground.autoScroll(-this.gameSpeed,0); //讓地面開始移動
    this.bird.body.gravity.y = 1150; //給鳥設一個重力
    this.readyText.destroy(); //去除 'get ready' 圖片
    this.playTip.destroy(); //去除 '玩法提示 圖片
    game.input.onDown.add(this.fly, this); //給鼠標按下事件綁定鳥的飛翔動作
    game.time.events.start(); //啟動時鍾事件,開始制造管道
}

我們再來看下鳥的飛翔動作,它由 this.fly 函數來實現

this.fly = function(){
    this.bird.body.velocity.y = -350; //飛翔,實質上就是給鳥設一個向上的速度
    game.add.tween(this.bird).to({angle:-30}, 100, null, true, 0, 0, false); //上升時頭朝上的動畫
    this.soundFly.play(); //播放飛翔的音效
}

 

重力和速度

Phaser.Physics.Arcade.Body 對象,也就是當你是用arcade物理引擎時 sprite.body 所指向的對象,擁有很多跟物理相關的屬性和方法。其中的 gravity 對象代表重力,它有x和y兩個屬性,分別代表水平方向和垂直方向的重力。我們可以使用它的 setTo(x,y)方法來同事設置兩個方向的重力。設置了重力的物體,它的運動會受到重力的影響,與真實生活中的物理現象是一致的。然后這個body它還有一個 velocity 對象,表示物體的速度,跟重力一樣,都分水平和垂直兩個方向,也可以用setTo(x,y)方法來設置。一旦給物體設置了合適的速度,它便能動了。

 

管道的生成

下面再來看一看管道生成函數 this.generatePipes

this.generatePipes = function(gap){ //制造一組上下的管道
    gap = gap || 100; //上下管道之間的間隙寬度
    var position = (505 - 320 - gap) + Math.floor((505 - 112 - 30 - gap - 505 + 320 + gap) * Math.random());//計算出一個上下管道之間的間隙的隨機位置
    var topPipeY = position-360; //上方管道的位置
    var bottomPipeY = position+gap; //下方管道的位置

    if(this.resetPipe(topPipeY,bottomPipeY)) return; //如果有出了邊界的管道,則重置他們,不再制造新的管道了,達到循環利用的目的

    var topPipe = game.add.sprite(game.width, topPipeY, 'pipe', 0, this.pipeGroup); //上方的管道
    var bottomPipe = game.add.sprite(game.width, bottomPipeY, 'pipe', 1, this.pipeGroup); //下方的管道
    this.pipeGroup.setAll('checkWorldBounds',true); //邊界檢測
    this.pipeGroup.setAll('outOfBoundsKill',true); //出邊界后自動kill
    this.pipeGroup.setAll('body.velocity.x', -this.gameSpeed); //設置管道運動的速度
}

 

管道生成的思路:利用隨機數計算出上下管道的位置,然后檢查當前是否有管道已經出了邊界,如果有,則重置出了邊界的那組管道的位置,如果沒有,則生成一組新的管道,這樣就能避免內存浪費了。所有管道我們都把它放在一個組中,便於集中管理。這里需要掌握的是sprite對象的reset方法:

reset(x, y, health)

這個方法能重置sprite對象的位置,更重要的是,如果在一個已經被殺死了(kill)的sprite對象上執行該方法,那么該sprite的 alive, exists, visible and renderable 等屬性都會變回為true。在需要重復利用已經存在的sprite對象時,經常要使用該方法。看下我們這個游戲中是怎么使用這個方法的:

 

this.resetPipe = function(topPipeY,bottomPipeY){//重置出了邊界的管道,做到回收利用
    var i = 0;
    this.pipeGroup.forEachDead(function(pipe){ //對組調用forEachDead方法來獲取那些已經出了邊界,也就是“死亡”了的對象
        if(pipe.y<=0){ //是上方的管道
            pipe.reset(game.width, topPipeY); //重置到初始位置
            pipe.hasScored = false; //重置為未得分
        }else{//是下方的管道
            pipe.reset(game.width, bottomPipeY); //重置到初始位置
        }
        pipe.body.velocity.x = -this.gameSpeed; //設置管道速度
        i++;
    }, this);
    return i == 2; //如果 i==2 代表有一組管道已經出了邊界,可以回收這組管道了
}

 

 

碰撞檢測

好了,管道和鳥都已經有了,而且它們都能動了,接下來就是,實現鳥撞到管道或地面后游戲結束的功能了。

在Arcade物理引擎中,碰撞檢測主要用到兩個函數,一個是collide,還有一個是overlap

collide方法與overlap的區別在於collide會影響兩個要檢測的對象之間的物理狀態,比如使用collide函數去檢測兩個物體,如果物體碰撞了,那么這兩個物體之間就會有力的相互作用,可能其中一個會被另一個彈開,或者兩個之間相互彈開。但如果使用overlap方法的話,則只會檢測兩個物體是否已經碰撞了,或者說已經重疊了,並不會產生物理作用,顯然,如果只需要知道兩個物體是否已經重疊了的話,overlap性能會更好。

碰撞檢測可以單個對象與單個對象進行檢測、單個對象與組進行檢測、組與組進行檢測。collide方法必須在每一幀中都進行調用,才能產生碰撞后的物理作用。

this.update = function(){ //每一幀中都要執行的代碼可以寫在update方法中
    if(!this.hasStarted) return; //游戲未開始,先不執行任何東西
    game.physics.arcade.collide(this.bird,this.ground, this.hitGround, null, this); //檢測與地面的碰撞
    game.physics.arcade.overlap(this.bird, this.pipeGroup, this.hitPipe, null, this); //檢測與管道的碰撞
    if(this.bird.angle < 90) this.bird.angle += 2.5; //下降時鳥的頭朝下的動畫
    this.pipeGroup.forEachExists(this.checkScore,this); //分數檢測和更新
}

 

 

分數管理

當鳥飛過一組管道后,就得1分。飛過一組管道,是指這組管道已經在鳥的左邊的,所以可以通過管道的x坐標來判斷是否已經得分。

this.checkScore = function(pipe){//負責分數的檢測和更新,pipe表示待檢測的管道
    //pipe.hasScored 屬性用來標識該管道是否已經得過分
    //pipe.y<0是指一組管道中的上面那個管道,一組管道中我們只需要檢測一個就行了
    //當管道的x坐標 加上管道的寬度小於鳥的x坐標的時候,就表示已經飛過了管道,可以得分了
    if(!pipe.hasScored && pipe.y<=0 && pipe.x<=this.bird.x-17-54){
        pipe.hasScored = true; //標識為已經得過分
        this.scoreText.text = ++this.score; //更新分數的顯示
        this.soundScore.play(); //得分的音效
        return true; 
    }
    return false;
}

 

只需要在每一幀中對每一個管道都調用一次該函數,就可以了。

 

聲音的播放

在Phaser中播放一段聲音很簡單,只需要事先加載好聲音資源。然后調用play方法播放就行了。

首先使用 game.load.audio() 來加載聲音資源。我們以本游戲中得分時播放的聲音為例,在state的preload方法中預先加載聲音資源

game.load.audio('score_sound', 'assets/score.wav');//得分的音效

 

然后通過 game.add.sound() 來得到一個sound對象

this.soundScore = game.add.sound('score_sound');

 

sound對象有許多方法用來控制聲音的播放暫停等,要播放聲音,只需要調用它的play方法即可。

this.soundScore.play(); //播放聲音

 

好了,把這些組合起來,就能做出我們的flappy bird游戲了。我所說的這些都只是些皮毛,是想讓大家對用Pharse來做游戲有個最初步的印象,也許你還有許多不明白的地方,pharse是個功能很強大的html5游戲框架,想要掌握它,還是必須多看文檔,多看官方給出的例子,然后自己動手去實踐,一步一步一點一滴的去學習,去積累。蘋果新發布的ios8中對webgl的支持已經大大加強了,無論是在safari瀏覽器還是webView運行html5游戲,性能都相當好,這也是移動設備發展的一個趨勢,所以掌握一個html5游戲框架,無論是自娛自樂,或是對自己能力的提升,甚至是找工作,都是有一定的益處的。

 

第一部分教程:

用Phaser來制作一個html5游戲——flappy bird (一)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM