[转]优化Flash性能

澳门新葡亰娱乐在线 7

HTML5作为新兴领域越来越热。然而在移动设备硬件性能弱于PC的背景下,对性能的需求显得更为重要,而HTML5性能优化前与优化后有着极大的差别,如何优化才能提高性能,对此熟知的人很少。本文以LayaAir引擎为例,通过代码示例详细阐述如何利用引擎对HTML5作出性能的极致优化。

原文:

主题包括:

翻译:

  • 代码执行基本原理
  • 基准测试
  • 内存优化
  • 图形渲染性能
  • 减少CPU使用量
  • 其他优化策略

 

第1节:代码执行基本原理

LayaAir引擎支持AS3、TypeScript、JavaScript三种语言开发,然而无论是采用哪种开发语言,最终执行的都是JavaScript代码。所有看到的画面都是通过引擎绘制出来的,更新频率取决于开发者指定的FPS,例如指定帧频率为60FPS,则运行时每个帧的执行时间为六十分之一秒,所以帧速越高,视觉上感觉越流畅,60帧是满帧。

由于实际运行环境是在浏览器中,因此性能还取决于JavaScript解释器的效率,指定的FPS帧速在低性能解释器中可能不会达到,所以这部分不是开发者能够决定的,开发者能作的是尽可能通过优化,在低端设备或低性能浏览器中,提升FPS帧速。

LayaAir引擎在每帧都会重绘,在性能优化时,除了关注每帧执行逻辑代码带来的CPU消耗,还需要注意每帧调用绘图指令的数量以及GPU的纹理提交次数。

 

第2节:基准测试

LayaAir引擎内置的性能统计工具可用于基准测试,实时检测当前性能。开发者可以使用laya.utils.Stat类,通过Stat.show()
显示统计面板。具体编写代码如下例所示:

Stat.show(0,0); //AS3的面板调用写法       
Laya.Stat.show(0,0); //TS与JS的面板调用写法

Canvas渲染的统计信息:

澳门新葡亰娱乐在线 1

WebGL渲染的统计信息:

澳门新葡亰娱乐在线 2

统计参数的意义:

FPS:

每秒呈现的帧数(数字越高越好)。
使用canvas渲染时,描述字段显示为FPS(Canvas),使用WebGL渲染时,描述字段显示为FPS(WebGL)。

Sprite:

渲染节点数量(数字越低越好)。
Sprite统计所有渲染节点(包括容器),这个数字的大小会影响引擎节点遍历,数据组织和渲染的次数。

DrawCall:

DrawCall在canvas和WebGL渲染下代表不同的意义(越少越好)。
Canvas下表示每帧的绘制次数,包括图片、文字、矢量图。尽量限制在100之下。
WebGL下表示渲染提交批次,每次准备数据并通知GPU渲染绘制的过程称为1次DrawCall,在每1次DrawCall中除了在通知GPU的渲染上比较耗时之外,切换材质与shader也是非常耗时的操作。
DrawCall的次数是决定性能的重要指标,尽量限制在100之下。

Canvas:

三个数值 —— 每帧重绘的画布数量 / 缓存类型为“normal”类型的画布数量 /
缓存类型为“bitmap”类型的画布数量”。
CurMem:仅限WebGL渲染,表示内存与显存占用(越低越好)。
Shader:仅限WebGL渲染,表示每帧Shader提交次数。

无论是Canvas模式还是WebGL模式,我们都需要重点关注DrawCall,Sprite,Canvas这三个参数,然后针对性地进行优化。(参见“图形渲染性能”)

在这篇文章中,你会学到优化Flash Professional应用性能的策略。优化过程包括编辑你的FLA工程文档确保发布的应用程序帧频可以满足动画的播放流畅。

第3节:内存优化

对象池

对象池,涉及到不断重复使用对象。在初始化应用程序期间创建一定数量的对象并将其存储在一个池中。对一个对象完成操作后,将该对象放回到池中,在需要新对象时可以对其进行检索。
由于实例化对象成本很高,使用对象池重用对象可减少实例化对象的需求。还可以减少垃圾回收器运行的机会,从而提高程序的运行速度。

以下代码演示使用

Laya.utils.Pool:

ar SPRITE_SIGN = 'spriteSign';
var sprites = [];
function initialize()
{
    for (var i = 0; i < 1000; i++)
    {
        var sp = Pool.getItemByClass(SPRITE_SIGN, Sprite)
        sprites.push(sp);
        Laya.stage.addChild(sp);
    }
}
initialize();

