>

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

- 编辑:至尊游戏网站 -

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

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

2018/09/05 · JavaScript · 小游戏

原文出处: 子慕大诗人   

Egret API

前言

文章按照作者调研和开发顺序初步介绍和理解了微信小游戏和白鹭引擎,并产出了基于白鹭引擎的应用初始化程序egret-wechat-start。  以下是正文——

 

egret.Shape

此类用于使用绘图应用程序编程接口 (API) 创建简单形状。
Shape 类含有 graphics 属性,通过该属性您可以访问各种矢量绘图方法。

var bg:egret.Shape = new egret.Shape();
bg.graphics.beginFill( 0x336699 );
bg.graphics.drawRect( 0, 0, this.stage.stageWidth, this.stage.stageHeight );
bg.graphics.endFill();
super.addChild( bg );
  • graphic 图形绘制
    • beginFill 设置填充颜色
    • drawRect 绘制矩形
    • endFill 用来结束绘制工作。
    • super.addChild 将某个显示对象添加到某个显示容器上
      这是Egret引擎操作显示列表的一个最常用的方法,这里使用 super 是由于所调用的方法 addChild 是当前类的父类定义的。根据个人习惯,这里完全可以用 this.addChild

微信小游戏

egret.TextField

TextField是egret的文本渲染类,采用浏览器/设备的API进行渲染,在不同的浏览器/设备中由于字体渲染方式不一,可能会有渲染差异如果开发者希望所有平台完全无差异,请使用BitmapText

var tx:egret.TextField = new egret.TextField();
tx.text = "Hi,你好,我是duminghong";
tx.size = 32;
tx.x = 20;
tx.y = 20;
tx.textColor = 0xfefefe;
tx.width = this.stage.stageWidth - 40;
this.addChild( tx );
  • text 设置文本
  • size 设置文字大小
  • x、y 设置文本对象的x和y坐标
  • textColor 设置文本颜色
  • width 设置文本的宽度
  • this.addChild 将某个显示对象添加到某个显示容器上

官方文档

如何开发和理解微信小游戏,先从官方文档和官方demo入手。  提供一个链接,可以快速浏览一下官方文档再继续看下面的内容。  这里对微信文档做个简单的理解总结,小游戏和小程序很多地方类似,都是提供了同一套微信Api,比如获取用户信息、toast等等,只是有部分提供的api不同。  小游戏对canvas做了封装,通过 wx.createCanvas() 创建画布,``getContext获取对象后,剩下的就是对原生canvas接口的操作了。  理解到这一点之后,我们就会发现小游戏仅仅是封装了下创建画布的接口,剩下的就是用户需要在画布里用原生canvas绘制了,并没有提供其他方便开发的功能。到此我们再看看微信开发者工具创建小游戏项目时,初始化的一个飞机游戏的demo。

图片 1

是如上图的一个很简单的游戏,说下这个游戏的大致实现逻辑:

1.  绘制游戏区域,背景图片

  1. 创建敌机对象,用户飞机对象,子弹对象

  2. 控制3种对象载入画布和位置改变,控制背景图片移动,添加音效

  3. 判断子弹碰撞,机身碰撞,并且生成对应结果(敌机消失,游戏结束)

游戏中和用户有交互操作有拖动飞机和弹框中的按钮,总体是一个很简单的小游戏,实现过程也并不复杂。  官方demo中最核心的动画内容就在loop方法里,使用的是帧动画( requestAnimationFrame )来实现界面动画。  针对游戏实现动画效果主要有两种方式,一种就是requestAnimationFrame帧动画,一种是用定时器实现。  帧动画和设备的处理速度有关系,默认1秒60帧,但是在手机设备里即便很简单的动画,性能差点的设备可能帧率都只有20-30左右。  因为帧动画每秒就要调用n次,也许并不需要那么高频率的函数调用,而定时器总的来说对时间的把控和函数调用次数更准确。 比如这个飞机游戏里如果有血条的概念,血条的加减其实可以用单独的定时器来控制。 一个游戏里可以两种方式都使用,根据应用场景选择更合理的方式。

