微信小遊戲和白鷺引擎開發實踐

 

前言

文章按照做者調研和開發順序初步介紹和理解了微信小遊戲和白鷺引擎,併產出了基於白鷺引擎的應用初始化程序egret-wechat-start。  如下是正文——
javascript

 

微信小遊戲

官方文檔

如何開發和理解微信小遊戲,先從官方文檔和官方demo入手。  提供一個連接 https://developers.weixin.qq.com/minigame/dev/,能夠快速瀏覽一下官方文檔再繼續看下面的內容。  這裏對微信文檔作個簡單的理解總結,小遊戲和小程序不少地方相似,都是提供了同一套微信Api,好比獲取用戶信息、toast等等,只是有部分提供的api不一樣。  小遊戲對canvas作了封裝,經過  wx.createCanvas() 建立畫布, getContext獲取對象後,剩下的就是對原生canvas接口的操做了。  理解到這一點以後,咱們就會發現小遊戲僅僅是封裝了下建立畫布的接口,剩下的就是用戶須要在畫布裏用原生canvas繪製了,並無提供其餘方便開發的功能。到此咱們再看看微信開發者工具建立小遊戲項目時,初始化的一個飛機遊戲的demo。 
 
是如上圖的一個很簡單的遊戲,說下這個遊戲的大體實現邏輯:
1.  繪製遊戲區域,背景圖片
2. 建立敵機對象,用戶飛機對象,子彈對象
3. 控制3種對象載入畫布和位置改變,控制背景圖片移動,添加音效
4. 判斷子彈碰撞,機身碰撞,而且生成對應結果(敵機消失,遊戲結束)
 
遊戲中和用戶有交互操做有拖動飛機和彈框中的按鈕,整體是一個很簡單的小遊戲,實現過程也並不複雜。  官方demo中最核心的動畫內容就在loop方法裏,使用的是幀動畫( requestAnimationFrame )來實現界面動畫。  針對遊戲實現動畫效果主要有兩種方式,一種就是 requestAnimationFrame幀動畫,一種是用定時器實現。  幀動畫和設備的處理速度有關係,默認1秒60幀,可是在手機設備裏即使很簡單的動畫,性能差點的設備可能幀率都只有20-30左右。  由於幀動畫每秒就要調用n次,也許並不須要那麼高頻率的函數調用,而定時器總的來講對時間的把控和函數調用次數更準確。 好比這個飛機遊戲裏若是有血條的概念,血條的加減其實能夠用單獨的定時器來控制。 一個遊戲裏能夠兩種方式都使用,根據應用場景選擇更合理的方式。  
 
如今根據一個新的需求來作一個遊戲,再來理解小遊戲的開發。  如今需求實現一個回合制遊戲,這個遊戲也有不少頁面,首頁就包含不少按鈕和可能出現的彈窗,也有各類列表頁,還有最關鍵的戰鬥頁面。  在作實現需求以前,須要提供一些公共的基礎模塊:資源預加載,接口攔截器,簡易路由等等。  跳過這些階段,若是咱們拿到ui設計,開始作首頁了,首頁有不少按鈕,咱們須要給A按鈕添加綁定事件,那咱們須要給canvas畫布綁定一個點擊事件,點擊觸發之後咱們獲取到當前用戶點擊位置,並取出A按鈕的位置寬高並計算出範圍,進行判斷是否點擊位置在範圍內,最後再觸發綁定的方法。 好像有點麻煩,可是還能實現,繼續作下去。  後來須要在首頁作一個彈框,這個時候,給彈框的B按鈕綁定點擊事件,又須要經過一樣的方法判斷是否點擊到B按鈕。  這個時候彈框的B按鈕恰好和A按鈕重疊都在一個點擊範圍內,那按鈕A和B的回調都會被執行。  代碼以下:
canvas.addEventListener('click', (event)=>{
    獲取event對象x,y

    獲取 buttonA:x,y,width,height
    判斷是否點擊

    獲取 buttonB:x,y,width,height
    判斷是否點擊
})

 