在initialize中创建大小为1000的对象池。

以下代码在当单击鼠标时,将删除显示列表中的所有显示对象,并在以后的其他任务中重复使用这些对象:

Laya.stage.on("click", this, function()
{
    var sp;
    for(var i = 0, len = sprites.length; i < len; i++)
    {
        sp = sprites.pop();
        Pool.recover(SPRITE_SIGN, sp);
        Laya.stage.removeChild(sp);
    }
});

调用Pool.recover后,指定的对象会被回收至池内。

使用Handler.create

在开发过程中,会经常使用Handler来完成异步回调。Handler.create使用了内置对象池管理,因此在使用Handler对象时应使用Handler.create来创建回调处理器。以下代码使用Handler.create创建加载的回调处理器:

Laya.loader.load(urls, Handler.create(this, onAssetLoaded));

在上面的代码中,回调被执行后Handler将会被对象池收回。此时,考虑如下代码会发生什么事:

Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this, onLoading));

在上面的代码中,使用Handler.create返回的处理器处理progress事件。此时的回调执行一次之后就被对象池回收,于是progress事件只触发了一次,此时需要将四个名为once的参数设置为false:

Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this, onLoading, null, false));

释放内存

JavaScript运行时无法启动垃圾回收器。要确保一个对象能够被回收,请删除对该对象的所有引用。Sprite提供的destory会帮助设置内部引用为null。

例如,以下代码确保对象能够被作为垃圾回收:

var sp = new Sprite();
sp.destroy();

当对象设置为null,不会立即将其从内存中删除。只有系统认为内存足够低时,垃圾回收器才会运行。内存分配(而不是对象删除)会触发垃圾回收。

垃圾回收期间可能占用大量CPU并影响性能。通过重用对象,尝试限制使用垃圾回收。此外,尽可能将引用设置为null,以便垃圾回收器用较少时间来查找对象。有时(比如两个对象相互引用),无法同时设置两个引用为null,垃圾回收器将扫描无法被访问到的对象,并将其清除,这会比引用计数更消耗性能。

资源卸载

游戏运行时总会加载许多资源,这些资源在使用完成后应及时卸载,否则一直残留在内存中。

下例演示加载资源后对比资源卸载前和卸载后的资源状态:

var assets = [];
assets.push("res/apes/monkey0.png");
assets.push("res/apes/monkey1.png");
assets.push("res/apes/monkey2.png");
assets.push("res/apes/monkey3.png");

Laya.loader.load(assets, Handler.create(this, onAssetsLoaded));

function onAssetsLoaded()
{
    for(var i = 0, len = assets.length; i < len; ++i)
    {
        var asset = assets[i];
        console.log(Laya.loader.getRes(asset));
        Laya.loader.clearRes(asset);
        console.log(Laya.loader.getRes(asset));
    }
}

关于滤镜、遮罩

尝试尽量减少使用滤镜效果。将滤镜(BlurFilter和GlowFilter)应用于显示对象时,运行时将在内存中创建两张位图。其中每个位图的大小与显示对象相同。将第一个位图创建为显示对象的栅格化版本,然后用于生成应用滤镜的另一个位图:

澳门新葡亰娱乐在线 3

应用滤镜时内存中的两个位图

当修改滤镜的某个属性或者显示对象时,内存中的两个位图都将更新以创建生成的位图,这两个位图可能会占用大量内存。此外,此过程涉及CPU计算,动态更新时将会降低性能(参见“图形渲染性能
– 关于cacheAs)。

ColorFiter在Canvas渲染下需要计算每个像素点,而在WebGL下的GPU消耗可以忽略不计。

最佳的做法是,尽可能使用图像创作工具创建的位图来模拟滤镜。避免在运行时中创建动态位图,可以帮助减少CPU或GPU负载。特别是一张应用了滤镜并且不会在修改的图像。

 

第4节:图形渲染性能

优化Sprite

1.尽量减少不必要的层次嵌套,减少Sprite数量。
2.非可见区域的对象尽量从显示列表移除或者设置visible=false。
3.对于容器内有大量静态内容或者不经常变化的内容(比如按钮),可以对整个容器设置cacheAs属性,能大量减少Sprite的数量,显著提高性能。如果有动态内容,最好和静态内容分开,以便只缓存静态内容。
4.Panel内,会针对panel区域外的直接子对象(子对象的子对象判断不了)进行不渲染处理,超出panel区域的子对象是不产生消耗的。