现在根据一个新的需求来做一个游戏,再来理解小游戏的开发。  现在需求实现一个回合制游戏,这个游戏也有很多页面,首页就包含很多按钮和可能出现的弹窗,也有各种列表页,还有最关键的战斗页面。  在做实现需求之前,需要提供一些公共的基础模块:资源预加载,接口拦截器,简易路由等等。  跳过这些阶段,如果我们拿到ui设计,开始做首页了,首页有很多按钮,我们需要给A按钮添加绑定事件,那我们需要给canvas画布绑定一个点击事件,点击触发以后我们获取到当前用户点击位置,并取出A按钮的位置宽高并计算出范围,进行判断是否点击位置在范围内,最后再触发绑定的方法。 好像有点麻烦,但是还能实现,继续做下去。  后来需要在首页做一个弹框,这个时候,给弹框的B按钮绑定点击事件,又需要通过同样的方法判断是否点击到B按钮。  这个时候弹框的B按钮刚好和A按钮重叠都在一个点击范围内,那按钮A和B的回调都会被执行。  代码如下:

JavaScript

canvas.addEventListener('click', (event)=>{ 获取event对象x,y 获取 buttonA:x,y,width,height 判断是否点击 获取 buttonB:x,y,width,height 判断是否点击 })

1
2
3
4
5
6
7
8
9
canvas.addEventListener('click', (event)=>{
    获取event对象x,y
 
    获取 buttonA:x,y,width,height
    判断是否点击
 
    获取 buttonB:x,y,width,height
    判断是否点击
})

一个弹窗上面的按钮点击,反而把弹框下面的按钮也点击到了,这不符合预期,那要解决这个问题,我们还需要一个层级管理器,根据层级判断谁应该触发,谁不应该触发。  目前就事件处理我们需要实现两个基础功能,事件监听池和元素对象层级管理器,因为事件只能绑定在canvas上,canvas事件触发以后,需要一个事件监听池来遍历监听池里的元素对象并判断谁被触发了(监听池也会随时增减监听对象),监听池获取的依然是一个对象集,层级管理器判断出对象集里最上层的元素进行触发。  想想功能好像越来越复杂了。  目前还没考虑完善,不仅仅是事件处理问题,还可能会有其它大大小小的问题。  用canvas原生开发,工作量可能会非常大。  所以这样看来,自己把这些实现了是不科学的,需要使用三方引擎开发才行。  因为两年前用过白鹭引擎,所以就事件监听和层级管理这个事情,我知道白鹭引擎已经实现了,除开事件,图形绘制,动画等等印象中白鹭都提供了,如果用引擎开发小游戏实现成本被大大降低。

响应用户操作

var tx:egret.TextField = new egret.TextField();
tx.text = "Hi,你好,我是duminghong";
tx.size = 32;
tx.touchEnabled = true;
tx.addEventListener( egret.TouchEvent.TOUCH_TAP, this.touchHandler, this );
  • 第一行设置touchEnabled为true,意即允许该显示对象响应Touch事件,这是Egret中特别需要注意的问题。因为所有的显示对象,默认都是不响应Touch事件的,这是基于性能考虑,因为打开对这种事件的响应,是对性能有不可忽略的影响的。

  • 第二行代码新增一个方法的引用,这就是事件处理函数,我们需要事件处理函数中对用户操作做出对应的反应。

在Main类中,加入如下代码:

private touchHandler( evt:egret.TouchEvent ):void{
    var tx:egret.TextField = evt.currentTarget;
    tx.textColor = 0x00ff00;
}

这里的事件处理函数是用一个类方法来实现,还有一种简写的方法,直接作为匿名函数传入:

tx.addEventListener(egret.TouchEvent.TOUCH_TAP, function(evt: egret.TouchEvent): void {
    tx.textColor = 0x0000ff;
}, this);

白鹭引擎

白鹭引擎功能很强大并且丰富。  这里我先介绍一下我主要使用的工具。

  • Egret Engine2D
  • Texture Merger
  • Egret 扩展库
  • Egret Wing图片 2

资源加载

Egret中所有的资源都是动态加载的。

Egret Engine2D

开发中主要的核心api

资源加载清单

通常 Egret 中的资源加载配置文件位于项目目录的resource文件夹内,取名 default.res.json

默认的 default.res.json 已经包含若干资源的配置:

{
    "resources": [
        {
            "name": "bgImage",
            "type": "image",
            "url": "assets/bg.jpg"
        },
        {
            "name": "egretIcon",
            "type": "image",
            "url": "assets/egret_icon.png"
        },
        {
            "name": "description",
            "type": "json",
            "url": "config/description.json"
        }
    ],
    "groups": [
        {
            "name": "preload",
            "keys": "bgImage,egretIcon"
        }
    ]
}
  • resource 可以视为资源库,当前游戏使用到的资源都可以放到这里。其中以资源为单位分别列出。每一项资源单位都包含三个属性:

    • name:表示这个资源的唯一标识符。注意资源比较多的项目应确定一套命名规则,避免不同资源命名之间重复或太接近而易混淆。

    • type:表示资源类型。

      每个 resource 单位中的type,是Egret约定好的若干类型,最常用的有以下类型:

      • image:表示各种常见的图片类型,包括 PNGJPG 格式,载入后将解析为 egret.Texture对象
      • text:表示文本类型,即文本文件,载入后将解析为 string对象
      • json:也是一种文本类型,不过内容是 json 格式的,载入后将直接解析为 json对象
  • groups 是预加载资源组。很多情况下,我们在某种游戏场合,需要同时加载若干资源,用以准备后续的游戏流程显示。们可以将若干项资源定义为一个资源组。需要时,只需加载这个资源组即可。

    每项是一个资源组。每一个资源组须包含两个属性:

    • name:表示资源组的组名

    • keys:表示这个资源组包含哪些资源,里面的逗号分隔的每一个字符串,都与 resource 下的资源 name 对应。

看看实际应用:

{
    "resources": [
        {
            "name": "figure_01",
            "type": "image",
            "url": "assets/pic_1.png"
        },
        {
            "name": "figure_02",
            "type": "image",
            "url": "assets/pic_2.png"
        },
        {
            "name": "figure_03",
            "type": "image",
            "url": "assets/pic_3.png"
        },
        {
            "name": "figure_04",
            "type": "image",
            "url": "assets/pic_4.png"
        },
        {
            "name": "figure_05",
            "type": "image",
            "url": "assets/pic_5.png"
        },
        {
            "name": "figure_06",
            "type": "image",
            "url": "assets/pic_6.png"
        },
        {
            "name": "change",
            "type": "sound",
            "url": "assets/change.mp3"
        },
        {
            "name": "bgMusic",
            "type": "sound",
            "url": "assets/bg.mp3"
        }
    ],
    "groups": [
        {
            "name": "figure",
            "keys": "figure_01,figure_02,figure_03,figure_04,figure_05,figure_06,change,bgMusic"
        }
    ]
}

Texture Merger

Texture Merger 可将零散纹理拼合为整图,同时也可以解析SWF、GIF动画,制作Egret位图文本,导出可供Egret使用的配置文件。  我主要使用其中的精灵图功能,把图片集合到一张图上,并且会同时导出一个json的精灵图的在图片中的位置等配置信息

在程序中加载资源

Main.ts 中的开头部分,我们会发现大量使用RES开头的代码,RES就是专门用来加载资源的类,这些代码我们稍后再分析,首先我们完成把这些图片载入所需的步骤。

注意,在 onConfigComplete 的最后,有一行加载资源组的代码: RES.loadGroup("preload");

很显然,loadGroup 就是用来加载资源组的。由于我们将资源组命名为 figure,因此这里代码中的 preload 需要改成 figure。 资源加载结束后,我们需要判断所加载的资源是哪个资源组的,所以 onResourceLoadComplete 中的 preload 也需要改成 figure

完成这些改动后,Egret将会加载 figure 资源组,并且程序执行到 createGameScene 时,资源组已经加载完成。

Egret 扩展库

扩展库在核心引擎功能之上提供了更高级的api,扩展库在引擎配置文件里配置好以后,会直接把方法和对象载入到egret全局对象中,目前我主要使用的扩展库有:

  1.  RES:  资源管理库
  2.  EUI: EUI是一套基于Egret核心显示列表的UI扩展库,它封装了大量的常用UI组件,能够满足大部分的交互界面需求,即使更加复杂的组件需求,您也可以基于EUI已有组件进行组合或扩展,从而快速实现需求。
  3.  Game:这个库好像没有什么专门的定义,我主要使用了:ScrollView 滚动视图。 来处理需要滚动的页面
  4.  Tween: 缓动动画库,类似于GreenSock库图片 3

加载资源的代码分析

在进一步显示图片前,我们了解一下资源加载的代码。 再回过头看看加载资源的代码。加载资源的过程整体分为两部分,第一步首先加载资源配置清单,第二步就是加载资源。

