Egret打包

HTML5

egret publish

iOS

egret create_app PKingIOS -f PKingDuckling -t ./egret-ios-support-4.0.3

Android

~$ egret create_app PKingAndroid -f PKingDuckling -t ./egret-android-support-4.0.3/egret-android-support-4.0.3/
您正在使用的引擎版本为 4.0.2
> copy from project template …
> replace all configure elements …
> rename project name …
native拷贝共计耗时:0.33秒
native拷贝共计耗时:0.149秒
创建完毕,共计耗时:0.996秒
~$

注意事项

1. 修改横屏
2. 去掉egret.assert以及window和document相关函数接口,否则会报错

Egret – 对象池例子

对象池主要好处是让内存管理工作更容易。 如果内存利用率无限制地增长,对象池可预防该问题。 此技术通常可提高性能和减少内存的使用。

class ObjectPool {


    constructor() {
        egret.Ticker.getInstance().register(this.onEnterFrame, this);
    }

    private onEnterFrame(advancedTime:number):void {
        var list = this._list.concat();
        for (var i = 0 , length = list.length; i < length; i++) {
            var obj:GameObject = list[i];
            obj.onEnterFrame(advancedTime);
        }
    }

    private _pool = {};

    private _list:Array = [];

    public createObject(classFactory:any):GameObject {
        var result;
        var key = classFactory.key;
        var arr = this._pool[key];
        if (arr != null && arr.length) {
            result = arr.shift();
        }
        else {
            result = new classFactory();
            result.key = key;
        }
        result.onCreate();
        this._list.push(result);
        return result;
    }

    public destroyObject(obj:GameObject) {
        var key = obj.key;
        if (this._pool[key] == null) {
            this._pool[key] = [];
        }
        this._pool[key].push(obj);
        obj.onDestroy();
        var index = this._list.indexOf(obj);
        if (index != -1) {
            this._list.splice(index, 1);
        }
    }

    private static instance:ObjectPool;

    public static getInstance():ObjectPool {
        if (ObjectPool.instance == null) {
            ObjectPool.instance = new ObjectPool();
        }
        return ObjectPool.instance;
    }
}

Egret – 资源管理简述

Egret入口