优化DrawCall

1.对复杂静态内容设置cacheAs,能大量减少DrawCall,使用好cacheAs是游戏优化的关键。
2.尽量保证同图集的图片渲染顺序是挨着的,如果不同图集交叉渲染,会增加DrawCall数量。
3.尽量保证同一个面板中的所有资源用一个图集,这样能减少提交批次。

优化Canvas

在对Canvas优化时,我们需要注意,在以下场合不要使用cacheAs:

1.对象非常简单,比如一个字或者一个图片,设置cacheAs=bitmap不但不提高性能,反而会损失性能。
2.容器内有经常变化的内容,比如容器内有一个动画或者倒计时,如果再对这个容器设置cacheAs=bitmap,会损失性能。

可以通过查看Canvas统计信息的第一个值,判断是否一直在刷新Canvas缓存。

关于cacheAs

设置cacheAs可将显示对象缓存为静态图像,当cacheAs时,子对象发生变化,会自动重新缓存,同时也可以手动调用reCache方法更新缓存。
建议把不经常变化的复杂内容,缓存为静态图像,能极大提高渲染性能,cacheAs有”none”,”normal”和”bitmap”三个值可选。

  1. 默认为”none”,不做任何缓存。
    2.当值为”normal”时,canvas下进行画布缓存,webgl模式下进行命令缓存。
    3.当值为”bitmap”时,canvas下进行依然是画布缓存,webGL模式下使用renderTarget缓存。这里需要注意的是,webGL下renderTarget缓存模式有2048大小限制,超出2048会额外增加内存开销。另外,不断重绘时开销也比较大,但是会减少drawcall,渲染性能最高。
    webGL下命令缓存模式只会减少节点遍历及命令组织,不会减少drawcall,性能中等。

设置cacheAs后,还可以设置staticCache=true以阻止自动更新缓存,同时可以手动调用reCache方法更新缓存。

cacheAs主要通过两方面提升性能。一是减少节点遍历和顶点计算;二是减少drawCall。善用cacheAs将是引擎优化性能的利器。

下例绘制10000个文本:

Laya.init(550, 400, Laya.WebGL);
Laya.Stat.show();

var textBox = new Laya.Sprite();

var text;
for (var i = 0; i < 10000; i++)
{
    text = new Laya.Text();
    text.text = (Math.random() * 100).toFixed(0);
    text.color = "#CCCCCC";

    text.x = Math.random() * 550;
    text.y = Math.random() * 400;

    textBox.addChild(text);
}

Laya.stage.addChild(textBox);

下面是笔者电脑上的运行时截图,FPS稳定于52上下。

澳门新葡亰娱乐在线 4

当我们对文字所在的容器设置为cacheAs之后,如下面的例子所示,性能获得较大的提升,FPS达到到了60帧。

// …省略其他代码… var textBox = new Laya.Sprite();
textBox.cacheAs = "bitmap"; // …省略其他代码…

澳门新葡亰娱乐在线 5

文字描边

在运行时,设置了描边的文本比没有描边的文本多调用一次绘图指令。此时,文本对CPU的使用量和文本的数量成正比。因此,尽量使用替代方案来完成同样的需求。

对于几乎不变动的文本内容,可以使用cacheAs降低性能消耗,参见“图形渲染性能
– 关于cacheAs”。

对于内容经常变动,但是使用的字符数量较少的文本域,可以选择使用位图字体。

跳过文本排版,直接渲染

大多数情况下,很多文本都不需要复杂的排版,仅仅简单地显示一行字。为了迎合这一需求,Text提供的名为changeText的方法可以直接跳过排版。

var text = new Text();
text.text = "text";
Laya.stage.addChild(text);
//后面只是更新文字内容,使用changeText能提高性能
text.changeText("text changed.");

Text.changeText会直接修改绘图指令中该文本绘制的最后一条指令,这种前面的绘图指令依旧存在的行为会导致changeText只使用于以下情况:

澳门新葡亰娱乐在线,文本始终只有一行。

文本的样式始终不变(颜色、粗细、斜体、对齐等等)。

即使如此,实际编程中依旧会经常使用到这样的需要。