在onAddToStage方法中,有代码:

RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE, this.onConfigComplete, this);
RES.loadConfig("resource/default.res.json", "resource/");

这是专门用来加载资源配置的代码。 首先添加一个针对 CONFIG_COMPLETE 事件的侦听,然后执行加载。 配置加载完成时,即会执行 onConfigComplete 方法。

在onConfigComplete方法中,有如下 :

RES.removeEventListener(RES.ResourceEvent.CONFIG_COMPLETE, this.onConfigComplete, this);
RES.addEventListener(RES.ResourceEvent.GROUP_COMPLETE, this.onResourceLoadComplete, this);
RES.addEventListener(RES.ResourceEvent.GROUP_LOAD_ERROR, this.onResourceLoadError, this);
RES.addEventListener(RES.ResourceEvent.GROUP_PROGRESS, this.onResourceProgress, this);
RES.loadGroup("figure");

第一行移除了对 CONFIG_COMPLETE 事件的侦听,这是一个推荐做法,因为我们不再需要加载配置文件,该侦听也就没有作用了。及时移除事件侦听可以删除不必要的引用,使得不再需要使用的对象能被垃圾回收及时清理,避免内存泄露。

接着,加入了对资源组事件的侦听。

首先是对资源组加载完成的侦听,这是必须的,因为程序的流程需要从这里进行,即程序需要在某种资源加载完成后进行预期的后续流程。 另外,任何加载都需要稳定的网络,而网络出现各种中断是很常见的情况,所以需要添加对加载错误事件的侦听,以在这种情况作出相应的处理,通常是重新加载或者是提示用户检查网络。 可选的,可以加入对加载进度的侦听,通常是通过某种样式的进度条显示给用户当前进度,这在所加载的内容需要耗时较长时对于用户体验非常重要。

对于加载错误和进度的侦听处理,我们这里不做过多说明。

在加载完成的处理,即 onResourceLoadComplete 中,通过检查当前加载完成的资源组名称,来做对应的处理。确认当前加载的资源组是 figure 后,便进入程序的正式流程 createGameScene 中。

Egret Wing

白鹭开发的代码编辑器,像其他编辑器一样,推荐使用它。

显示图片

Bitmap 类表示用于显示位图图片的显示对象。利用 Bitmap() 构造函数,可以创建包含对 BitmapData 对象引用的 Bitmap 对象。

创建了 Bitmap 对象后,使用父级 DisplayObjectContainer 实例的 addChild()addChildAt() 方法可以将位图放在显示列表中。

一个 Bitmap 对象可在若干 Bitmap 对象之中共享其 texture 引用,与缩放或旋转属性无关。由于能够创建引用相同 texture 对象的多个 Bitmap 对象,因此,多个显示对象可以使用相同的 texture 对象,而不会因为每个显示对象实例使用一个 texture 对象而产生额外内存开销。

// 火炮兰
var Pohwaran: egret.Bitmap = new egret.Bitmap(RES.getRes("figure_01"));
Pohwaran.x = -40;
Pohwaran.y = 20;
this.addChild(Pohwaran);

// 德玛 德玛西亚之力·盖伦
var Garen: egret.Bitmap = new egret.Bitmap(RES.getRes("figure_02"));
Garen.x = 40;
Garen.y = 20;
this.addChild(Garen);

// 男枪 法外狂徒·格雷福斯
var Graves : egret.Bitmap = new egret.Bitmap(RES.getRes("figure_03"));
Graves .x = 120;
Graves .y = 20;
this.addChild(Graves);

// 南小鸟
var Minami: egret.Bitmap = new egret.Bitmap(RES.getRes("figure_04"));
Minami.x = 200;
Minami.y = 20;
this.addChild(Minami);

// 绫波丽
var Ayanami: egret.Bitmap = new egret.Bitmap(RES.getRes("figure_05"));
Ayanami.x = 280;
Ayanami.y = 20;
this.addChild(Ayanami);

// 小南
var Konan: egret.Bitmap = new egret.Bitmap(RES.getRes("figure_06"));
Konan.x = 360;
Konan.y = 20;
this.addChild(Konan);

显示所需的图片,在Egret对应的类就是 Bitmap。使用 Bitmap 创建一个图片时,在其构造函数中传入RES载入的资源,这里取得的是一个图片的资源,图片资源通过 getRes 获得的将是一个 Texture 对象。

