H5遊戲開發的一乘輕騎---Phaser

赤~日華發表於2017-12-12

快速入門(普通版)

Phaser 是一款非常優秀的 HTML5 遊戲框架,致力於發展 PC 端和移動端的 HTML5 遊戲,是一款不可多得的神器。

本篇文章將帶你快速入門遊戲的開發(移動端),適合想要了解遊戲開發或者想投入遊戲開發的人群,毫無疑問,Phaser 是一個值得你學習的遊戲框架。

特別注意

Phaser 雖然是一款非常優秀的HTML5遊戲框架,但是也有它軟肋的地方,現在請注意下面兩個 Phaser 處理不了或者處理起來很棘手的地方:

1. 3D。如果你想學做一個很酷的3D動畫,可以學習 Three.js 

2. 視訊(移動端)。如果你在移動端頁面上需要嵌入視訊,請使用原生HTML5標籤 video,如果需要對 video 有更好的擴充套件,可以參考 Video.js 

接下來你可以先開啟我們這篇文章所講的 案例 (伺服器渣渣,可能要等好久好久哈哈哈),看完這個案例,你可能會說,這有什麼的,我用Jquery的動畫都做得出來,哈哈,以前我也對一個大神說過類似的話,然後人家很清楚地跟我說拿 Phaser 的動畫跟 Jquery 的動畫比簡直是在侮辱 Phaser。

好了,話就說到這裡,我個人覺得太複雜的案例反而會讓人理不清思路,便有違我們的快速入門的宗旨,相信看完這篇文章之後,你可以開啟遊戲開發的篇章,自由的描繪喜歡的世界。

程式碼地址

說在前面

首先,為了能正常執行我們的案例,你需要一個本地伺服器。你可能會有疑問,為什麼我們不能直接把 index.html  拖進瀏覽器中並執行呢?