如果你曾运行过一个Flash工程,见过播放总是停顿的动画,当然这种情况你非常不想看到。如果你想来做测验重现这种停顿的动画,可以创建一个有简单动画的工程,然后设置帧频为小于10的任意数字(例如5)。然后发布,可以看到这个动画有多么停顿了。

第5节:减少CPU使用量

减少动态属性查找

JavaScript中任何对象都是动态的,你可以任意地添加属性。然而,在大量的属性里查找某属性可能很耗时。如果需要频繁使用某个属性值,可以使用局部变量来保存它:

function foo()
{
    var prop = target.prop;
    // 使用prop
    process1(prop);
    process2(prop);
    process3(prop);
}

计时器

LayaAir提供两种计时器循环来执行代码块。

  1. Laya.timer.frameLoop执行频率依赖于帧频率,可通过Stat.FPS查看当前帧频。
  2. Laya.timer.loop执行频率依赖于参数指定时间。

当一个对象的生命周期结束时,记得清除其内部的Timer:

Laya.timer.frameLoop(1, this, animateFrameRateBased);
Laya.stage.on("click", this, dispose);
function dispose() 
{
    Laya.timer.clear(this, animateFrameRateBased);
}

获取显示对象边界的做法

在相对布局中,很经常需要正确地获取显示对象的边界。获取显示对象的边界也有多种做法,而其间差异很有必要知道。

1.使用getBounds/ getGraphicBounds。、

var sp = new Sprite();
sp.graphics.drawRect(0, 0, 100, 100, "#FF0000");
var bounds = sp.getGraphicBounds();
Laya.stage.addChild(sp);

getBounds可以满足多数多数需求,但由于其需要计算边界,不适合频繁调用。

2.设置容器的autoSize为true。

var sp = new Sprite();
sp.autoSize = true;
sp.graphics.drawRect(0, 0, 100, 100, "#FF0000");
Laya.stage.addChild(sp);

上述代码可以在运行时正确获取宽高。autoSize在获取宽高并且显示列表的状态发生改变时会重新计算(autoSize通过getBoudns计算宽高)。所以对拥有大量子对象的容器应用autoSize是不可取的。如果设置了size,autoSize将不起效。

使用loadImage后获取宽高:

var sp = new Sprite();
sp.loadImage("res/apes/monkey2.png", 0, 0, 0, 0, Handler.create(this, function()
{
    console.log(sp.width, sp.height);
}));
Laya.stage.addChild(sp);

loadImage在加载完成的回调函数触发之后才可以正确获取宽高。

3.直接调用size设置:

Laya.loader.load("res/apes/monkey2.png", Handler.create(this, function()
{
    var texture = Laya.loader.getRes("res/apes/monkey2.png");
    var sp = new Sprite();
    sp.graphics.drawTexture(texture, 0, 0);
    sp.size(texture.width, texture.height);
    Laya.stage.addChild(sp);
}));

使用Graphics.drawTexture并不会自动设置容器的宽高,但是可以使用Texture的宽高赋予容器。毋庸置疑,这是最高效的方式。

注:getGraphicsBounds用于获取矢量绘图宽高。

根据活动状态改变帧频

帧频有三种模式,Stage.FRAME_SLOW维持FPS在30;Stage.FRAME_FAST维持FPS在60;Stage.FRAME_MOUSE则选择性维持FPS在30或60帧。

有时并不需要让游戏以60FPS的速率执行,因为30FPS已经能够满足多数情况下人类视觉的响应,但是鼠标交互时,30FPS可能会造成画面的不连贯,于是Stage.FRAME_MOUSE应运而生。

下例展示以Stage.FRAME_SLOW的帧率,在画布上移动鼠标,使圆球跟随鼠标移动:

Laya.init(Browser.width, Browser.height);
Stat.show();
Laya.stage.frameRate = Stage.FRAME_SLOW;

var sp = new Sprite();
sp.graphics.drawCircle(0, 0, 20, "#990000");
Laya.stage.addChild(sp);

Laya.stage.on(Event.MOUSE_MOVE, this, function()
{
    sp.pos(Laya.stage.mouseX, Laya.stage.mouseY);
});

澳门新葡亰娱乐在线 6

此时FPS显示30,并且在鼠标移动时,可以感觉到圆球位置的更新不连贯。设置Stage.frameRate为Stage.FRAME_MOUSE:

Laya.stage.frameRate = Stage.FRAME_MOUSE;

澳门新葡亰娱乐在线 7

此时在鼠标移动后FPS会显示60,并且画面流畅度提升。在鼠标静止2秒不动后,FPS又会恢复到30帧。

使用callLater

callLater使代码块延迟至本帧渲染前执行。如果当前的操作频繁改变某对象的状态,此时可以考虑使用callLater,以减少重复计算。

考虑一个图形,对它设置任何改变外观的属性都将导致图形重绘:

var rotation = 0,
    scale = 1,
    position = 0;

function setRotation(value)
{
    this.rotation = value;
    update();
}

function setScale(value)
{
    this.scale = value;
    update();
}

function setPosition(value)
{
    this.position = value;
    update();
}

function update()
{
    console.log('rotation: ' + this.rotation + 'tscale: ' + this.scale + 'tposition: ' + position);
}

调用以下代码更改状态:

setRotation(90); setScale(2); setPosition(30);

控制台的打印结果是

rotation: 90 scale: 1 position: 0
rotation: 90 scale: 2 position: 0
rotation: 90 scale: 2 position: 30

update被调用了三次,并且最后的结果是正确的,但是前面两次调用都是不需要的。

尝试将三处update改为:

Laya.timer.callLater(this, update);

此时,update只会调用一次,并且是我们想要的结果。

图片/图集加载

在完成图片/图集的加载之后,引擎就会开始处理图片资源。如果加载的是一张图集,会处理每张子图片。如果一次性处理大量的图片,这个过程可能会造成长时间的卡顿。

在游戏的资源加载中,可以将资源按照关卡、场景等分类加载。在同一时间处理的图片越少,当时的游戏响应速度也会更快。在资源使用完成后,也可以予以卸载,释放内存。

 

第6节:其他优化策略

1.减少粒子使用数量,在移动平台Canvas模式下,尽量不用粒子;

2.在Canvas模式下,尽量减少旋转,缩放,alpha等属性的使用,这些属性会对性能产生消耗。(在WebGL模式可以使用);

3.不要在timeloop里面创建对象及复杂计算;

4.尽量减少对容器的autoSize的使用,减少getBounds()的使用,因为这些调用会产生较多计算;

5.尽量少用try catch的使用,被try catch的函数执行会变得非常慢;

有两个主要因素可以决定Flash的性能:CPU或GPU[解释:图形处理器(Graphics Processing
Unit) ]的使用和内存的使用。这些因素不是互相独立的。一些优化方法也许在这个方面可以提升性能,但是会对另一个方面有副作用。在下面的单元里,我会解释他们的工作原理,提供一些让你可以明确的做决定的原因,比如,为了降低CPU或GPU的加载而增加内存的使用。

 

如果你是为移动设备开发Flash游戏的,很可能你需要一些下面将要讨论的技术手段来达到可接受的帧频。如果你是开发桌面应用的(非游戏),很可能用很小的帧频就可以达到可接受的效果,或者不熟悉这篇文章里描述的技术也可以。

 

判断和衡量游戏性能

 

在理想的世界里,Flash的测试环境允许你模仿目标平台,然后根据目标平台的情况判断你的应用运行情况。不幸的是,除非你的开发平台和目标平台相似,否则现在还不能评估出在测试环境中你的项目的运行情况。

 

除非,你在开发环境中衡量你的应用性能,然后定期让它在目标平台中运行一下,确认它在目标平台也运行良好。

 

如何你在目标平台测试项目并发现问题,你可以用MT类来调试你的应用来解决问题。(在提供的例子文件文件夹内,打开位于这个目录的AS类:MT/com/kglad/MT.as。)

 

内存追踪,内存使用,和性能测试

MT代码改编自Damian Connolly,可以访问他的网站。这个MT类会打印出帧频、内存消耗,列出内存中存在的对象。为了更好使用MT类,遵循以下步骤:

 

1.导入MT类:

  import com.kglad.MT;

 

2.在文档类里初始化它,或在项目的主时间轴上这样写:

  MT.init(this,reportFrequency);

  上面这行代码,“this”表示引用影片的主时间轴,“reportFrequency”表示一个有符号整数(这个数字是自己填的)。主时间轴的引用是用来计算和实现帧频的,reportFrequency是频率(以秒计算),它会跟踪一个Flash应用的帧频输出报告和内存数量的消耗。如果你不想定时输出帧频和内存报告数据,传0(或比0更小的数字)。即使你选择不输出帧频,你仍然运行了这个类的内存跟踪。

 

3.为了跟踪应用里你创建的对象,加上这句话:

