对于性能的追求是永无止境的,此文只做抛砖引玉,欢迎讨论。
用好对象池绝对能提高很多项目的执行效率,下面结合我在《消灭方块》的开发介绍一下对象池的应用。
一、什么是对象池?
频繁地创建和销毁对象会消耗很多性能,有需要频繁创建对象的时候使用对象池绝对是不二的选择。《消灭方块》中的方块是操作最频繁的一个对象,一边销毁一边创建,不加优化的话会严重拖累性能。
既然对象池这么好,到底什么才是对象池呢?听起来很高深,简单来讲对象池就是个数组,把不用的对象放进去,因为数组还保存了对象的引用,所以对象不会被回收,等需要用的时候再从数组中取出来,把这个数组当作一个”缓存池“。简单的例子:
回收不用的对象:
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();
}
}
这个类使用了单例模式,全局统一用这一个单例管理。提供了三个公共方法分别是创建、回收和改变值,已写好注释,如有不足或者错误欢迎跟帖讨论。