egret launcher

当然还需要安装一个egret launcher来管理引擎、工具和项目打包,小游戏就需要打包之后才能在微信开发者工具里使用

图片 4

图片 5

 

显示深度控制

开始egret开发

你可以快速浏览一遍官方教程,以便更好对下文有所理解, 。  文章不是教程所以会省略掉那些白鹭官网里的教程。  现在我们使用egret launcher创建一个初始化项目,初始化后的文件结构如下图,我展开了resource和src文件夹,因为我们需要操作的主要是这两个文件夹,resource文件夹主要是存放静态资源,我们的代码都在src里,白鹭使用的是typescript。

图片 6

在wing工具里,我们可以马上开启调试,就可以在浏览器或者它自带的容器里预览效果。  main.ts是启动文件,main中首先使用await对resource中定义好的图片资源进行了预加载,所以预览开始后会出现loading效果,loading的绘制是写在src中LoadingUI.ts,图片加载完成以后,main里直接创建了下图2的页面,并且添加了一个按钮,点击后会出现一个弹窗。  效果如下图。

图片 7   图片 8 图片 9

至此,初始化demo已经告诉了我们如何绘制图像和绑定事件了,如下图,我只截取了click按钮的代码,图像绘制首先需要创建一个相应的egret或者eui对象,比如eui.Button、egret.TextField、egret.Bitmap等等,然后给对象设置相应属性,比如label、x y坐标,width, height等。  再使用main的addChild载入到画布中(下面的this就是main对象,main继承于eui.UILayer)。  demo中的代码在载入loading的时候,使用了this.stage.addChild,直接addChild或者使用stage.addChild都可以载入到画布中。  白鹭封装的addEventListener方法和原生js的监听方法是一样的使用方法。

图片 10

demo的代码说到这里总结一下,我们在main入口对象中可以使用addChild载入一个视图对象到画布中,比如文本,按钮等。  我们也可以在main里addChild一个视图容器A,视图容器A也可以添加文本按钮等,那我们在视图容器A中再次addChild视图容器B,那么这样就形成了层级嵌套main->A->B,如果想象成dom元素就是div.main->div.A->div.B的关系,我们用代码来对比一下:

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); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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>

1
2
3
4
5
6
7
8
<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默认根据先后顺序确定上下层关系,先载入的在下层。  首先最下层创建了一个背景层,接着是ScrollView和baseContent,页面容器会载入到他们之中,如果页面需要滚动会把页面视图对象载入到SV中,不需要滚动会载入到baseContent中,Layer和loading在更上层的位置。

图片 11

基层容器准备好以后,我们可以创建一个首页页面。  我会创建3个文件:base.ts,Index_ui.ts,Index.ts。  Index继承Index_ui,Index_ui继承base。  所有的_ui都会继承base,base会定义通用方法和属性。  因为一个页面到最后可能代码量会比较大,甚至比较乱,所以才把一个页面拆分成page和page_ui,_ui里写视图相关代码,page里调用_ui的方法、处理请求和编写逻辑,达到视图和逻辑分离的效果。  当首页写好以后,需要创建一个简易路由,用路由提供的方法把Index添加到SV容器中。  我把路由直接写到了main中,changePage就是页面切换的方法,代码大致如下:

图片 12

通过remove和add视图容器达到了切换页面的效果。  下面说说编写_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);//根据内容计算处理居中 } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 的异步形式。

图片 13

和src同级的还有一个texture文件夹,里面是TextureMeger使用精灵图的相关文件,放在仓库里是方便后期管理。

图片 14

简易的初始化demo,我已经更新到github上。  egret-resource是源码,egret-resource_wxgame是白鹭打包后的文件夹,它在开发者工具里运行。  egret-resource_wxgame应该在ignore里忽略,这里没有忽略是方便下载源码的朋友直接在开发者工具里运行demo。  当前程序使用白鹭引擎版本5.2.5。

图片 15

demo里随便写了几个页面,看下效果:

图片 16

获取显示深度

this.getChildIndex();

具体代码:

console.log(
    "display indexes:",
    this.getChildIndex(bg),
    this.getChildIndex(Pohwaran),
    this.getChildIndex(Garen),
    this.getChildIndex(Graves),
    this.getChildIndex(Minami),
    this.getChildIndex(Ayanami),
    this.getChildIndex(Konan)
);

// display indexes: 0 1 2 3 4 5 6

还有踩过很多坑,下面记录一下:

  • 在公众号后台把设置里的服务类设置成游戏类,输入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
  • 所有图片用工具压缩,会减少上传代码的大小和提升资源加载速度

 

修改显示深度

this.setChildIndex( <x>, <深度数值(最大值是显示列表长度-1)> );

具体代码:

this.setChildIndex(Graves,this.getChildIndex(Garen));

// display indexes: 0 1 3 2 4 5 6

显示深度的规则:

  1. 某一个显示深度只能对应一个显示对象,一个显示对象也只能有一个显示深度。

  2. 显示深度总是从零开始连续的,当某个深度位置的显示对象被设置为其他深度时,原来的深度会自动被紧邻的比其深度值大1位置的显示对象占据,后续深度位置的显示对象会依次往前排。

  3. 某一容器内的显示列表的深度最大值是显示列表长度-1。

当这一切都准备好以后,剩下的就是体力活啦,当然还有游戏最重要的核心玩法实现、动画和交互效果,这些可能是一个游戏实现难度最大的部分。仓库地址: 。

1 赞 收藏 评论

图片 17

交换显示深度

this.swapChildren( <x>, <y> );

具体代码:

this.swapChildren(Ayanami,Konan);

// display indexes: 0 1 3 2 4 6 5

不可逾越的显示深度最大值

this.setChildIndex( captain, <比显示列表长度大就可以> );

具体看代码:

this.setChildIndex(Graves,10);

// display indexes: 0 1 2 6 3 5 4

会发现深度并没有变成10,而是自动取允许的最大值6。

这是引擎自动处理的,也算是一种容错功能吧。

Tween动画效果

所谓Tween动画,就是设计某种属性(比如位置、透明度和缩放)的两个不同状态,然后在给定的时间内从一个状态平滑过渡到另外一个状态。

认识锚点

锚点用另一个易于理解的词来说,就是定位点。因此锚点是只存在于显示对象的概念。并且锚点是对 显示对象自身 设置的。

以显示对象本身的左上角作为原点的,取值就是具体的像素值。使用显示对象属性 anchorOffsetXanchorOffsetY 来设置坐标值锚点。

Konan.anchorOffsetX = 30;
Konan.anchorOffsetY = 40;

设置锚点后,我们还需要根据锚点的偏移修改坐标值,以使绿巨人还保持原先的显示位置:

Konan.x += 30;
Konan.y += 40;

设计并实现一组Tween动画

类内部建立记录次数变量

private times:number;

点击次数控制的代码:

this.times = -1;
    var self = this;
    this.stage.addEventListener(egret.TouchEvent.TOUCH_TAP,function() {
        switch(++self.times % 5) {
            case 0:
                egret.Tween.get(Pohwaran).to({ x: Graves.x },300,egret.Ease.circIn);
                egret.Tween.get(Graves).to({ x: Pohwaran.x },300,egret.Ease.circIn);
                break;
            case 1:
                egret.Tween.get(Garen).to({ alpha: .3 },300,egret.Ease.circIn).to({ alpha: 1 },300,egret.Ease.circIn);
                break;
            case 2:
                egret.Tween.get(Minami).to({ scaleX: .4,scaleY: .4 },500,egret.Ease.circIn).to({ scaleX: 1,scaleY: 1 },500,egret.Ease.circIn);
                break;
            case 3:
                egret.Tween.get(Ayanami).to({ y: Ayanami.y + 40 },500,egret.Ease.circIn).to({ y: Ayanami.y},500,egret.Ease.circIn);
                break;
            case 4:
                egret.Tween.get(Konan).to({ x: Konan.x + 40 },500,egret.Ease.circIn).to({ x: Konan.x },500,egret.Ease.circIn);
                break;
        }
    },this);
  • Tween.get 传入需要对其进行动画控制的对象,并返回一个 Tween 对象。

  • to 设置 Tween 对象的动画。to方法包含三个参数:

    • 第一个参数是动画目标属性组,这个参数可以对目标对象本身的各项属性进行设定,就是动画结束时的状态,可以设定一个或多个属性。

      • x/y 位置
      • alpha 透明度
      • scaleX/scaleY 缩放因数
    • 第二个参数是动画时间,以毫秒计。

    • 第三个参数是补间方程,即对动画区间内每个时间点的属性值设定分布。在 egret.Ease 已经提供了丰富的补间方程,可以根据自己的喜好选择。

加入声音

var b_sound: egret.Sound = RES.getRes("bgMusic");

var b_channel: egret.SoundChannel = b_sound.play(0,1);

成了一个 sound 对象并调用 soundplay 方法,其中的第一个参数 0 表示播放的开始时间,第二个参数表示播放次数,如果将第二个参数设置为负数将循环播放。

play 方法返回了一个 SoundChannel 对象。通过操作 channnel 对象可以控制声音的音量大小停止播放等。

常规网络通讯

在游戏开发项目中,数据的通讯无疑是不可或缺的因素。看看网络通讯的基本用法。

URLRequest

URLRequest 类封装了进行HTTP请求所需要的所有信息。 常用的HTTP请求有 GET/POST 两种类型。当进行HTTP请求时,可以直接在 URLRequest 实例上设置请求类型和实际数据。

HTTP请求首先需要URL,我们准备了一个专用于测试的URL,其返回当前浏览器的代理信息: http://httpbin.org/user-agent

使用URLRequest类,就要创建其实例,通常在构造函数中传入URL即可:

var urlreq:egret.URLRequest = new egret.URLRequest( "http://httpbin.org/user-agent" );

URLLoader

URLRequest 只是一个信息集合,实际通讯需要使用 URLLoaderURLLoader 必须使用一个 URLRequest 实例来发挥作用,并且为了得到返回结果,需要加一个事件监听,代码如下:

var urlloader:egret.URLLoader = new egret.URLLoader();
urlloader.addEventListener( egret.Event.COMPLETE, function( evt:egret.Event ):void{
    console.log(evt.target.data);
}, this );
urlloader.load( urlreq );

使用WebSocket通讯

众所周知,WebSocket为Web应用提供了更高效的通讯方式。 本节介绍WebSocket的基本用法。

确保项目支持WebSocket
从Egret1.5.0开始,以官方扩展模块的形式支持WebSocket。在现有的Egret项目中,修改egretProperties.json中的”modules”,添加”socket”模块:

{
    "name": "socket"
}

注意 添加模块的时候要注意保证 json 的语法正确。

在项目所在目录内执行一次引擎编译:

egret build -e

本步骤已经完成,现在项目中既可以使用WebSocket相关的API了。

WebSocket客户端用法

所有的通讯都是基于一个WebSocket实例,首先创建WebSocket对象。
首先看基本代码。

private webSocket:egret.WebSocket;
    private createGameScene():void { this.webSocket = new egret.WebSocket();
    this.webSocket.addEventListener(egret.ProgressEvent.SOCKET_DATA, this.onReceiveMessage, this);
    this.webSocket.addEventListener(egret.Event.CONNECT, this.onSocketOpen, this);
    this.webSocket.connect("echo.websocket.org", 80);
}

WebSocket对象主要有两个事件,一个是连接服务器成功,另一个是收到服务器数据。在正常的网络交互中,这两个事件都是要必须侦听的。

加入侦听事件后,即可连接服务器。注意像所有的通讯协议一样,服务器需要支持WebSocket协议,为便于测试,WebSocket官方提供了一个专用于测试的服务器 echo.websocket.org ,连接其80端口即可测试。

在连接成功后,即可发送消息,消息的具体格式都是根据情况自己定义的,通常是json格式,便于解析。当然可以自定义其他的字符串格式:

private onSocketOpen():void {
    var cmd = "Hello Egret WebSocket";
    console.log("The connection is successful, send data: " + cmd);
    this.webSocket.writeUTF(cmd);
}

服务器根据约定的格式给客户端发送消息,则会触发 SOCKET_DATA 事件,在其事件处理函数 onReceiveMessage 中即可读取消息,读取到字符串后,即可根据约定的格式解析:

private onReceiveMessage(e:egret.Event):void {
    var msg = this.webSocket.readUTF();
    console.log("Receive data:" + msg);
}

编译运行,没有错误的话,控制台将会输出如下log信息:

The connection is successful, send data: Hello Egret WebSocket
Receive data: Hello Egret WebSocket

待续...

本文由门户名站发布,转载请注明来源:微信小游戏和白鹭引擎开发实践