data-entry-class=”Main”
入口主函数是Main
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
这个是Egret最开始的回调函数,执行onAddToStage
这里我们主要进行显示游戏初始的Loading界面以及这节重点要说的加载资源
加载资源大致分为以下几个步骤,主要包括

  • 步骤1
  • 添加config以及相关监听事件
    RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE, this.onConfigCompleteHandle, this);
    RES.loadConfig(“resource/default.res.json”, “resource/”);

  • 步骤2
  • 设置加载组信息以及相关回调事件
    RES.addEventListener(RES.ResourceEvent.GROUP_COMPLETE, this.onResourceLoadComplete, this);
    RES.addEventListener(RES.ResourceEvent.GROUP_PROGRESS, this.onResourceLoadProgress, this);
    RES.addEventListener(RES.ResourceEvent.GROUP_LOAD_ERROR, this.onResourceLoadError, this);
    RES.loadGroup(“preload”);

    Egret2D跑酷之龙骨换装研究

    全局换装

    全局换装的原理其实就是完全的替换骨骼对应的纹理集合。

    //
    var dragonBonesFactory:DragonBonesFactory = new DragonBonesFactory();
    dragonBonesFactory.initArmatureFile(RES.getRes("role_pkd_ske_json"),
                                        RES.getRes("role_pkd_tex_png"),
                                        RES.getRes("role_pkd_tex_json"));
    // 生成骨骼
    this._animation = dragonBonesFactory.makeArmature("role_pkd_animation");
    // 
    

    对应的纹理集如下图所示


    生成骨骼动画效果如下

    我们整体改变纹理集合的颜色,来进行测试

    //
    this._animation.getArmature().replaceTexture(RES.getRes("role_pkd_tex_wb_png"));
    

    效果如下图


    我们如果纹理集变成这样是不可以的,需要我们使用局部换肤

    运行效果如下

    局部换装

    我们主要通过dragonBonesFactory.getFactory().replaceSlotDisplay的接口完成。
    具体参数意义如下

    
     * @param dragonBonesName 指定的龙骨数据名称。 - 一般自己没有设置就设置null
     * @param armatureName 指定的骨架名称。 - 替换后的骨架名称
     * @param slotName 指定的插槽名称。 - 替换后骨骼的插槽名称
     * @param displayName 指定的显示对象名称。 - 替换后插槽的Display纹理
     * @param slot 指定的插槽实例。 - 现在正在使用的Slot
     // 举例
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "pking_body", 
        "ss_pking_body", this._animation.getArmature().getSlot("pking_body"));
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "ss_pking_hat", "ss_pking_hat", this._animation.getArmature().getSlot("ss_pking_hat"));
    

    效果如下:


    这个时候我们看到换装效果已经OK,但是在新的换装之后,红色框记的区域,层级发生了改变,这里我们需要手动进行调整下。

    
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "pking_body", 
        "ss_pking_body", this._animation.getArmature().getSlot("pking_body"));
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "ss_pking_hat", 
        "ss_pking_hat", this._animation.getArmature().getSlot("ss_pking_hat"));
    this._animation.name = "DXY";
    this._animation.getArmature().display.scaleX = 2.0
    this._animation.getArmature().display.scaleY = 2.0
    const displayA = this._animation.getArmature().getSlot("pking_neck2").display as egret.DisplayObject;
    const displayB = this._animation.getArmature().getSlot("pking_body").display as egret.DisplayObject;
    const displayC = this._animation.getArmature().getSlot("ss_pking_hat").display as egret.DisplayObject;
    
    displayA.parent.addChildAt(displayA, displayA.parent.getChildIndex(displayB) - 1);
    displayC.parent.addChildAt(displayC, displayC.parent.getChildIndex(displayB) - 1);
    

    这个可以使用的前提就是动画中没有控制过层级的顺序(ZOrder)
    动画设置ZOrder了,可以多设置几个卡槽,然后程序控制显示和隐藏即可,代码如下

    
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "pking_body", 
        "ss_pking_body", this._animation.getArmature().getSlot("pking_body_top"));
    const display:egret.DisplayObject = this._animation.getArmature().getSlot("pking_body").display
    display.visible = false
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "ss_pking_hat", 
        "ss_pking_hat", this._animation.getArmature().getSlot("ss_pking_hat"));
    this._animation.name = "DXY";
    this._animation.getArmature().display.scaleX = 2.0
    this._animation.getArmature().display.scaleY = 2.0
    

    最后效果如下视频


    欢迎各位留言!

    Egret – 2D跑酷元素编辑器更新版



    之前有过一篇写有关金币编辑器的文章,现在为了满足策划的需求,打算更新一篇有关跑酷综合编辑器的内容。
    编辑器说明(宽度 * 高度):
    * 障碍物 (2 * 2)
    * 弹板 (3 * 1)

    元素编辑器


    金币(1*1):
    弹板(3*1):
    地面弹板(3*1):
    普通衣服(2*2):
    宇航服(2*2):
    吸金币(2*2):
    小萝卜(1*1):
    大萝卜(2*2):
    加速(2*2):
    浮空的鸟(2*2):
    飞行的鸟(2*2):
    电池(2*2):
    泥泞道路(3*1):
    大号金币(2*2):
    浮空平台(3*1):
    恐龙衣服(2*2):
    运动衣服(2*2):
    轮胎(2*2):
    轮胎(2*3):
    箱子(2*2):
    箱子(2*4):
    盒子(2*1):
    盒子(2*3):
    球(3*3):
    飞行器(2*2):
    烽火台(9*13):

    导入关卡文件
    粘贴到此处



    Egret – 2D跑酷道具吸金币

    在众多跑酷游戏中,除了跑酷元素以外还包括很多其他的道具效果,其中比较场景的包括:吸金币、加速、无敌等,这一章我们来做一下吸金币的效果。

  • 检索金币与豆小鸭的位置关系
  • 我们通过圆形扫描就可以,这里比较容易


    实现方式如下

    // 
    public AttractCoin() {
        var view:egret.DisplayObjectContainer = (this.app.viewManager.getView(ViewConst.Player));
        var dxy = view.getChildByName("DXY");
        var coins:Array> = new Array>();
        var width = 0;
        var height = 0;
        var num = this.coinContent.numChildren;
        var id_index = 0;
        for (var i = 0; i < num; i++) {
            var c_content:egret.DisplayObjectContainer = this.coinContent.getChildAt(i);
            for (var j = 0; j < c_content.numChildren; j++) {
                var coin = c_content.getChildAt(j);
                width = coin.width
                height = coin.height;
                coins.push([coin.x + c_content.x + this.coinContent.x, 
                    coin.y + c_content.y + this.coinContent.y]);
                /*App.DebugUtils.debugDraw(coin, "coin" + id_index, "0x00ff00", c_content.x + this.coinContent.x, 
                    c_content.y + this.coinContent.y, false);*/
                id_index = id_index + 1
            }
        }
        this._ctr.coinModel.checkCoinsInObject(coins, [dxy.x, dxy.y], 500, dxy.width, dxy.height);
    }
    // 
    public checkCoinsInObject(coins:Array>, player_position:Array, r:number, width?:number, height?:number) {
        // 遍历所有的Coins
        if (width == undefined) {
            width = 0;
        }
        if (height == undefined) {
            height = 0;
        }
        for (var key in coins) {
            var coin:Array = >coins[key];
            var lenQt = App.MathUtils.getDistanceQt(coin[0], coin[1], player_position[0] - width / 2, player_position[1] - height / 2);
            // App.DebugUtils.debugDrawPosition("test01", "0xff0000", coin[0] - width / 2, coin[1] - height / 2, width, height);
            if (lenQt <= r * r) {
                console.log("Attract");
            }
        }
    }
    

    这里的遍历方式都是可以进行优化的,比如碰撞检测时候我们也无需遍历所有的,可以进行优化。

  • 金币的移动
  • // 
    public checkCoinsInObject(player, coins:Array, coins_pos:Array>, player_position:Array, r:number, width?:number, height?:number) {
        // 遍历所有的Coins
        if (width == undefined) {
            width = 0;
        }
        if (height == undefined) {
            height = 0;
        }
        // 定义金币移动速度 (单位时间位移即可)
        var speed = [13, 13];
        // 设定最小距离
        var min_distance_sq = 10 * 10;
        var del_coins:Array = Array();
        for (var key in coins_pos) {
            var coin_pos:Array = >coins_pos[key];
            var coin = coins[key];
            var lenQt = App.MathUtils.getDistanceQt(coin_pos[0], coin_pos[1], player_position[0] - width / 2, player_position[1] - height / 2);
            if (lenQt <= r * r) {
                if (lenQt < min_distance_sq) {
                    // 吃掉金币
                    del_coins.push(coin);
                } else {
                    // 移动
                    if (coin_pos[0] < player.x) {
                        coin.x = coin.x + speed[0];
                    } else {
                        coin.x = coin.x - speed[0];
                    }
    
                    if (coin_pos[1] < player.y) {
                        coin.y = coin.y + speed[1];
                    } else {
                        coin.y = coin.y - speed[1];
                    }
                }
            }
        }
        for (var key in del_coins) {
            var del_coin = del_coins[key];
            del_coin.parent.removeChild(del_coin);
        }
    }
    

    大致效果如下:


    我们还是可以对吸金币进行一些效果的优化

    Egret – 2D跑酷金币功能

    我们已经可以开始跑酷了,现在我们需要一个金币层,用来显示跑酷地图上的金币,这里需要注意的是金币的显示需要与地图背景、道路相吻合。金币层如下图所示

    普通加金币

  • 点与矩形碰撞
  • /** 
     *  
     * @param x1 点 
     * @param y1 点 
     * @param x2 矩形view x 
     * @param y2 矩形view y 
     * @param w  矩形view 宽 
     * @param h  矩形view 高 
     * @return 
     */ 
    public isCollisionPoint(x1:number, y1:number, x2:number, y2:number, w:number, h:number): boolean {
        if (x1 >= x2 && x1 <= x2 + w && y1 >= y2 && y1 <= y2 + h) {  
            return true;
        }
        return false;
    }
    
  • 检测两个矩形是否碰撞
  • /** 
     * 检测两个矩形是否碰撞 
     * @return 
     */ 
    public isCollisionWithRect(x1:number, y1:number, w1:number, h1:number,
            x2:number, y2:number, w2:number, h2:number) : boolean{
        if (x1 >= x2 && x1 >= x2 + w2) {  
            return false;  
        } else if (x1 <= x2 && x1 + w1 <= x2) {  
            return false;  
        } else if (y1 >= y2 && y1 >= y2 + h2) {  
            return false;  
        } else if (y1 <= y2 && y1 + h1 <= y2) {  
            return false;  
        }
        return true;
    }
    

    我们需要判断逗小鸭和金币的外边矩形碰撞,我们需要绘制下对象外边框

    /*
     * Drag Object
     */
    public debugDraw(obj:egret.DisplayObject, name, color, offx:number, offy:number, is_center:boolean) {
        if (this.app.root.getChildByName(name) != undefined) {
            this.app.root.removeChild(this.app.root.getChildByName(name));
        }
        var shp:egret.Shape = new egret.Shape();
        shp.name = name;
        shp.graphics.beginFill( color, 1);
        if (is_center) {
            shp.graphics.drawRect( (obj.x + offx) - obj.width / 2, (obj.y + offy) - obj.height / 2, 
                obj.width, obj.height );
        } else {
            shp.graphics.drawRect( (obj.x + offx), (obj.y + offy), 
                obj.width, obj.height );
        }
        
        shp.graphics.endFill();
        shp.alpha = 0.5
        this.app.root.addChildAt(shp, this.app.root.numChildren);
    }
    //////
    public CheckCollision() {
        var view:egret.DisplayObjectContainer = (this.app.viewManager.getView(ViewConst.Player));
        var dxy = view.getChildByName("DXY");
        App.DebugUtils.debugDraw(dxy, "dxy", "0xff0000", 0, 0, true);
        var num = this.coinContent.numChildren;
        var id_index = 0;
        for (var i = 0; i < num; i++) {
            var c_content:egret.DisplayObjectContainer = this.coinContent.getChildAt(i);
            for (var j = 0; j < c_content.numChildren; j++) {
                var coin = c_content.getChildAt(j);
                App.DebugUtils.debugDraw(coin, "coin" + id_index, "0x00ff00", c_content.x + this.coinContent.x, 
                    c_content.y + this.coinContent.y, false);
                id_index++;
            }
        }
    }
    

    运行结果如下图所示


    在跳起来的时候两个矩形发生了碰撞,下一步我们就来处理下

    ...
    var dxy_rect = App.MathUtils.getRect(dxy, 0, 0, true);
    var coin_rect = App.MathUtils.getRect(coin, c_content.x + this.coinContent.x, 
        c_content.y + this.coinContent.y, false);
    if (App.MathUtils.isCollisionWithRect(dxy_rect[0], dxy_rect[1], dxy_rect[2], dxy_rect[3],
    coin_rect[0], coin_rect[1], coin_rect[2], coin_rect[3])) {
        coin.parent.removeChild(coin);
    }
    ...
    


    下一章来完成加吸金币工具的功能

    Egret – 2D跑酷背景循环讨论

    跑酷场景参考如下:
    让我们一步步来实现并对这个进行优化

    跑酷地图分层原理

    首先我们需要确认跑酷游戏元素的组成部分,主要包括:

  • 游戏场景 (Scene)
  • 游戏控制角色 (Role)
  • 游戏道具 (Tool)
  • 道路障碍 (Obstacles)
  • 称为SRTO。

    游戏场景

    游戏场景我们可以理解为跑酷地图。首先我们需要知道跑酷地图分为多少层?每一层都是什么?
    不同的2D跑酷游戏,地图包含的层级也有所不同,一般分为以下几层:
    1 背景底衬
    2 天空层
    3 远景层
    4 近景层
    5 道路层
    6 前景层
    而对于2D没有摄像机的,一般通过控制道路移动来实现,这里为了实现景深感,需要对于不同的层级做不同的速度处理,1层作为底衬不动,2层作为天空层,移动很缓慢处于基本不动的情况,而3层移动缓慢,但是移动可以感受到,4、5、6三层移动速度一致,一般称之为跑酷的移动速度。除了景深效果,还有就是地图的无限循环,这里就涉及到一些方法,我们一起讨论下,如果不加循环效果,就会出现如下效果:


    除去绘制的物理体效果,背景层、天空层还在,但是远景、近景前景基本都已经不复存在。
    我们一层一层的来进行处理,首先来添加近景层。

    一般需要背景循环,我们的步骤如下

  • 初始化
  • public InitNear():void {
    	// 
    	this.nearContent = new egret.DisplayObjectContainer();
    	this.addChildAt(this.nearContent, this.numChildren);
    
    	// 添加草皮
    	var grass_content:egret.DisplayObjectContainer = this.AddGrass();
    	// 添加该草皮的元素
    	this.AddGrassElement(grass_content);
    }
    

  • 检查更新后位置,如果位置超过左边界, 则进行Append
  • public update(_targetVelocity:number[]) {
    	// 近景
    	this.nearContent.x += _targetVelocity[0] * 0.84;
    	this.nearContent.y += _targetVelocity[1] * 0.84;
    	this.farHillContent.x += _targetVelocity[0] * 0.84 * 0.33;
    	this.farHillContent.y += _targetVelocity[1] * 0.84 * 0.33;
    	this.cloudContent.x += _targetVelocity[0] * 0.84 * 0.1;
    	this.cloudContent.y += _targetVelocity[1] * 0.84 * 0.1;
    	this.CheckNear();
    	this.CheckFar();
    	this.CheckCloud();
    }
    public CheckNear() {
        var cNum = this.nearContent.numChildren;
        var all_numbers = 0;
        var over_numbers = 0;
        var remove_objs = [];
        for (var i = 0; i < cNum; i++) {
            var child = this.nearContent.getChildAt(i);
            if (child.name.substr(0, 1) == "G") {
                all_numbers = all_numbers + 1;
                var offx = child.x + this.nearContent.x;
                if (offx < -100) {
                    over_numbers = over_numbers + 1;
                } else if (offx < -1000) {
                    remove_objs.push(child);
                }
            }
        }
    
        for (var i = 0; i < remove_objs.length; i++) {
            var grass:egret.Bitmap = remove_objs[i];
            grass.parent.removeChild(grass);
        }
    
        if (over_numbers == all_numbers) {
            // Create New
            this.AppendNear();
        }
    }
    public AppendNear() {
        // 附加近景
        // 添加草皮
        var grass_content:egret.DisplayObjectContainer = this.AddGrass();
        // 添加该草皮的元素
        this.AddGrassElement(grass_content);
        var offx = this.GetNearRight(3);
        grass_content.x = offx;
    }
    // GetRight
    public GetNearRight(type:number) {
        var ret = 0;
        var cNum = 0;
        if (type == 1) {
            // 云
            cNum = this.cloudContent.numChildren;
            for (var i = 0; i < cNum; i++) {
                var child = this.cloudContent.getChildAt(i);
                if (child.name.substr(0, 1) == "C") {
                    if (child.x + child.width > ret) {
                        ret = child.x + child.width;
                    }
                }
            }
        } else if (type == 2) {
            // 远山
            cNum = this.farHillContent.numChildren;
            for (var i = 0; i < cNum; i++) {
                var child = this.farHillContent.getChildAt(i);
                if (child.name.substr(0, 1) == "H") {
                    if (child.x + child.width > ret) {
                        ret = child.x + child.width;
                    }
                }
            }
        } else if (type == 3) {
            // 草地
            cNum = this.nearContent.numChildren;
            for (var i = 0; i < cNum; i++) {
                var child = this.nearContent.getChildAt(i);
                if (child.name.substr(0, 1) == "G") {
                    if (child.x + child.width > ret) {
                        ret = child.x + child.width;
                    }
                }
            }
        }
        return ret;
    }
    



    从上面视频我们知道,需要修正部分装饰物的位置,我们设置一个偏移数组来完成。
    经过位置和随机优化,我们效果如下

    现在只剩下道路的和场景的匹配,我们调整一下道路即可。下一章我们将开始制作吃金币和吸金币的功能。

    Egret2D跑酷之金币编辑器


    在《天天酷跑》游戏中,我们可以吃各种不同的金币,其中金币的样式非常的多样化,有箭头、爱心、动物等等多种形式,这些金币的摆放,需要我们使用一个编辑器来完成,一般的方式就是我们设计一个(n*m)二维矩阵进行,天天酷跑中金币样式如下:

    二维矩阵如下所示

    其中a(m*n)表示第n行、第m列的元素,此处我们设定一些元素类型,以后也可以根据这个进行扩展。
    N表示空元素
    C表示普通金币
    于是下面这个矩阵
    C N N N C
    N C N C N
    N N C N N
    N C N C N
    C N N N C
    金币构图如下

    除了每个元素的行列坐标,还有就是行间距和列间距也影响显示结果,于是一般的我们认为每个金币显示的内容包括一个行间距、一个列间距、一个二维矩阵以及n(行数)和m(列数)。
    为了使用数据更佳通用,这边将使用json数据类型,json样例表示如下:

    
    {
        "name": "coin1",
        "width": 5,
        "height": 5,
        "content": [
            [
                "C",
                "N",
                "N",
                "N",
                "C"
            ],
            [
                "N",
                "C",
                "N",
                "C",
                "N"
            ],
            [
                "N",
                "N",
                "C",
                "N",
                "N"
            ],
            [
                "N",
                "C",
                "N",
                "C",
                "N"
            ],
            [
                "C",
                "N",
                "N",
                "N",
                "C"
            ]
        ],
        "width_distance": 5,
        "height_distance": 5
    }
    

    coins.json点击下载

    • Egret读取数据生成
    
            // 测试金币 
            var coins = RES.getRes("coins_json");
            var maxW = coins['width'];
            var maxH = coins['height'];
            var disW = coins['width_distance'];
            var disH = coins['height_distance'];
            
            // 得到Coin
            var coin_texture = RES.getRes("cc01_png");
            var coin:egret.Bitmap = new egret.Bitmap(coin_texture);
            var coin_width = coin.width;
            var coin_height = coin.height;
            coin = null;
    
            // 开始位置
            var start_x = 300;
            var start_y = 300;
            for (var i = 0; i < maxW; i++) {
                for (var j = 0; j < maxH; j++) {
                    if (coins.content[i][j] == "C") {
                        var coin_texture = RES.getRes("cc01_png");
                        var coin:egret.Bitmap = new egret.Bitmap(coin_texture);
                        this.app.root.addChildAt(coin, 5);
                        coin.x = start_x;
                        coin.y = start_y;
                        coin.scaleX = 0.33;
                        coin.scaleY = 0.33;
                    }
                    start_y = start_y + coin_height * 0.33 + disH;
                }
                start_y = 300;
                start_x = start_x + coin_width * 0.33 + disW;
            }
    

    运行结果如下图所示:

    我们通过参数可以改变样式,比如修改列间距为-10,显示结果如下图所示

    当然也可以修改行间距为-10如下图所示

    • 金币编辑器

    下面我们就制作一下这个金币编辑器,我们直接使用js配合服务器进行完成。
    行数:
    列数:
    元素行宽:
    元素列宽:


    选中效果如下:

    程序运行结果如图

    Egret2D有关Camera模拟的思考

    上文说到在开发跑酷游戏中遇到了些问题,比如摄像机,我再思考Egret中主要通过渲染Stage,然后实现显示,我们是否完全可以通过控制Stage或者根目录级别的容器的x、y以及ScaleX和ScaleY来进行模拟摄像机的(Camera)情况,因为这里主要是2D开发的游戏,所以我们不考虑Y轴方向旋转。3D的可以直接使用Egret3D直接参考上篇文章。
    下面我们来做一个测试,首先我们准备好Texture和一个简单的对象纹理图。

    然后设置一个CameraController来实现模拟摄像机的效果如下:

    
    class CameraController extends BaseController {
        public constructor() {
            super();
            this.registerFunc(ActionConst.Create, this.onCreate, this);
        }
    
        private onCreate():void {
            
        }
    
        public Left() {
            this.app.root.$setX(this.app.root.$getX() - 0.33 * this.app.timerManager.dt);
        }
    
        public Right() {
            this.app.root.$setX(this.app.root.$getX() + 0.33 * this.app.timerManager.dt);
        }
    
        public Up() {
            this.app.root.$setY(this.app.root.$getY() - 0.33 * this.app.timerManager.dt);
        }
    
        public Down() {
            this.app.root.$setY(this.app.root.$getY() + 0.33 * this.app.timerManager.dt);
        }
    
        public ScaleBig() {
            this.app.root.$setScaleX(this.app.root.$getScaleX() * 0.99);
            this.app.root.$setScaleY(this.app.root.$getScaleY() * 0.99);
        }
    
        public ScaleSmall() {
            this.app.root.$setScaleX(this.app.root.$getScaleX() / 0.99);
            this.app.root.$setScaleY(this.app.root.$getScaleY() / 0.99);
        }
    }
    

    其中app.root表示根节点,然后对象与root显示节点位于不同的层级,然后通过控制root节点位置来模拟移动如下:


    然后其中道具金币均在root层级上,然后为了实现景深,可以指定多层root,每层root移动速率不一样,然后所有障碍及碰撞均位于最外层的root层面上即可。

    Egret – 3D(例子)

    由于要开始制作H5跑酷类的游戏,所以需要不少游戏引擎不可缺少的元素,比如摄像机,所以花点时间来看看Egret3D,Egret官方例子运行如下(当然还有其它的例子,我们一个一个看下)。

  • 基础样例
  • /**
     * 立方体模型
     * 
     */
    class CubeSample {
    
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private init() {
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(50, 100, 10);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial();
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 0;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 颜色材质样例
  • /**
     * 颜色材质
     * 
     */
    class ColorMaterialSample {
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private init() {
    
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(128, 128, 128);
            let mat: egret3d.ColorMaterial = new egret3d.ColorMaterial(0xffff00);
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 1000;
            this.cameraCtl.rotationX = 40;
            this.cameraCtl.rotationY = 40;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 光照样例
  • class DirectLightSample {
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private loader: egret3d.UnitLoader;
        private init() {
    
            this.loader = new egret3d.UnitLoader("resource/texture/earth.png");
            this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onLoader, this);
        }
    
        private light: egret3d.DirectLight;
        private _lightDir: egret3d.Vector3D = new egret3d.Vector3D(0, -1, 0);
        private _rotationX: number = 0.01;
        private onLoader(e: egret3d.LoaderEvent3D) {
            let img: egret3d.ImageTexture = e.target.data;
    
            let geom: egret3d.SphereGeometry = new egret3d.SphereGeometry(200, 30, 30);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let earth: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(earth);
    
            this.light = new egret3d.DirectLight(this._lightDir);
            this.light.diffuse = 0xffffff;
            this.light.intensity = 0.1;
            this.light.ambient = 0xffffffff;
    
            let group: egret3d.LightGroup = new egret3d.LightGroup();
            group.addLight(this.light);
            earth.material.lightGroup = group;
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 1000;
            this.cameraCtl.rotationX = 0;
        }
    
        private _lightIntensity = 0.01;
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
            if (this.light.intensity >= 0.5) {
                this._lightIntensity = -0.01;
            }
            if (this.light.intensity <= 0.1) {
                this._lightIntensity = 0.01;
            }
            this.light.intensity += this._lightIntensity;
    
            if (this.light.dir.x >= 1) {
                this._rotationX = -0.01;
            }
            if (this.light.dir.x <= -1) {
                this._rotationX = 0.01;
            }
            this.light.dir.x += this._rotationX;
        }
    }
    
  • 高度图模型样例
  • /**
     * 高度图模型
     * 
     */
    class ElevationSample {
    
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private loader: egret3d.UnitLoader;
        private init() {
    
            this.loader = new egret3d.UnitLoader("resource/elevation/timg.jpg");
            this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onLoader, this);
        }
    
        private onLoader(e: egret3d.LoaderEvent3D) {
            let img: egret3d.ImageTexture = e.target.data;
    
            let geom: egret3d.ElevationGeometry = new egret3d.ElevationGeometry(img, 512, 100, 512);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 40;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 天空盒样例
  • /**
     * 天空盒
     * close
     */
    class SkyboxSample {
        public constructor() {
            StageMgr.Instance().init();
    
            this.init();
        }
    
        private queueLoader: egret3d.QueueLoader;
    
        private init() {
            this.queueLoader = new egret3d.QueueLoader();
    
            this.queueLoader.load("resource/skybox/cloudy_noon_BK.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_DN.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_FR.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_LF.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_RT.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_UP.png");
    
            this.queueLoader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onQueueLoader, this);
    
            //this.loader = new egret3d.UnitLoader("resource/skybox/cloudy_noon_BK.png");
            //this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onQueueLoader, this);
        }
    
        private loader: egret3d.UnitLoader;
    
        private onQueueLoader2(e: egret3d.LoaderEvent3D) {
            console.log("ede", this.queueLoader.getAsset("resource/skybox/cloudy_noon_UP.png"));
        }
    
        private onQueueLoader(e: egret3d.LoaderEvent3D) {
    
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_FR.png"));
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_BK.png"));
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_LF.png"));
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_RT.png"));
    
            let cubeTexture: egret3d.CubeTexture = egret3d.CubeTexture.createCubeTextureByImageTexture(
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_FR.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_BK.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_LF.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_RT.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_UP.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_DN.png")
            );
    
            let mat: egret3d.CubeTextureMaterial = new egret3d.CubeTextureMaterial(cubeTexture);
            let cube: egret3d.CubeGeometry = new egret3d.CubeGeometry(50, 50, 50);//10000, 10000, 10000);
            //cube.buildGeomtry(false);
            let sky: egret3d.Sky = new egret3d.Sky(cube, mat, StageMgr.Instance().view3d.camera3D);
            StageMgr.Instance().view3d.addChild3D(sky);
    
            console.log("load")
            let img: egret3d.ImageTexture = this.queueLoader.getAsset("resource/skybox/cloudy_noon_UP.png");
            let mat2: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let a: egret3d.Mesh = new egret3d.Mesh(cube, mat2);
            StageMgr.Instance().view3d.addChild3D(a);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 0;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 纹理样例
  • /**
     * 纹理材质
     * 
     */
    class TextureMaterialSample {
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private loader: egret3d.UnitLoader;
        private init() {
    
            this.loader = new egret3d.UnitLoader("resource/texture/Icon.png");
            this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onLoader, this);
        }
    
        private onLoader(e: egret3d.LoaderEvent3D) {
            let img: egret3d.ImageTexture = e.target.data;
    
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(128, 128, 128);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 1000;
            this.cameraCtl.rotationX = 40;
            this.cameraCtl.rotationY = 40;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 线框渲染样例
  • /**
     * 线框渲染
     * 
     */
    class WireframeSample {
        public constructor() {
            StageMgr.Instance().init();
    
            this.init();
        }
    
        private init() {
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(100, 100, 100);
            let wf: egret3d.Wireframe = new egret3d.Wireframe();
            wf.fromGeometry(geom);
            wf.fromVertexs(egret3d.VertexFormat.VF_POSITION);
            StageMgr.Instance().view3d.addChild3D(wf);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 0;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    

    Egret – ts(Egret) 与 js 的调用

    ts 是 js 的超集,因此只要是 js 与 js 可以互相调用的,ts 均可以调用,只不过需要增加声明来解决编译时报错。
    ts 最终生成的文件为 js,因此 js 调用 ts 其实就是 js 调用 js。
    而在互相调用之前,需要进行声明!即生成d.ts文件。
    下面我们重点来说下如何生成d.ts文件

    
    (function(p2){
    
        // exports
        p2.KinematicCharacterController = KinematicCharacterController;
    
        // imports
        var vec2 = p2.vec2;
        var Ray = p2.Ray;
        var RaycastResult = p2.RaycastResult;
        var AABB = p2.AABB;
        var EventEmitter = p2.EventEmitter;
    
        // constants
        var ZERO = vec2.create();
        var UNIT_Y = vec2.fromValues(0,1);
    
        // math helpers
        function sign(x){
            return x >= 0 ? 1 : -1;
        }
        function lerp(factor, start, end){
            return start + (end - start) * factor;
        }
        function clamp(value, min, max){
            return Math.min(max, Math.max(min, value));
        }
        function angle(a, b){
            return Math.acos(vec2.dot(a, b));
        }
        function expandAABB(aabb, amount){
            var halfAmount = amount * 0.5;
            aabb.lowerBound[0] -= halfAmount;
            aabb.lowerBound[1] -= halfAmount;
            aabb.upperBound[0] += halfAmount;
            aabb.upperBound[1] += halfAmount;
        }
        ...
        /**
         * Should be executed after each physics tick, using the physics deltaTime.
         * @param {number} deltaTime
         */
        KinematicCharacterController.prototype.update = (function(){
            var scaledVelocity = vec2.create();
            return function (deltaTime) {
                var input = this.input;
                var velocity = this.velocity;
                var controller = this;
    
                var wallDirX = (controller.collisions.left) ? -1 : 1;
                var targetVelocityX = input[0] * this.moveSpeed;
    
                var smoothing = this.velocityXSmoothing;
                smoothing *= controller.collisions.below ? this.accelerationTimeGrounded : this.accelerationTimeAirborne;
                var factor = 1 - Math.pow(smoothing, deltaTime);
                velocity[0] = lerp(factor, velocity[0], targetVelocityX);
                if(Math.abs(velocity[0]) < this.velocityXMin){
                    velocity[0] = 0;
                }
    
                var wallSliding = false;
                if ((controller.collisions.left || controller.collisions.right) && !controller.collisions.below && velocity[1] < 0) {
                    wallSliding = true;
    
                    if (velocity[1] < -this.wallSlideSpeedMax) {
                        velocity[1] = -this.wallSlideSpeedMax;
                    }
    
                    if (this.timeToWallUnstick > 0) {
                        velocity[0] = 0;
    
                        if (input[0] !== wallDirX && input[0] !== 0) {
                            this.timeToWallUnstick -= deltaTime;
                        } else {
                            this.timeToWallUnstick = this.wallStickTime;
                        }
                    } else {
                        this.timeToWallUnstick = this.wallStickTime;
                    }
                }
    
                if (this._requestJump) {
                    this._requestJump = false;
    
                    if (wallSliding) {
                        if (wallDirX === input[0]) {
                            velocity[0] = -wallDirX * this.wallJumpClimb[0];
                            velocity[1] = this.wallJumpClimb[1];
                        } else if (input[0] === 0) {
                            velocity[0] = -wallDirX * this.wallJumpOff[0];
                            velocity[1] = this.wallJumpOff[1];
                        } else {
                            velocity[0] = -wallDirX * this.wallLeap[0];
                            velocity[1] = this.wallLeap[1];
                        }
                    }
    
                    if (controller.collisions.below) {
                        velocity[1] = this.maxJumpVelocity;
                    }
                }
    
                if (this._requestUnJump) {
                    this._requestUnJump = false;
                    if (velocity[1] > this.minJumpVelocity) {
                        velocity[1] = this.minJumpVelocity;
                    }
                }
    
                velocity[1] += this.gravity * deltaTime;
                vec2.scale(scaledVelocity, velocity, deltaTime);
                controller.move(scaledVelocity, input);
    
                if (controller.collisions.above || controller.collisions.below) {
                    velocity[1] = 0;
                }
            };
        })();
    
    })(this.p2);
    

    我们来写下p2.KinematicCharacterController的声明文件如下

    
    declear module p2 {
        export function sign(x:number);
        export function lerp(factor:number, start:number, end:number);
        export function clamp(value:number, min:number, max:number);
        export function angle(a:number, b:number);
        export function expandAABB(aabb:{}, amount:number);
        //
        export class RaycastController {
            constructor(options: {});
            updateRaycastOrigins();
            calculateRaySpacing();
        }
    
        export class Controller {
            constructor(options: {});
            resetCollisions(velocity:number);
            moveWithZeroInput(velocity:number, standingOnPlatform:boolean);
            move(velocity:number, input:number, standingOnPlatform:boolean);
            emitRayCastEvent():{};
            horizontalCollisions(velocity:number);
            verticalCollisions(velocity:number, input:any);
            climbSlope(velocity:number, slopeAngle:number);
            descendSlope(velocity:number);
            resetFallingThroughPlatform();
        }
    
        export class KinematicCharacterController {
            constructor(options: {});
            setJumpKeyState(isDown:boolean);
            update():any;
        }
    }
    

    点击下载
    欢迎提问

    Egret基础 – TypeScript学习之路

    TypeScript学习之路

    为了方便cocos2d-x的朋友,上篇作为引子,今天这篇会就着Egret所使用的Typscript语言学习一下。主要大致分为基本语法和类型条件与循环接口与函数类、包与命名空间泛型 几个部分。希望可以使得朋友尽快入手。

    基本语法和类型

    
       /**
        * 定义变量的方法有 var 、let、const
        */ 
        var number1:number = 100;
        var number2 = 200;
        let number3:number = 300;
        let number4 = 400;
        const number5 = 500;
    

    变量定义

    这里定义出现了var和let,他们之间有什么区别呢?看下段代码你就会有所了解
    1460604178615
    会提示错误src/Main.ts(194,34): error TS2304: Cannot find name ‘num2’.
    当然你要是强行编译,然后运行,你会发现
    1460604271545
    还是显示出来了,因为这个是js定义变量的原理导致的,会将所有的声明提前,但是我们还是要遵循ts这个规则。
    至于const和let的比较可以参考下文
    1460604389668

    基本类型

    
    var b:boolean = false;
    var n:number = 100;
    var s:string = "Hello Egret";
    var name = "TomYuan";
    var h_str: string = `Hello, my name is ${name}`;
    console.log(h_str);
    var arr1:Array = [100, 200];
    var arr2:number[] = [100, 200];
    var p1:[string, number] = ["TomYuan", 100];
    var p2:[string, string, string] = ["China", "USA", "Russia"];
    enum COLOR {Red, Green, Blue};
    var color = COLOR.Red;
    var any_value:any = 100;
    any_value = false;
    any_value = "AnyValue";
    function test(): void {
        // TODO
    }
    

    一般在学习一门语言的路上,必然会学习条件语句和循环语句,这里我们就一起来学习下相关的内容。

    
    var a = Math.random() * 100;
    var b = Math.random() * 100;
    if (a + b > 150) {
        console.log("运气不错,去买彩票吧!");
    } else if (a + b < 10) {
        console.log("运气爆棚,多买几张彩票!");
    } else {
        console.log("今天适合在家睡觉");
    }
    

    通过if…else if..else我们可以完成很多有用的逻辑。当然typescript也同样是支持switch的,而且类型更多样化

    
    enum PlayerStatus { IDLE,MOVE,ATK,SKILL,DEAD};
    var curr_status = PlayerStatus.MOVE;
    switch(curr_status) {
        case PlayerStatus.IDLE:{
            console.log("玩家处于呼吸状态");
            break;
        }
        case PlayerStatus.MOVE:{
            console.log("玩家处于移动状态");
            break;
        }
        case PlayerStatus.ATK:{
            console.log("玩家正在进行攻击");
            break;
        }
        case PlayerStatus.SKILL:{
            console.log("玩家释放了大招");
            break;
        }
        case PlayerStatus.DEAD: {
            console.log("玩家已经死亡");
            break;
        }
        default: {
            console.log("玩家正在发呆");   
        }
    }
    

    有了switch…case…的机制,我们可以很容易实现一些有限状态机的内容。
    学习完了选择,我们就想到了各式各样的循环,看看都有什么的循环等着我们来征服。

    
    var nums: number[] = [100, 200, 312, 405, 771, 512];
    var sum = 0;
    for (var i = 0; i < nums.length; i++) {
        sum = sum + nums[i];
    }
    console.log(`Total:${sum}`);
    for (var k in nums) {
        console.log("n:" + nums[k]);
    }
    

    接口与函数
    我们这里将函数和接口同意称之为接口,让我们看一个简单的接口

    
    function abs(n:number): number {
        if (n >= 0) {
            return n;
        }
        return -n;
    }
    ...
    console.log(abs(-100));        // OUT: 100
    

    下面我们来看下属性接口

    
    interface CostomValue {
        color_r:number;
        color_g:number;
        color_b:number;
    }
    function covertToRGB(v: CostomValue):number {
        var ret:number = 0;
        ret = (v.color_r << 16) + ret;
        ret = (v.color_g << 8) + ret;
        ret = v.color_b + ret;
        return ret;
    }
    ...
    var color:CostomValue = {color_r: 225, color_g: 255, color_b: 155};
    var ret = covertToRGB(color);
    console.log(ret);
    

    上述属性中我们发现都是必须参数,那么可选参数该怎么做呢?

    
    interface CostomValue {
        color_r:number;
        color_g:number;
        color_b:number;
        color_a?:number;
    }
    

    看看那个小小的?就是了
    除了属性接口还有函数接口

    
    interface avg {
        (num1:number, num2:number, ...number):number;
    }
    var avgFunc:avg;
    avgFunc = function(n1:number, n2:number, n3:number, n4:number) {
        return (n1 + n2 + n3 + n4) / 4;
    }
    ...
    var avg_v = avgFunc(100, 200, 300, 400);
    console.log(avg_v);
    

    除了属性和函数还有数组和类接口,数组我这里就不介绍了,一起看下类接口吧!

    
    interface Run {
        Output();
    }
    class People implements Run {
        constructor() {
        }
        Output() {
            console.log("People Running...");
        }
    }
    class Cat implements Run {
        constructor() {
        }
        Output() {
            console.log("Cat Running...");
        }
    }
    ...
    var p = new People();
    p.Output();
    var cat = new Cat();
    cat.Output();
    

    经常玩C++的你一定立刻就懂了,不解释!

  • 类、包与命名空间
  • 
    class Main extends egret.DisplayObjectContainer {
        /**
         * 加载进度界面
         * Process interface loading
         */
        private loadingView:LoadingUI;
        public constructor() {
            super();
            this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
        }
        private onAddToStage(event:egret.Event) {
            //设置加载进度界面
            //Config to load process interface
            this.loadingView = new LoadingUI();
            this.stage.addChild(this.loadingView);
            //初始化Resource资源加载库
            //initiate Resource loading library
            RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE, this.onConfigComplete, this);
            RES.loadConfig("resource/default.res.json", "resource/");
        }
        ...
    }
    

    官方的代码中这里体现了大部分类的内容,包括继承关系以及封装性
    egret是命名空间。我们在做自己的项目的时候,可以使用一个命名空间来整体规划项目。
    有关包的概念可以参考这里:http://www.typescriptlang.org/docs/handbook/modules.html

    泛型

    
    function identity(arg: T): T {
        return arg;
    }
    

    有了这些准备,我们继续学习之旅吧!

    Egret基础-事件与自定义事件

    游戏中的事件机制

    在游戏开发过程中,我们会不断的使用事件的机制,不管是Touch操作还是网络通信都离不开这些,本篇我们就事件机制做一个整理和学习。

    cocos2d-x中的事件机制

    使用过cocos2d-x的朋友都知道,其事件机制主要由以下几个类控制
    CCEvent CCEventController CCEventCustom CCEventDispatcherCCEventListenerCCEventType
    如果你对cocos2d-x事件了解的非常好,对于Egret中的事件你也会非常容易理解。为了后面的加深理解,先简单的阐述下cocos2d-x中事件的原理。
    cocos2d-x中的Event类型大概分为:

    enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        FOCUS,
        GAME_CONTROLLER,
        CUSTOM
     };
    

    而Egret中的事件类型,看目录egret\events,我们知道Egret默认事件的类型包括
    Focus(焦点)Geolocation(定位)HTTPSIOErrorMotion(手势)Orientation(旋转与方向)ProgressStageOrientationTextTimerTouch
    在cocos2d-x中,不同的事件使用不同的事件监听器的创建接口,当然也存在自定义的事件监听器接口。我们这里均以 Touch 和 Custom 来进行举例说明。
    这里我们以cocos2d-x的EventListenerTouchOneByOne举例

    class CC_DLL EventListenerTouchOneByOne : public EventListener
    {
    public:
        ...
        static EventListenerTouchOneByOne* create();
        ...
    public:
        typedef std::function<bool(Touch*, Event*)> ccTouchBeganCallback;
        typedef std::function<void(Touch*, Event*)> ccTouchCallback;
        ccTouchBeganCallback onTouchBegan;
        ccTouchCallback onTouchMoved;
        ccTouchCallback onTouchEnded;
        ccTouchCallback onTouchCancelled;
        ...
    };
    // CPP
    EventListener::init(Type::TOUCH_ONE_BY_ONE, LISTENER_ID, nullptr)
    

    从上述代码我们知道主要执行了父类的init方法,其实主要就是将事件回调、事件类型、事件ID等信息进行了初始化。而添加事件监听的地方在 EventDispatcher ,对应添加事件监听和删除事件监听大致有这几个方法:

    
    // .h
    void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);
    void addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority);
    EventListenerCustom* addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback);
    void removeEventListener(EventListener* listener);
    void removeEventListenersForType(EventListener::Type listenerType);
    void removeEventListenersForTarget(Node* target, bool recursive = false);
    void removeCustomEventListeners(const std::string& customEventName);
    void removeAllEventListeners();
    

    事件这块都涉及了优先级的概念。同样的在Egret中事件分发接口 IEventDispatcher 中有如下定义:

    
    /**
    * @language zh_CN
    * 使用 EventDispatcher 对象注册事件侦听器对象,以使侦听器能够接收事件通知。可以为特定类型的事件、阶段和优先级在显示列表的所有节
    * 点上注册事件侦听器。成功注册一个事件侦听器后,无法通过额外调用 on() 来更改其优先级。要更改侦听器的优先级,必须
    * 先调用 removeEventListener()。然后,可以使用新的优先级再次注册该侦听器。注册该侦听器后,如果继续调用具有不同 type 或 useCapture
    * 值的 on(),则会创建单独的侦听器注册。<br/>
    * 如果不再需要某个事件侦听器,可调用 EventDispatcher.removeEventListener()
    * 删除它;否则会产生内存问题。由于垃圾回收器不会删除仍包含引用的对象,因此不会从内存中自动删除使用已注册事件侦听器的对象。复制
    * EventDispatcher 实例时并不复制其中附加的事件侦听器。(如果新近创建的节点需要一个事件侦听器,必须在创建该节点后附加该侦听器。)
    * 但是,如果移动 EventDispatcher 实例,则其中附加的事件侦听器也会随之移动。如果在正在处理事件的节点上注册事件侦听器,则不会在当
    * 前阶段触发事件侦听器,但会在事件流的稍后阶段触发,如冒泡阶段。如果从正在处理事件的节点中删除事件侦听器,则该事件侦听器仍由当前操
    * 作触发。删除事件侦听器后,决不会再次调用该事件侦听器(除非再次注册以备将来处理)。
    * @param type 事件的类型。
    * @param listener 处理事件的侦听器函数。此函数必须接受 Event 对象作为其唯一的参数,并且不能返回任何结果,
    * 如下面的示例所示: function(evt:Event):void 函数可以有任何名称。
    * @param thisObject 侦听函数绑定的this对象
    * @param useCapture 确定侦听器是运行于捕获阶段还是运行于冒泡阶段。如果将 useCapture 设置为 true,
    * 则侦听器只在捕获阶段处理事件,而不在冒泡阶段处理事件。如果 useCapture 为 false,则侦听器只在冒泡阶段处理事件。
    * 要在两个阶段都侦听事件,请调用 on() 两次:一次将 useCapture 设置为 true,一次将 useCapture 设置为 false。
    * @param  priority 事件侦听器的优先级。优先级由一个带符号的整数指定。数字越大,优先级越高。优先级为 n 的所有侦听器会在
    * 优先级为 n -1 的侦听器之前得到处理。如果两个或更多个侦听器共享相同的优先级,则按照它们的添加顺序进行处理。默认优先级为 0。
    * @see #once()
    * @see #removeEventListener()
    * @version Egret 2.4
    * @platform Web,Native
    */
    addEventListener(type:string, listener:Function, thisObject:any, useCapture?:boolean, priority?:number):void;
    ...
    once(type:string, listener:Function, thisObject:any, useCapture?:boolean, priority?:number):void;
    removeEventListener(type:string, listener:Function, thisObject:any, useCapture?:boolean):void;
    

    文件中的注释也非常清楚,从这个角度说Egret和cocos2d-x的事件使用方法上非常类似。至于原理,我一会儿会在Egret中讲解,在解释cocos2d-x事件原理之前,我们知道有了addEventListener,还有一个不得不说的就是dispatch。dispatch就是分发的意思,意思我们单单addEventListener之后,事件只是被注册,但是什么时候去调用,还是需要我们使用dispatch来分发消息的。在Egret中也是同样的道理。因为本文库主要阐述的是Egret的方方面面,所以其他引擎我会粗略的发表一下自己的意见,有兴趣的朋友可以私下多多交流。
    addEventListener 所做的事情就是就是将注册的事件加入到_listenerMap中,而 removeEventListener 的时候会将事件从_listenerMap中删除。在dispatchEvent的时候,首先会根据优先级对时间监听进行排序,然后执行代码如下:

    
    auto onEvent = [&event](EventListener* listener) -> bool{
        event->setCurrentTarget(listener->getAssociatedNode());
        listener->_onEvent(event);
        return event->isStopped();
    };
    

    我们整体寻找一下默认的dispatch有哪些?当然自定义的事件需要你自己add之后,进行dispatch喔。

    我们搜索了以后得到了很多结果,为了验证找其中一处:
    当我们注册了Touch的Listener,在底层点击的时候,已经加入了dispatch,所以我们无需自己调用。
    在cocos2d-x这里说了这么多,只有一个意思,因为Egret的事件机制和cocos2d-x基本是一致的。

    Egret中的事件机制浅析

    事件机制原理(官方):http://edn.egret.com/cn/docs/page/112
    我这里简单的做一个说明或者概括:

     所有事件机制的原理大多相同:
     基本都有【事件发送方】、【事件接收方】、【事件】三个部分组成。
     事件是连接 发送方 和 接收方的桥梁。
     事件发送方在发送事件之后可以等待回应或者认为事情已经完成。
     事件接收方接到事件会去根据事件做不同的事情,这个事情我们称之为回调。

    有了上面对cocos2d-x的事件分析,我们了解了一些,下面我们就从代码层面开始研究,最后我们会多说几个例子来整体进行巩固。那么我们就从这里开始。


    由于文件个数并不多,所以我们依次看看这些文件的父子级关系和内容。
    Event.ts

    
    // 文件的开始对这个文件有一个非常好的注释:
    /**
    Event 类作为创建事件实例的基类,当发生事件时,Event 实例将作为参数传递给事件侦听器。Event 类的属性包含有关事件的基本信息,例如事件的类型或者是否可以取消事件的默认行为。对于许多事件(如由 Event 类常量表示的事件),此基本信息就足够了。但其他事件可能需要更详细的信息。
    */
    // 简单点说上面我们提到了事件机制的三个部分,这里就是【事件的基类】
    

    下面我们写个例子,测试怎么创建一个事件。

    
    private EventTest(e: egret.Event) {
        console.log(e.data);
    }
    ...
    var e:egret.Event = egret.Event.create(egret.Event, "Test");
    e.data = "TestData";
    
    var p:egret.DisplayObjectContainer = new egret.DisplayObjectContainer();
    p.addEventListener("Test", this.EventTest, this);
    
    var o:egret.DisplayObjectContainer = new egret.DisplayObjectContainer();
    
    var ret = o.dispatchEvent(e);
    console.log(ret);
    

    好吧,我承认上述的代码不漂亮,而且更糟糕的是没有执行,一会儿我们一起看看这个为什么,从这个角度而言也说明,和我们所了解的cocos还是有些区别的。
    我们在编译后的代码中

    这里得到的list是空。如果我们此时直接让p.dispatchEvent(e)是没有问题的。
    上面也说明,一个新的对象直接去dispatchEvent的时候,他的eventMap是没有的。
    当然我们先按照官方的设定来玩这个概念。之后了解清楚原理,可以自己随意扩展。
    现在我们看看那个地方是最重要的呢?毫无疑问 addEventListener 非常重要。
    事件派发器之Add

    下面我们就从Egret的事件派发器说起。
    EventDispatcher继承于IEventDispatcher
    这个接口中的几个方法,需要一个一个详细的聊聊。

    addEventListener

    参数:
    type:string 事件的类型
    listener:Function 处理事件的侦听器函数。此函数必须接受 Event 对象作为其唯一的参数,并且不能返回任何结果
    thisObject:any 侦听函数绑定的this对象
    useCapture?:boolean 确定侦听器是运行于捕获阶段还是运行于冒泡阶段
    priority?:number 事件侦听器的优先级
    通过分析源码我们知道addEventListener主要是判断事件优先级确定index,然后构建eventBin如下:

    
    var eventBin = {
        type: type, listener: listener, thisObject: thisObject, priority: priority,
        target: this, useCapture: useCapture, dispatchOnce: !!dispatchOnce
    };
    

    然后将事件插入到list中,也就说明此时改对象的eventMap就有了对应的事件了。所以该对象在执行dispatchEvent的时候,取得对应的list,然后通过下面一行命令调用回调:
    eventBin.listener.call(eventBin.thisObject, event);
    说完了这个,再重新写个例子,连带整理下Egret事件使用方法:

    
    // 自定义顾客事件
    class CustomerEvent extends egret.Event {
    
        public static BUY:string = "buy";
        public money:number;
        public constructor(type:string, bubbles:boolean = false, cancelable:boolean = false) {
            super(type, bubbles, cancelable);
        }
    }
    // 商品类
    class Good extends egret.DisplayObject {
    
        private _name:string;
        private _value:number;
        private _jin:number;
    
        constructor(name:string, value:number, jin:number) {
            super();
            this._name = name;
            this._value = value;
            this._jin = jin;
        }
    
        public get name() :string {
            return this._name;
        }
    
        public set name(v:string) {
            this._name = v;
        }
    
        public get value():number {
            return this._value;
        }
    
        public set value(v:number) {
            this._value = v;
        }
    
        public get jin():number {
            return this._jin;
        }
        public set jin(v:number) {
            this._jin = v;
        }
    }
    
    // 商店类
    class Shop extends egret.DisplayObject {
    
        public static totalMoney:number;
        private goods:Array<Good>;
        private config:Array<any> = [
            ["苹果", 5.98, 15],
            ["鸭梨", 4.1, 22],
            ["香蕉", 7.2, 17],
            ["猕猴桃", 14, 5]
        ];
        private static _shopInstance: Shop;
        constructor() {
            super();
        }
    
        public AddMoney(e:CustomerEvent) {
            Shop.totalMoney = Shop.totalMoney + e.money;
            console.log("ShopMoney:$" + Shop.totalMoney);
        }
    
        private init():void {
            this.addEventListener(CustomerEvent.BUY, this.AddMoney, this);
            Shop.totalMoney = 0;
            this.goods = new Array<Good>();
            for (var i = 0; i < this.config.length; i++) {
                let name = this.config[i][0];
                let value = this.config[i][1];
                let jin = this.config[i][2];
                this.goods.push(new Good(name, value, jin));
            }
        }
    
        public static getInstance():Shop {
    
            if (!Shop._shopInstance) {
                Shop._shopInstance = new Shop();
                Shop._shopInstance.init();
            }
            return Shop._shopInstance;
        }
    
        public getGoods():Array<Good> {
            return this.goods;
        }
    
        public getRandomGood() {
            var ret = null;
            var index = Math.floor(Math.random() * this.goods.length);
            if (this.goods[index]) {
                ret = this.goods[index];
            }
            return ret;
        }
    
        public allGoodOver() {
            var ret = false;
            var is_zero = 0;
            for (var i = 0; i < this.goods.length; i++) {
                is_zero = is_zero + this.goods[i].jin;
            }
            if (is_zero == 0) {
                ret = true;
            }
            return ret;
        }
    }
    
    // 顾客类
    var family_name:string[] = ["张", "王", "李", "魏", "吴", "袁", "周", "孙"];
    var post_name:string[] = ["博", "洋", "秋水", "长苏"];
    function genName():string {
        var ret = "";
        var index1 = Math.ceil(Math.random() * family_name.length) - 1;
        var index2 = Math.ceil(Math.random() * post_name.length) - 1;
        ret = family_name[index1] + post_name[index2];
        return ret;
    }
    
    class Customer extends egret.DisplayObject {
    
        private _balance:number;
        private c_name:string;
        constructor() {
            super();
            // 自动名字
            this.c_name = genName();
            console.log("Name:" + this.c_name);
            // 自动余额
            this._balance = Math.floor(Math.random() * 100) + 100;
            console.log("Balance:" + this._balance);
            // 添加事件监听
            this.addEventListener(CustomerEvent.BUY, this.buyCallBack, this);
        }
    
        // 购买
        public buyCallBack(e:CustomerEvent) {
            this._balance = this._balance - e.money;
            console.log(this.c_name + "剩余:$" + this._balance);
        }
    
        public set balance(v:number) {
            this._balance = v;
        }
    
        public get balance():number {
            return this._balance;
        }
    
        public buy(index:number, n:number) {
            // 购买
            var shop = Shop.getInstance();
            var goods:Array<Good> = shop.getGoods();
            var e:CustomerEvent = new CustomerEvent(CustomerEvent.BUY);
            e.money = goods[index].value * n;
    
            // 更新数据
            goods[index].jin = goods[index].jin - n;
            shop.dispatchEvent(e);
            this.dispatchEvent(e);
        }
    }
    
    class GameMechine extends egret.DisplayObject {
        public static startGame(customers:Array<any>) {
    
            var shop = Shop.getInstance();
            var max_circle = 100;
            var start_index = 0;
            while (true) {
                // 检测
                if (shop.allGoodOver()) {
                    console.log("Game Over1");
                    break;
                }
    
                if (start_index > max_circle) {
                    console.log("Game Over2");
                    break;
                }
    
                console.log("Nexting..");
                // 选择钱数最多的
                var p_index:[number, number] = [0, 0];
                for (var i = 0; i < customers.length; i++) {
                    let c = <Customer>customers[i];
                    if (p_index[1] < c.balance) {
                        p_index[0] = i;
                        p_index[1] = c.balance;
                    }
                }
    
                // 购买价格 * 斤数最高的,如果没有次之
                var g_index:[number, number] = [0, 0];
                var goods:Array<Good> = shop.getGoods();
                for (var i = 0; i < goods.length; i++) {
                    var value = goods[i].value * goods[i].jin;
                    if (g_index[1] < value) {
                        g_index[0] = i;
                        g_index[1] = value;
                    }
                }
    
                // 默认1
                (<Customer>customers[p_index[0]]).buy(g_index[0], 1);
            }
        }
    }
    //// 具体执行
    var customers:Array<Customer> = new Array<Customer>();
    for (var i = 0; i < 3; i++) {
        // 生成三名顾客
        let c = new Customer();
        customers.push(c);
    }
    
    console.log("===================Start====================");
    GameMechine.startGame(customers);