一個彈窗上面的按鈕點擊,反而把彈框下面的按鈕也點擊到了,這不符合預期,那要解決這個問題,咱們還須要一個層級管理器,根據層級判斷誰應該觸發,誰不該該觸發。  目前就事件處理咱們須要實現兩個基礎功能,事件監聽池和元素對象層級管理器,由於事件只能綁定在canvas上,canvas事件觸發之後,須要一個事件監聽池來遍歷監聽池裏的元素對象並判斷誰被觸發了(監聽池也會隨時增減監聽對象),監聽池獲取的依然是一個對象集,層級管理器判斷出對象集裏最上層的元素進行觸發。  想一想功能好像愈來愈複雜了。  目前還沒考慮完善,不只僅是事件處理問題,還可能會有其它大大小小的問題。  用canvas原生開發,工做量可能會很是大。  因此這樣看來,本身把這些實現了是不科學的,須要使用三方引擎開發才行。  由於兩年前用過白鷺引擎,因此就事件監聽和層級管理這個事情,我知道白鷺引擎已經實現了,除開事件,圖形繪製,動畫等等印象中白鷺都提供了,若是用引擎開發小遊戲實現成本被大大下降。html

 

白鷺引擎

白鷺引擎功能很強大而且豐富。  這裏我先介紹一下我主要使用的工具。  
  • Egret Engine2D
  • Texture Merger
  • Egret 擴展庫
  • Egret Wing

 

Egret Engine2D

開發中主要的核心apijava

Texture Merger

Texture Merger 可將零散紋理拼合爲整圖,同時也能夠解析SWF、GIF動畫,製做Egret位圖文本,導出可供Egret使用的配置文件。  我主要使用其中的精靈圖功能,把圖片集合到一張圖上,而且會同時導出一個json的精靈圖的在圖片中的位置等配置信息git

Egret 擴展庫

擴展庫在覈心引擎功能之上提供了更高級的api,擴展庫在引擎配置文件裏配置好之後,會直接把方法和對象載入到egret全局對象中,目前我主要使用的擴展庫有:github

  1.  RES:  資源管理庫
  2.  EUI: EUI是一套基於Egret核心顯示列表的UI擴展庫,它封裝了大量的經常使用UI組件,可以知足大部分的交互界面需求,即便更加複雜的組件需求,您也能夠基於EUI已有組件進行組合或擴展,從而快速實現需求。
  3.  Game:這個庫好像沒有什麼專門的定義,我主要使用了:ScrollView 滾動視圖。 來處理須要滾動的頁面
  4.  Tween: 緩動動畫庫,相似於GreenSock庫

 

Egret Wing

白鷺開發的代碼編輯器,像其餘編輯器同樣,推薦使用它。
 

egret launcher

固然還須要安裝一個egret launcher來管理引擎、工具和項目打包,小遊戲就須要打包以後才能在微信開發者工具裏使用
 


 

開始egret開發

你能夠快速瀏覽一遍官方教程,以便更好對下文有所理解,http://developer.egret.com/cn/github/egret-docs/Engine2D/getStarted/helloWorld/index.html 。  文章不是教程因此會省略掉那些白鷺官網裏的教程。  如今咱們使用egret launcher建立一個初始化項目,初始化後的文件結構以下圖,我展開了resourcesrc文件夾,由於咱們須要操做的主要是這兩個文件夾,resource文件夾主要是存放靜態資源,咱們的代碼都在src裏,白鷺使用的是typescriptweb

 

wing工具裏,咱們能夠立刻開啓調試,就能夠在瀏覽器或者它自帶的容器裏預覽效果。  main.ts是啓動文件,main中首先使用awaitresource中定義好的圖片資源進行了預加載,因此預覽開始後會出現loading效果,loading的繪製是寫在srcLoadingUI.ts,圖片加載完成之後,main裏直接建立了下圖2的頁面,而且添加了一個按鈕,點擊後會出現一個彈窗。  效果以下圖。typescript

    

 

至此,初始化demo已經告訴了咱們如何繪製圖像和綁定事件了,以下圖,我只截取了click按鈕的代碼,圖像繪製首先須要建立一個相應的egret或者eui對象,好比eui.Button、egret.TextField、egret.Bitmap等等,而後給對象設置相應屬性,好比label、x y座標,width, height等。  再使用mainaddChild載入到畫布中(下面的this就是main對象,main繼承於eui.UILayer)。  demo中的代碼在載入loading的時候,使用了this.stage.addChild,直接addChild或者使用stage.addChild均可以載入到畫布中。  白鷺封裝的addEventListener方法和原生js的監聽方法是同樣的使用方法。  json

 

demo的代碼說到這裏總結一下,咱們在main入口對象中可使用addChild載入一個視圖對象到畫布中,好比文本,按鈕等。  咱們也能夠在mainaddChild一個視圖容器A,視圖容器A也能夠添加文本按鈕等,那咱們在視圖容器A中再次addChild視圖容器B,那麼這樣就造成了層級嵌套main->A->B,若是想象成dom元素就是div.main->div.A->div.B的關係,咱們用代碼來對比一下:canvas

class Main extends eui.UILayer {


    protected createChildren(): void {

        let A = new egret.DisplayObjectContainer();
        this.addChild(A);

        let textA = new egret.TextField();
        textA.text = 'text A Description';
        A.addChild(textA);

        let B = new egret.DisplayObjectContainer();
        A.addChild(B);
        
        let buttonB = new eui.Button();
        buttonB.label = 'button B';
        B.addChild(buttonB);
    }

}

對應小程序

<div class="main">
    <div class="A">
        <span>text A Description</span>
        <div class="B">
            <button value="button B"></button>
        </div>
    </div>
</div>

 

根據以上代碼的理解和咱們要作的需求(實現一個回合制遊戲,這個遊戲也有不少頁面,首頁就包含不少按鈕和可能出現的彈窗,也有各類列表頁,還有最關鍵的戰鬥頁面)。  我在main裏寫一個initElement方法,建立基層容器,代碼以下圖,addChild默認根據前後順序肯定上下層關係,先載入的在下層。  首先最下層建立了一個背景層,接着是ScrollViewbaseContent,頁面容器會載入到他們之中,若是頁面須要滾動會把頁面視圖對象載入到SV中,不須要滾動會載入到baseContent中,Layerloading在更上層的位置。  

 

基層容器準備好之後,咱們能夠建立一個首頁頁面。  我會建立3個文件:base.ts,Index_ui.ts,Index.ts。  Index繼承Index_uiIndex_ui繼承base。  全部的_ui都會繼承basebase會定義通用方法和屬性。  由於一個頁面到最後可能代碼量會比較大,甚至比較亂,因此才把一個頁面拆分紅pagepage_ui_ui裏寫視圖相關代碼,page裏調用_ui的方法、處理請求和編寫邏輯,達到視圖和邏輯分離的效果。  當首頁寫好之後,須要建立一個簡易路由,用路由提供的方法把Index添加到SV容器中。  我把路由直接寫到了mainchangePage就是頁面切換的方法,代碼大體以下:

 

經過removeadd視圖容器達到了切換頁面的效果。  下面說說編寫_ui頁面的規則,下面是Index_ui的部分代碼,el_layout提早把頁面元素的佈局信息提早定義並統一管理。  把Index邏輯頁面須要操做的元素引用到$el對象裏方便調用和操做。  把數據信息統一放在$data中。  建立頁面視圖元素以前,須要把第一個元素的y座標傳給 $firstEleY 這是爲了後面pageContentCenter方法能獲取到準確的頁面內容高度,pageContentCenter要執行在全部頁面元素建立完成以後,pageContentCenter會根據當前頁面的高度再匹配當前設備的高度進行垂直居中。

class Index_ui extends Base {
    public el_layout = {
        indexbg: {x:0, y:0, w:750, h:1665},
        gold: {x:300, y:100, w:300, h:39}
    };
    public constructor() {
        super();
        this.RES_index = RES.getRes('index');
        this.RES_common = RES.getRes('common');
    }
    public RES_index;
    public RES_common;
    public $el = {
        gold: Object(egret.TextField)
    }
    public $data = {
        gold: '0'
    }

    public async createView() {

      //背景
      let RES_bg = new egret.Bitmap( RES.getRes('indexbg') );

      $util.setLayout(RES_bg, this.el_layout['indexbg']);

      RES_bg.fillMode = egret.BitmapFillMode.REPEAT;

      this.$main.PageBg.addChild(RES_bg);


        //頂部元素必傳值
        this.$firstEleY = this.el_layout.gold.y;

        this.pageContentCenter(true);//根據內容計算處理居中
    }
}

 

一個簡易的開發封裝的核心代碼已經搭建好了,然後咱們還須要封裝一些其它工具類,以下圖:配置文件($config)、封裝攔截器($api)、濾鏡($filter)、工具函數($util)、微信api封裝(Wx)。  Platform.ts是白鷺自動生成的文件,根據它的規則本身寫了一個Wx.ts文件,因爲不一樣平臺的接口形式各有不一樣,白鷺推薦開發者經過這種方式封裝平臺邏輯,以保證總體結構的穩定,白鷺推薦開發者將全部接口封裝爲基於 Promise 的異步形式。

 

和src同級的還有一個texture文件夾,裏面是TextureMeger使用精靈圖的相關文件,放在倉庫裏是方便後期管理。

 

 

簡易的初始化demo,我已經更新到github上https://github.com/zimv/egret-wechat-start。  egret-resource是源碼,egret-resource_wxgame是白鷺打包後的文件夾,它在開發者工具裏運行。  egret-resource_wxgame應該在ignore裏忽略,這裏沒有忽略是方便下載源碼的朋友直接在開發者工具裏運行demo。  當前程序使用白鷺引擎版本5.2.5。

 

demo裏隨便寫了幾個頁面,看下效果:

 

 

 

還有踩過不少坑,下面記錄一下:

  • 在公衆號後臺把設置裏的服務類設置成遊戲類,輸入appId後會自動打開開發者工具遊戲開發的界面 
  • 小遊戲自定義字體微信支持程度差
  • 部分功能和api須要註冊的小程序才能使用,好比轉發功能,目前註冊了一個我的小遊戲用於前期開發
  • 使用wing工具編輯代碼,編譯調試,編譯後的代碼會存放在bin-debug文件夾裏,我用的mac,項目菜單裏有三個選項編譯、調試和清理。我新增了一個xx文件,卻在調試的時候一直報錯,檢查瀏覽器source裏也沒有新增的文件,bin-debug也沒有,弄了好久,一直覺得是本身代碼寫錯了,最後意識到多是編譯器有問題,這個時候我點擊了清理按鈕,新增的文件就在bin-debug裏出現了。應該是個bug,要多注意檢查bin-debug裏的文件是否有更新
  • RES.getResByUrl是網絡異步加載,須要提早addChild保證層級正常,請求完成再修改對象的texture屬性,也能夠經過addChildAt方法指定層級。
  • TextField  字體size小於10會影響佈局,文本是否換行取決於設置的元素高度
  • webgl模式沒法加載網絡url圖片
  • scrollView有addChild方法,可是方法裏的代碼是直接拋錯,表示不能用這個接口。它的子元素綁定touchStart move等事件會失效,因此目前又增長裏一個baseContent,根據需求切換父容器 
  • measuredHeight這個測量接口只會測量最上面元素和最下面元素的實際高度,因此第一個元素若是y值大於0要注意配置$firstEleY
  • 全部圖片用工具壓縮,會減小上傳代碼的大小和提高資源加載速度

 

  當這一切都準備好之後,剩下的就是體力活啦,固然還有遊戲最重要的核心玩法實現、動畫和交互效果,這些多是一個遊戲實現難度最大的部分。倉庫地址:https://github.com/zimv/egret-wechat-start 。

相關文章
相關標籤/搜索