因為這與用於訪問檔案的協議有關。當你在網路上請求任何東西時,你需要使用HTTP,而伺服器可以保證你只能訪問你想要的檔案。但是當你直接把 html 檔案拖到瀏覽器上直接執行時,它是通過本地檔案系統(file://)載入的,由於本地檔案系統沒有域的概念,沒有伺服器級別的安全,只是一個原始檔案系統。

您真的希望JavaScript能夠從檔案系統中的任何地方載入檔案嗎?答案當然是不行。你的計算機當然也不會同意。

而 Phaser 需要載入資源:影象,音訊檔案,JSON資料,或者其他Javascript檔案。為了做到這些,它就需要執行在伺服器下。

如果你對如何配置一個本地伺服器來執行 html 檔案感到毫無頭緒,並且這也不是我們這篇文章的重點,那我可以介紹一個很簡單的方式,你可以嘗試進行下列操作:

1. 開啟命令列,輸入以下命令回車: npm install puer -g (如果沒有 npm 請自行百度安裝)2. puer 安裝完成後,在需要搭載在本地伺服器的案例根資料夾下呼叫命令列,輸入以下命令回車:puer -p 9999 如下:

H5遊戲開發的一乘輕騎---Phaser

3.如果puer執行成功則如下並自動開啟瀏覽器:

H5遊戲開發的一乘輕騎---Phaser

如下案例已經搭載在地址為 http://localhost:9999/ 上了,點選 index.html 就可以把 Phaser 執行起來啦

H5遊戲開發的一乘輕騎---Phaser

開始第一步

首先建立我們的目錄結構如下:

H5遊戲開發的一乘輕騎---Phaser

img資料夾下的圖片可以自行從 案例程式碼地址 上拷貝,然後你會發現,咦,GitHub上怎麼多了一張 sprites.png 圖片和一個 sprites.xml 檔案,這個後面將會講到,現在我們只需要準備現階段需要的,js/libs檔案下的 jquery.min.jsphaser.min.js 請自行準備或從 案例程式碼地址 上拷貝,然後其它檔案我們需要保證空白以便我們後面迅速填充內容。

第二步

首先當然是向我們的 index.html 快速填充內容啦,內容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Phaser快速入門(普通版)</title>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
</head>
<style>
    html,
    body {
        width: 100%;
        height: 100%;
        font-family: "Helvetica Neue", Helvetica, STHeiTi, sans-serif;
        overflow: hidden;
        background: #fff;
        margin: 0;
        padding: 0;
    }
    .main {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
    }
    .main img {
        width: 100%;
        height: 100%;
    }
</style>
<body>
    <!--H5頁面所有動畫的canvas載體-->
    <div id="game-container"></div>
    <!--預載入背景,防止預載入的資源載入太久以致螢幕長時間空白-->
    <div class="main">
        <img src="img/bg.png" alt="">
    </div>
</body>
<script src="js/libs/jquery.min.js"></script>
<script src="js/states/utils.js"></script>
<script src="js/libs/phaser.min.js"></script>
<script src="js/states/boot.js"></script>
<script src="js/states/preload.js"></script>
<script src="js/states/state1.js"></script>
<script src="js/states/state2.js"></script>
<script src="js/app.js"></script>
</html>複製程式碼

好了,內容填充完畢,在這個檔案裡面首先我們案例所有動畫都是搭載在id為 game-container 的元素上,然後class為 main 的元素是預載入背景,看了註釋對它的用意還不是很清楚的話可以不用深究,後面自然就清楚了。

然後你還會發現我們匯入了一個 jquery.min.js ,你可能會有這樣的疑問,這是要幹嘛?難道在使用 Phaser 的時候還需要先匯入 Jquery 嗎?答案肯定不是,匯入 Jquery 只是為了在後面證明一些東西。utils.js 是封裝了一些我們在後面經常用到的操作(滿屏效果,縮放圖片)

接下來我們把重點放在我們這個案例執行起來的機制,先來看看我們的5個核心檔案:

<script src="js/states/boot.js"></script>
<script src="js/states/preload.js"></script>
<script src="js/states/state1.js"></script>
<script src="js/states/state2.js"></script>
<script src="js/app.js"></script>複製程式碼

這5個檔案的執行順序依次是:

app.js -> boot.js -> preload.js -> state1.js -> state2.js

app.js 裡面會執行 boot.jspreload.jsstate1.jsstate2.js 這4個場景,因此 app.js 需要在匯入 boot.jspreload.jsstate1.jsstate2.js 這4個檔案之後匯入。接下來逐一解析這5個檔案。

app.js

這個檔案是我們 Phaser 執行的第一個檔案,主要進行一些遊戲開發方面的配置,包括設定我們資源的路徑,掛載元素,新增場景,啟動場景等。

具體程式碼如下:

(function() {
    'use strict'

    // 設定資源目錄(專案根目錄)
    var baseURI ='../..'

    //將圖片目錄放在記憶體中,方便全域性呼叫
    localStorage.baseURI = baseURI

    //設定$('#game-container')的高度等於螢幕的高度(這裡用原生js程式碼書寫)
    document.getElementById('game-container').style.height = document.body.clientHeight + 'px'

    //獲取螢幕的縮放比
    var Ratio = window.devicePixelRatio

    //獲取螢幕的寬和高
    var w = document.documentElement.clientWidth || document.body.clientWidth
    var h = document.documentElement.clientHeight || document.body.clientHeight

    //因為我們在index.html設定了禁止縮放的meta頭
    //<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    //所以當我們將螢幕的寬和高直接傳入Phaser.Game物件時,瀏覽器會自動將canvas按螢幕的縮放比縮放,也就是當你的螢幕縮放比是2時,canvas寬高你設定成螢幕的寬高時實際看到的卻只有一半,因此相應我們需要將畫布放大Ratio倍
    var ww = Ratio * w
    var hh = Ratio * h

    //前兩個引數是Phaser要建立的canvas元素的寬高,第三個引數是遊戲渲染的引擎,這裡讓Phaser自己識別設定即可,第四個引數是遊戲掛載在哪個元素上
    var game = new Phaser.Game(ww, hh, Phaser.AUTO, 'game-container')

    //新增場景
    game.state.add('Boot', Boot)
    game.state.add('Preload', Preload)
    game.state.add('State1', State1)
    game.state.add('State2', State2)

    //啟動場景
    game.state.start('Boot')

})(window)複製程式碼

上面的每一行程式碼都已經加上相應的註釋,如果有空的話可以看一下並理解清楚,現在我們只需知道 app.js 只是用來配置並且啟動我們的第一個場景(boot.js)就行。

在說我們的場景之前,我們先來了解什麼是場景,場景是我們 Phaser 遊戲開發的主體,我們所有看到的遊戲動畫都是在場景裡實現,場景可以是一個js自定義物件,也可以是一個函式,只要存在preload、create、update這三個方法中的任意一個,就是一個合法的場景(總共5個方法,另外兩個是init和render方法)。

init方法:一些場景的初始化程式碼可以寫在這個方法裡,最先執行。

preload方法:用來載入資源的,如果沒有init方法則它會最先執行。

create方法:初始化以及構建場景,會等到preload方法里載入的資源全部載入完成後執行。

update方法:更新函式,它會在遊戲的每一幀都執行,一般是1/60秒執行一次。

render方法:在遊戲的每一渲染週期都會呼叫,用來做一些自定義的渲染工作。

boot.js

這個場景不會向使用者展示,只是為了載入下個場景(即預載入場景preload.js)所需的資源,因此資源不能載入太多,太多的話則螢幕會長時間黑屏(黑屏其實就是這個場景載入資源的過程,因為這是第一個場景,沒有上個場景幫你載入好的資源用來構建介面,所以也就沒有可以友好顯示給使用者的介面),而這個問題我們也在index.html 用 $('.main') 解決,即先展示 $('.main') 這個元素展現出來的介面(介面背景需要跟預載入場景背景一樣),以便這個場景載入太久有個良好的使用者體驗,而當這個場景載入完後 $('.main') 也可以實現跟預載入場景的良好銜接。

具體程式碼如下:

var Boot = function(game) {
    var baseURI = localStorage.baseURI
    this.init = function() {
        //game.device.desktop判斷是WAP端還是PC端,true為PC端,false為WAP端
        if (!game.device.desktop) {
            //設定遊戲背景色
            game.stage.backgroundColor = '#282C34';
            //滑鼠指標物件,由於WAP端沒有滑鼠,因此設定為1(即為null)
            game.input.maxPointers = 1;
            //縮放控制,這裡將畫布(canvas)拉伸至填滿父容器(即#game-container),不保持比例
            game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
            //啟用時,顯示畫布(canvas)將在父容器中水平對齊
            game.scale.pageAlignHorizontally = true;
            //啟用時,顯示畫布(canvas)將在父容器中垂直對齊
            game.scale.pageAlignVertically = true;
            //強制遊戲只能以一個方向執行,這裡設定遊戲僅能在縱向模式執行(true),無法再橫向模式執行(false)
            game.scale.forceOrientation(false, true);
            //當forceOrientation設定只能縱向模式執行,把手機橫向擺放就會呼叫enterIncorrectOrientation這個方法
            game.scale.enterIncorrectOrientation.add(enterIncorrectOrientation, this);
            //當forceOrientation設定只能縱向模式執行,把手機橫向擺放就會呼叫enterIncorrectOrientation這個方法,重新將手機縱向擺放就會呼叫leaveIncorrectOrientation這個方法
            game.scale.leaveIncorrectOrientation.add(leaveIncorrectOrientation, this);
        }else{
            game.scale.pageAlignHorizontally = true
            game.scale.pageAlignVertically = true
            //防止瀏覽器失去焦點後動畫暫停,如果考慮到計算機效能可以設定為false
            game.stage.disableVisibilityChange = true;
        }

    }
    this.preload = function() {
        //設定圖片支援跨域請求
        game.load.crossOrigin = true
        //載入預載入介面所需的資源,可以看到圖片跟$('.main')介面一致,第一個引數是建立sprite物件時所需要的資源標識,第二個引數是資源所在路徑
        game.load.image('start_bg', baseURI + '/img/bg.png')

    }
    this.create = function() {
        //跳轉到下一個場景
        game.state.start('Preload');
    }
    function enterIncorrectOrientation() {
        alert('請將手機縱向擺放');
    }
    function leaveIncorrectOrientation() {
        alert('已經將手機縱向擺放');
    }
}
複製程式碼

上面的每一行程式碼已經都加上相應的註釋,如果有空的話也可以看一下並理解清楚,現在我們只需知道 boot.js 只是用來設定我們所有場景並且載入我們的第二個場景(預載入場景preload.js)所需的資源就行。

preload.js

這個場景是預載入場景,會載入後面所有場景的資源,由於載入的資源比較多,所以在這個場景我們需要以一種友好的方式展現我們資源載入的過程,用到的是上個場景已經幫我們載入完成的 start_bg 資源,和 Phaser 自身的文字物件(顯示載入進度)。

preload.js 特別注意我們需要載入一個幀圖片(實現動圖效果)

//載入幀圖片,第一個引數是建立sprite物件時所需要的資源標識,第二個引數是圖片所在路徑,第三個引數是標識圖片xml檔案
game.load.atlasXML('sprites', baseURI + '/img/sprites.png',baseURI + '/img/sprites.xml')複製程式碼

載入幀圖片我們需要一張包含所有動作的整體圖片,還需要一個配置xml檔案。那麼問題來了,我們為什麼需要兩個檔案呢,為什麼不直接把幀圖片做成動圖然後展示,這個問題其實很簡單,首先直接一張動圖遠遠比一張靜態圖片大的多(xml檔案太小忽略不計),而且我們也無法在程式碼中控制動圖的切換速度。

好吧,現在按我們的想法來,如果我們有兩張需要在 Phaser 做成動圖的圖片,比如下面:

H5遊戲開發的一乘輕騎---Phaser

然後我們要怎麼把它們合成我們想要的一張整體圖和一個配置xml檔案呢?這個時候我們就需要一個軟體來幫我們處理了,這裡我來介紹 Shoebox :

H5遊戲開發的一乘輕騎---Phaser

官網地址

下載安裝完成後,我們開啟 Shoebox ,將需要合成動圖的各幀圖片一起移到 Sprite Sheet ,如圖鬆手放開:

H5遊戲開發的一乘輕騎---Phaser

點選 Save ,就已經生成了一張幀圖片和一個xml檔案:

H5遊戲開發的一乘輕騎---Phaser

xml檔案主要記載我們幀圖片包含的每張動作圖片的位置和名稱資訊,當然,這不是我們關注的重點。

具體程式碼如下:

var Preload = function(game) {
    var baseURI = localStorage.baseURI
    var tool = new utils()
    var w = null
    var h = null
    var start_bg = null
    var loading = null
    this.init = function() {
        //獲取畫布的寬高,即Ratio倍螢幕寬高
        w = game.width
        h = game.height

        //動畫組,方便統一處理多個物件的動畫
        group=game.add.group()

        //將$('.main')元素隱藏
        $('.main').hide()

        //由於start_bg跟$('.mian')介面一致,使用者是看不出$('.mian')隱藏start_bg顯示這個過程的
        //建立sprite物件,第一個引數是畫布x座標(距離畫布左邊緣多遠),第二個引數是畫布y座標(距離畫布上邊緣多遠),第三個引數是構建物件的資源
        start_bg = game.add.sprite(0, 0, 'start_bg')
        //將start_bg寬高設定全屏
        tool.setFull(start_bg)

        //建立文字物件,前面兩個引數跟sprite等同,第三個引數是文字內容,第四個引數是文字樣式
        loading=game.add.text(w*.5, h*.5, '0%',
            {
                fontSize:60,
                fill:'#ffffff'
            })
        //錨點位置(相對自身),第一個引數是相對自身左移多少(.5是左移自身寬度的50%),第二個引數是相對自身上移多少(.5是上移自身高度的50%)
        loading.anchor.set(.5, .5)
        //loading最後的位置是相對畫布居中

        //loading的補間動畫,from(從怎樣的狀態轉變到預設狀態),to(從預設狀態轉變到怎樣的狀態),這裡用的是from
        //第一個引數:一個js物件,包含著需要進行動畫的屬性,{ alpha: 0 }表示透明度為0
        //第二個引數:動畫的持續時間
        //第三個引數:動畫過程函式,預設為勻速動畫Phaser.Easing.Linear.None
        //第四個引數:是否自動開始
        //第五個引數:動畫開始前的延遲時間,單位是毫秒
        //第六個引數:動畫重複的次數,如果需要動畫永遠迴圈,則把該值設為 Number.MAX_VALUE
        //第七個引數:是否自動反轉
        game.add.tween(loading).from({ alpha: 0 }, 500, null, true, 0, 0, false)

    }
    this.preload = function() {
        game.load.crossOrigin = true

        //載入幀圖片,第一個引數是建立sprite物件時所需要的資源標識,第二個引數是圖片所在路徑,第三個引數是標識圖片xml檔案
        game.load.atlasXML('sprites', baseURI + '/img/sprites.png',baseURI + '/img/sprites.xml')
        
        game.load.image('next', baseURI + '/img/next.png')
        game.load.image('img2_1', baseURI + '/img/2_1.jpg')
        game.load.image('img2_2', baseURI + '/img/2_2.jpg')

        //這個方法是檔案載入過程,返回的progeress是完成的進度,0~100
        game.load.onFileComplete.add(function(progeress) {
            loading.setText(progeress + '%')
        })

        //所有檔案都完成載入時會呼叫這個方法,我們可以在呼叫這個方法的時候跳轉到下一個場景,效果等同於在create方法執行game.state.start('State1')
        game.load.onLoadComplete.add(function() {
            game.state.start('State1')
        })

    }
    this.create = function() {
       //game.state.start('State1')    }
    this.update = function() {}
}複製程式碼

上面的程式碼我們要特別注意 $('.main').hide() ,這也是我們在前面匯入 jquery.min.js 的要證明的東西,就是你可以在 Phaser 裡面寫 Jquery 的任何程式碼。

如果上面兩個場景(boot.jspreload.js)你已經完全理解和摸透了話,那下面這兩個場景(state1.jsstate2.js)估計你能夠很快速就看完。

state1.js

這個場景主要應用到 Phaser 的動圖,事件還有動畫組的運用。在這裡細心的小夥伴可以看到我們把本來寫在create方法的初始化以及構建場景的程式碼寫在了preload方法裡,就我的經驗,只要一個場景不需要預載入資源,preload方法和create方法的效用並沒有差別。

具體程式碼如下:

var State1 = function(game) {

    var baseURI = localStorage.baseURI

    var tool = new utils()
    var w = null
    var h = null
    
    var next=null
    var sprites=null
    var group=null

    this.preload = function() {

      w = game.width
      h = game.height

      //動畫組,方便統一處理多個物件的動畫
      group=game.add.group()

      //第四個引數是沒有加入動畫時靜態展示第幾幀圖片
      sprites=game.add.sprite(w*.5,h*.6,'sprites',0)
      tool.setSize(sprites,'width',w*.5)
      sprites.anchor.set(.5,1)
      //給sprite物件新增一個新動畫,第一個引數是動畫名稱
      sprites.animations.add('run')
      //播放動畫,第一個引數是動畫名稱,第二個引數是播放的速率,第三個引數是是否迴圈
      sprites.animations.play('run',4,true)
      
      
      //將start_bg加入動畫組
      group.add(sprites)

      next=game.add.sprite(w*.5,h*.8,'next')
      tool.setSize(next,'width',w*.3)
      next.anchor.set(.5,.5)
      game.add.tween(next).to({ width:next.width+20,height:next.height+20 }, 500, Phaser.Easing.Linear.In, true, 0, -1, true)

      group.add(next)

      //預設情況下,遊戲物件不會處理任何事件,所以我們需要讓它可以處理事件
      next.inputEnabled = true

      //當對next物件點選然後手指放開的時候觸發
      next.events.onInputUp.add(function() {

        //onComplete方法是補間動畫完成後的回撥,我們可以在跳轉到下一個場景的時候做一些使用者體驗比較良好的當前場景的退場動畫(這裡的退場動畫類似淡出效果)
        game.add.tween(group).to({ alpha: 0 }, 500, Phaser.Easing.Linear.In, true, 0, 0, false).onComplete.add(function() {
            game.state.start('State2')
        })
        
      })

    }

}複製程式碼

state2.js

這個場景只是為了承接上個場景 next 按鈕的點選跳轉。

var State2 = function(game) {
  
    var w = null
    var h = null

    var img2_1=null
    var img2_2=null

    this.preload = function() {
      w = game.width
      h = game.height

      img2_1=game.add.sprite(0,h*.5,'img2_1')
      img2_1.width=w
      img2_1.height=h*.5
      img2_1.anchor.set(0,1)
      game.add.tween(img2_1).to({ y:h }, 2000, Phaser.Easing.Linear.In, true, 500, -1, true)

      img2_2=game.add.sprite(0,h*.5,'img2_2')
      img2_2.width=w
      img2_2.height=h*.5
      game.add.tween(img2_2).to({ y:0 }, 2000, Phaser.Easing.Linear.In, true, 500, -1, true)
    }
    this.create = function() {}
}複製程式碼

好了,state2.js 就不做過多的贅言了,看到了這裡相信你早已知道這個場景是怎樣的一種動畫效果了。

煮了一天的雞湯

好了,到這裡我們就好好結束吧,Phaser 還有太多有趣的地方等著你去發掘,如果想要了解更多可以直接去 官網 ,裡面有很多有趣的小例子。

在這裡特別鳴謝眯眼貓的黎世燦大神帶我瘋狂入坑,並且對這篇文章的技術支援。

在這裡祝賀大家雙12快樂,事業進步!


相關文章