在上一篇教程中咱們完成了boot、preload、menu這三個state的製做,下面咱們就要進入本遊戲最核心的一個state的製做了。play這個state的代碼比較多,我不會一一進行說明,只會把一些關鍵的東西挑出來講。html
咱們點擊遊戲菜單中的開始按鈕後,首先出現的是這個畫面:html5
在第一部分的教程中,咱們已經制做了一個遊戲菜單的場景,這個頁面也跟那個差很少,只不過這個頁面去除了遊戲標題和開始按鈕,而多出了Get Ready以及提示點擊屏幕來操做遊戲的兩張圖片。在這個state裏,咱們須要啓用物理引擎,而且要可以響應鼠標點擊事件。ios
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); //點擊屏幕後正式開始遊戲 }; }
啓用物理系統git
默認的遊戲中的每一個對象的物理系統是關閉的,要啓用一個對象的物理系統,可使用 game.physics.enable() 方法github
enable(object, system, debug)
object : 要開啓物理系統的對象,能夠是單個對象,也能夠是一個包含多個對象的數組web
system : 要啓用的物理系統,默認爲 Phaser.Physics.ARCADE,Phaser目前支持三種物理引擎,分別是Arcade ,P2 以及 Ninja。api
debug : 是否開啓調試數組
只有開啓了對象的物理系統,該對象才具備物理特性,開啓了物理系統後,對象的body屬性指向該對象擁有的物理系統,全部與物理相關的屬性或方法都必須在body上進行操做。瀏覽器
鼠標點擊事件app
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遊戲框架,不管是自娛自樂,或是對本身能力的提高,甚至是找工做,都是有必定的益處的。
第一部分教程: