微信小游戏和白鹭引擎开发实践

 

前言

文章按照做者调研和开发顺序初步介绍和理解了微信小游戏和白鹭引擎,并产出了基于白鹭引擎的应用初始化程序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 。

相关文章
相关标签/搜索