Egret开发《消灭方块》后记(二)对象池的应用

对于性能的追求是永无止境的,此文只做抛砖引玉,欢迎讨论。

用好对象池绝对能提高很多项目的执行效率,下面结合我在《消灭方块》的开发介绍一下对象池的应用。

一、什么是对象池?

频繁地创建和销毁对象会消耗很多性能,有需要频繁创建对象的时候使用对象池绝对是不二的选择。《消灭方块》中的方块是操作最频繁的一个对象,一边销毁一边创建,不加优化的话会严重拖累性能。

既然对象池这么好,到底什么才是对象池呢?听起来很高深,简单来讲对象池就是个数组,把不用的对象放进去,因为数组还保存了对象的引用,所以对象不会被回收,等需要用的时候再从数组中取出来,把这个数组当作一个”缓存池“。简单的例子:

回收不用的对象:

public recycleObject(noUse:any):void
{
    this._pool.push(noUse);
}

需要用这个对象的时候,先检索对象池有没有缓存对象,有的话取出来以后初始化属性,对象池是空的话再创建新对象:

public getObject():any
{
    if(this._pool.length)
        return this._pool.shift();
    else
        return new egret.Bitmap(); //用bitmap作示例
}
这就是一个简单的对象池。

二、Egret中的对象池

在Egret的egret-game-library下面有个ObjectPool.ts,这边是Egret官方的“对象池”,关键代码如下:

public createObject(classFactory:any):GameObject {
    var result;
    var key = classFactory.key;
    var arr = this._pool[key];
    //先在缓存中检索是否有缓存的目标对象
    if (arr != null && arr.length) {
        result = arr.shift();
    }
<span style="font-family: Arial, Helvetica, sans-serif;">    //缓存中没有对象可用时,根据类型创建新的对象作为返回</span>
    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);
    }
}

Egret的ObjectPool更进一步,用的数组+字典的组合来缓存对象,这样的好处就是可以缓存不止一种对象。如果你的项目比较大,有很多地方需要用对象池的话,强烈建议使用Egret的ObjectPool。

三、《消灭方块》中的对象池

如果项目本身比较小,只有一两处需要用对象池的话,建议自己根据项目实际需要写对象池比较好。一来用起来随意,不用刻意继承对象实现接口,二来可以做一些功能上的拓展,会“更好用”。

《消灭方块》中一空有两处使用了对象池,一处是方块对象池,缓存和创建方块用的,代码很简单就不再贴了。另一处是拼接数字的对象池,就是把0-9个数字的图片素材根据实际需求拼接成需要的数字,使用的频率比较高,在多处都有调用,于是写了个通用的管理类处理这项需求。

效果图:

             

实现原理示意图:


不得不说,当DisplayObjectContainer不再像AS3一样是抽象类的时候用起来有多方便。原理就是上图,用多个单数字的Bitmap拼接成目标数字,存放到一个DisplayObjectContainer中返回给需要用的地方。那么这一个数字对象确切来说包含了两种对象(Bitmap和DisplayObjectContainer),所以回收缓存这个对象就要分两步:首先分解DisplayObjectContainer内容,清空并缓存里面的BItmap,最后缓存外部DisplayObjectContainer,所以,实现这个功能就需要两个缓存池搭配使用。

原理讲完上代码:

class ImgNumManager
{
    private _imgPool:egret.Bitmap[];
    private _containerPool:egret.DisplayObjectContainer[];
    private static _instance:ImgNumManager;
    public constructor()
    {
        this._imgPool = [];
        this._containerPool = [];
    }

    public static i():ImgNumManager
    {
        if(!this._instance)
            this._instance = new ImgNumManager();
        return this._instance;
    }
    //根据需要的数字和类型返回一个<span style="font-family: Arial, Helvetica, sans-serif;">DisplayObjectContainer</span>
    public createNumPic(num:number, type:string):egret.DisplayObjectContainer
    {
        var container:egret.DisplayObjectContainer = this.getContainer();
        var numStr:string = num.toString();
        var index:number = 0;
        var tempBm:egret.Bitmap;
        for(index; index < numStr.length; index ++)
        {
            tempBm = this.getSingleNumPic(parseInt(numStr.charAt(index)), type);
            container.addChild(tempBm);
        }
        this.repositionNumPic(container);
        return container;
    }
    //回收带数字的DisplayObjectContainer
    public desstroyNumPic(picContainer:egret.DisplayObjectContainer):void
    {
        this.clearContainer(picContainer);
        if(picContainer.parent)
            picContainer.parent.removeChild(picContainer);
        this._containerPool.push(picContainer);
    }
<pre name="code" class="java">    //改变带数字的DisplayObjectContainer数字值
public changeNum(picContainer:egret.DisplayObjectContainer, num:number, type:string):void { var numStr:string = num.toString(); var tempBm:egret.Bitmap;


        <span style="font-family: Arial, Helvetica, sans-serif;">//如果当前数字个数多于目标个数则把多余的回收</span>
        if(picContainer.numChildren > numStr.length)
        {
            while(picContainer.numChildren > numStr.length)
            {
                this.recycleBM(<egret.Bitmap>picContainer.getChildAt(picContainer.numChildren - 1))
            }
        }
        var index:number = 0;
        for(index; index < numStr.length; index ++)
        {
            //如果当前的Bitmap数量不够则获取新的Bitmap补齐
            if(index >= picContainer.numChildren)
                picContainer.addChild(this.getBitmap());
            (<egret.Bitmap>picContainer.getChildAt(index)).texture = RES.getRes("imgs."+type+numStr.charAt(index));
        }
        this.repositionNumPic(picContainer);
    }

    //每个数字宽度不一样,所以重新排列
    private repositionNumPic(container:egret.DisplayObjectContainer):void
    {
        var index:number = 0;
        var lastX:number = 0;
        var temp:egret.DisplayObject;
        for(index; index < container.numChildren; index++)
        {
            temp = container.getChildAt(index);
            temp.x = lastX;
            lastX = temp.x + temp.width;
        }
    }
    //清理容器
    private clearContainer(picContainer:egret.DisplayObjectContainer):void
    {
        while(picContainer.numChildren)
        {
            this.recycleBM(<egret.Bitmap>picContainer.removeChildAt(0));
        }
    }
    //回收Bitmap
    private recycleBM(bm:egret.Bitmap):void
    {
        if(bm && bm.parent)
        {
            bm.parent.removeChild(bm);
            bm.texture = null;
            this._imgPool.push(bm);
        }
    }

    private getContainer():egret.DisplayObjectContainer
    {
        if(this._containerPool.length)
            return this._containerPool.shift();
        return new egret.DisplayObjectContainer();
    }
    //获得单个数字Bitmap
    private getSingleNumPic(num:number, type:string):egret.Bitmap
    {
        var bm:egret.Bitmap = this.getBitmap();
        bm.texture = RES.getRes("imgs." + type + num);
        return bm;
    }

    private getBitmap():egret.Bitmap
    {
        if(this._imgPool.length)
            return this._imgPool.shift();
        return new egret.Bitmap();
    }
}
这个类使用了单例模式,全局统一用这一个单例管理。提供了三个公共方法分别是创建、回收和改变值,已写好注释,如有不足或者错误欢迎跟帖讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值