MT.track(whatever_object,any_detail);

上面这行代码的第一个参数是你想跟踪的对象(看看它是否从内存中移除了),第二个参数是可选的字符串,它包含任何你想测试的东西。(有些开发人员会用这个参数得到特定对象是什么,在哪和或者存在的时间等细节。)

 

4.为了创建报告,显示你跟踪的对象是否还在内存里,加上这句话:

MT.report();

你没必要了解MT的代码,只管用就行了。但是,了解一些Dictionary类是如何存储所有传给MT.track()的弱引用也是好的。这个类里包括如何使用它的注释。

 

在这篇文章的开头提供了许多使用MT类的示例文件测试。为了更多的学习MT类,查看这些测试例子看看MT类是怎么用的。

就像物理里的观察者效应,我们观察帧频和(或者)内存,和(或者)跟踪内存,改变应用的帧频和内存使用情况。但是,如果观察输出结果比较少很可能观察的效果也会降低。此外,没有绝对的观察数字。每过一段时间调试和优化,改变帧频和/内存使用的情况才是最重要的。MT类很好的做到了承担追踪这些变化的责任。

 

为了降低因为频繁调用trace方法,而出现虚假的低帧频情况,MT类不允许每秒输出结果。(trace方法本身会降低帧频。)要十分注意这点,如果可以的话,你可以用textfield代替trace方法,来尽可能的消除调用trace方法给帧频带来的混淆影响。

 

在范例文件测试工程里,MT类是唯一的工具来检查内存使用和精确的内存问题。你也可以直接检查CPU或GPU的使用情况(查看执行应用程序时帧频的实际使用情况【就是看资源管理器】)。

 

 

实现优化算法

 

 

 

在这个单元,我会开始做一些内存管理的指导,下面单元标题的顺序是按照首字母排序的。然后为了这个目的,我会提供有关CPU/GPU管理信息的子标题来探讨。

 

 

 

也许我们会觉得提供两个单元的技术是合理的。但是,如果你通读完这篇文章,知道了用内存管理影响CPU/GPU的方法,那么列出的内存管理的建议,可以和CPU/GPU单元里列出的方法一起使用,这样效果会更好。

 

 

 

在为你提供特定的最佳实现方法之前,我认为技术问题同样重要,知道的多了你就学的轻松,反之就会很累。我同样会列第二个清单,它会按技术获益的优先级次序从高到低排列。

 

记住这些清单是主观的。它的顺序是依据个人开发经验和能力来定的,还有测试情形和测试环境。

应用的优化技术从易到难排列

 

1.不要使用滤镜

 

2.尽可能使用倒序for循环,避免使用do循环和while循环

 

3.明确的停止使用Timer,以便垃圾回收

 

4.使用弱引用时间侦听器,当不用的时候移除

 

5.尽可能在任何时候严格定义变量类型

 

6.当不需要鼠标交互的时候明确的禁用鼠标交互

 

7.尽可能在任何时候使用回调函数来取代dispatchEvent(继承的)类

 

8.不需要声音时停止Sound类,以便垃圾回收Sound(继承的)类和SoundChannel(继承的)类

 

9.尽量让每一个所需的元素使用最基本的DisplayObject类

 

10.Air应用(移动设备)总是使用cacheAsBitmap
和cacheAsBitmapMatrix
(前一个是位图缓存,后一个是位图矩阵缓存?我没用过)

 

11.尽可能在任何时候重新使用Object

 

12.Event.ENTER_FRAME循环:使用不同的侦听器和不同的侦听函数应用在尽可能少的DisplayObjects

 

13.用PoolObject(对象池)取代创建和垃圾回收Object

 

14.使用局部位图传输(块传输)

 

15.使用阶段的块传输

 

16.使用Stage3D

 

 

 

优化技术后好处由大到小排列

 

1.使用阶段的块传输(如果有足够的系统内存)

 

2.使用Stage3D

 

3.使用局部位图传输(块传输)

 

4.在移动设备上使用cacheAsBitmap 和cacheAsBitmapMatrix

 

5.当鼠标交互不需要的时候明确的禁用鼠标交互

 

6.不要使用滤镜

 

7.需要的时候使用最基本的DisplayObject类

 

8.尽量在任何时候重新利用对象

 

9.Event.ENTER_FRAME循环:使用不同的侦听器和侦听函数,他们最好应用在尽可能少的DisplayObjects

 

10.尽可能使用倒序for循环,避免使用do循环和while循环

 

11.用PoolObject(对象池)取代创建和垃圾回收Object

 

12.尽可能在任何时候严格定义变量类型

 

13.使用弱引用时间侦听器,当不用的时候移除

 

14.尽可能在任何时候使用回调函数来取代dispatchEvent(继承的)类

 

15.明确的停止使用Timer,以便垃圾回收

 

16.不需要声音时停止Sound类,以便垃圾回收Sound(继承的)类和SoundChannel(继承的)类

 

记住这些优先级排序,然后前进到下个单元,学习如何更新你的Flash工程来更有效率的管理内存。

 

 

管理内存

 

下面列的建议不够详尽,但它包括了那些可以大幅度提升Flash性能的策略内容。

 

 

 

使用回调函数 VS dispatEvent

 

 

 

当派发事件的时候会增加内存的使用,因为每个事件必须被创建并且分配内存给它。这种行为是这样解释的:事件也是对象,因此也需要内存。

 

我试着发送少量事件,发现每个消耗40到128字节。我也发现使用回调函数会使用更少的内存,比使用事件效率更高。(查看在实例文档里的测试文件callback_v_dispatchEvent。)

应用滤镜

 

 

 

当你大量应用滤镜时也很消耗内存。根据Adobe帮助文档,使用一个滤镜会消耗双倍内存。在真实Flash Professional
CS6的测试环境中,我曾发现使用滤镜的确会增加内存消耗,但是这种消耗不接近双倍内存。(回顾测试范例,在filters文件夹下)

 

 

 

为每个元素使用正确类型的现实对象

Shape,Sprite,和MovieClip对象每个都使用不同的内存数量。一个Shape对象需要236字节,Sprite需要412字节,MovieClip需要448字节。

 

 

 

如果你在一个工程里使用上千的显示对象,如果不需要交互的话,你也许需要大量Shape类来拯救你的内存。或者,当不需要时间轴时使用Sprite类。

 

 

 

对象池

 

 

 

当你打开你的应用时,要创建各种你会一直使用的对象引用,对象池可以将这些引用保存在数组里。任何时候一个对象需要时,就可以从这个数组里取出使用。

无论何时当一个对象不再需要时,把它再重新放回数组里。

 

有种常规做法是用Vector来代替Array来存储相同类型的对象。使用Vector也许可以比使用Array快两倍,但是!除非你要做成百上千次的操作,否则你不会注意到两者的差别,因为小于上千次的操作它们一样快。(可以看看array_v_vector
文件夹下的范例文件。)

使用对象池可以获得性能上的好处,同时更主要的收益是让管理内存变得简单。如果你在内存利用方面有无限制增长的问题,用对象池可以很好的解决,它是提高性能、降低内存使用的通用技术。

我看到当测试一个每帧包含许多要垃圾回收和再利用的SWF文件里,使用对象池后帧频快了10%,而内存使用则减少了10%。(可以查看pooling_v_gc
文件夹里的范例。)

重用对象

 

 

 

当你要在一个循环里创建许多对象时,最好在循环外先创建一个对象,然后再循环里重复利用它。当然这个方法也不是对所有工程都有效的,但在很多情况下这个技术还是有用的。

在描述位图传输的单元包括一个重用大量对象的例子。你可以在测试文档里看这是怎么实现的。

处理声音

 

 

 

有关声音的问题在内存使用方面是非常小儿科的。当播放一段声音时,它不可能被垃圾回收的(可以使用Flash Professional
CS6来测试文件)。当声音播放完或一个SoundChannel实例执行停止声音时,Sound类就准备垃圾回收了。(想学更多的话可以看看名为sound_test
文件夹下的范例。)

使用Timer

 

 

 

使用Timer时要格外小心。如果没有停止Timer(有两种情况:1.currentCount
属性小于它的循环次数;2.没有调用stop()方法),Timer就不会被垃圾回收,即使你已经移除了侦听器,然后将所有引用设为null。一旦你移除了侦听器,Timer的侦听函数就不被再次调用,但是Timer却仍然消耗内存。

 

 

 

Timer类仅仅使用72字节的内存,所以很可能在一个基于桌面/浏览器的Flash游戏里成为一个很不起眼的问题。但是,当你在移动设备里反复的打开、播放、关闭游戏,然后不断重复启动游戏,你也许就看到这个难以忽略的问题了。

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图