如何最大化HTML5的性能

第1节:代码执行的基本原则

LayaAir

该引擎支持三种语言的开发:AS3、TypeScript和JavaScript。但是,无论采用哪种开发语言,最终执行的都是JavaScript代码。全部

你看到的图片都是引擎绘制的,更新频率取决于开发者指定的FPS。比如帧频是60FPS,运行时每帧的执行时间是六十分之一秒,那么帧率。

越高视觉感觉越流畅,60帧是全帧。

因为实际运行环境在浏览器中,所以性能也取决于JavaScript解释器的效率。在低性能的解释器中可能达不到指定的FPS帧率,所以这部分不是开发者能决定的。开发者能做的就是尽可能的通过优化来提高低端设备或者低性能浏览器中的FPS帧率。

LayaAir引擎重画每一帧。在性能优化上,不仅要关注每一帧执行逻辑代码带来的CPU消耗,还要关注每一帧调用的画图指令数量和GPU的纹理提交数量。

第2部分:基准测试

LayaAir engine内置的性能统计工具可用于基准测试和实时检测当前性能。开发人员可以使用laya.utils.Stat类通过Stat.show()显示统计面板。下面的示例显示了具体的代码:

1

2

Stat.show(0,0);//AS3面板调用编写

赖雅。Stat.show(0,0);//TS和JS面板调用编写

画布渲染的统计数据:

WebGL渲染的统计数据:

统计参数的重要性:

FPS:

每秒渲染的帧数(数字越大越好)。

用canvas渲染时,描述字段显示为FPS(Canvas),用WebGL渲染时,描述字段显示为FPS(WebGL)。

雪碧:

渲染节点的数量(数量越少越好)。

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

抽奖电话:

DrawCall在canvas和WebGL渲染下代表不同的含义(越少越好)。

Canvas表示每一帧的绘制时间,包括图片、人物和矢量图。尽量限制在100以下。

WebGL

下表表示渲染的提交批次,每次准备数据并通知GPU渲染绘制的过程称为1次draw调用,除了每1次draw调用通知GPU渲染外,其他都很耗时。

此外,在材质和着色器之间切换也是一个非常耗时的操作。DrawCall次数是决定性能的重要指标,应尽量限制在100。

画布:

三个值-每帧重画的画布数量/具有“正常”缓存类型的画布数量/具有“位图”缓存类型的画布数量。

CurMem:仅WebGL渲染,表示内存和视频内存占用(越低越好)。

着色器:仅WebGL渲染,指示每帧着色器提交的数量。

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

第3部分:内存优化

对象池

对象池涉及到对象的重复使用。在应用程序初始化期间,会创建一定数量的对象并将其存储在一个池中。在对一个对象的操作完成后,将该对象放回池中,并在需要新对象时检索它。

由于实例化对象的成本很高,使用对象池重用对象可以减少实例化对象的需要。还可以减少垃圾收集器运行的机会,从而提高程序的运行速度。

下面的代码演示如何使用?

Laya.utils.Pool:

1

2

10

11

12

ar SPRITE _ SIGN = ' spriteSign

var sprites =[];

函数initialize()

{

for(var I = 0;我& lt1000;i++)

{

var sp = pool . getitembyclass(SPRITE _ SIGN,SPRITE)

sprites . push(sp);

laya . stage . addchild(sp);

}

}

initialize();

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

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

1

2

10

Laya.stage.on("click ",this,function()

{

var sp

for(var i = 0,len = sprites.length我& ltleni++)

{

sp = sprites . pop();

Pool.recover(SPRITE_SIGN,sp);

laya . stage . remove child(sp);

}

});

调用Pool.recover后,指定的对象将被回收到池中。

使用Handler.create

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

1

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

在上面的代码中,处理程序将在回调执行后被对象池回收。此时,考虑下面的代码会发生什么情况:

1

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

在上面的代码中,Handler.create返回的处理程序用于处理progress事件。此时回调执行一次后被对象池回收,所以进度事件只触发一次。此时,名为once的四个参数需要设置为false:

1

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

释放内存

JavaScript运行时无法启动垃圾收集器。为了确保对象可以被回收,请删除对该对象的所有引用。Sprite提供的destory将帮助设置内部引用为空。

例如,下面的代码确保对象可以作为垃圾回收:

1

2

var sp = new Sprite();

sp . destroy();

当一个对象被设置为null时,它不会立即从内存中删除。只有当系统认为内存足够低时,垃圾收集器才会运行。触发垃圾收集的是内存分配,而不是对象删除。

垃圾

在垃圾收集期间,它可能会占用大量CPU并影响性能。尝试通过重用对象来限制垃圾收集的使用。此外,尽可能将引用设置为null,这样垃圾收集器就可以花更少的时间来寻找对。

大象。有时候(比如两个对象互相引用),两个引用不能同时设置为null,垃圾收集器会扫描不可访问的对象并清除,这比引用计数消耗更多的性能。

资源卸载

游戏运行时总会加载很多资源,这些资源在使用后要及时卸载,否则会留在内存中。

以下示例演示了加载后资源卸载前后的资源状态比较:

1

2

10

11

12

13

14

15

16

17

18

var资产=[];

assets . push(" RES/apes/monkey 0 . png ");

assets . push(" RES/apes/monkey 1 . png ");

assets . push(" RES/apes/monkey 2 . png ");

assets . push(" RES/apes/monkey 3 . png ");

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

函数onAssetsLoaded()

{

for(var i = 0,len = assets.length我& ltlen++i)

{

var asset = assets[I];

console . log(laya . loader . getres(asset));

laya . loader . clearres(asset);

console . log(laya . loader . getres(asset));

}

}

关于滤镜,遮罩

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

应用过滤器时内存中的两个位图。

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

ColorFiter需要计算画布渲染下的每一个像素点,但是WebGL下的GPU消耗可以忽略。

最佳做法是,尽可能使用图像创建工具创建的位图来模拟滤镜。避免在运行时创建动态位图有助于减少CPU或GPU负载。尤其是应用了滤镜且不会被修改的图像。

第4部分:图形渲染性能

优化精灵

1.尽量减少不必要的层次嵌套,减少精灵的数量。

2.不可见区域中的对象应该尽可能从显示列表中移除,或者设置visible=false。

3.对于容器中大量的静态内容或者不经常变化的内容(比如按钮),可以为整个容器设置cacheAs属性,这样可以大大减少Sprite的数量,显著提高性能。如果有动态内容,最好将其与静态内容分开,以便只缓存静态内容。

4.在面板内,面板区域外的直接子对象(子对象的子对象无法判断)不会被渲染,面板区域外的子对象也不会被消耗。

优化绘图调用

1.为复杂的静态内容设置缓存可以大大减少DrawCall,利用好缓存是游戏优化的关键。

2.尽量保证同一图集的图像渲染顺序相邻。如果交叉渲染不同的地图集,DrawCall的数量会增加。

3.尽量保证同一面板的所有资源使用一个图集,这样可以减少提交批次。

优化画布

在优化Canvas时,我们需要注意在以下情况下不要使用缓存:

1.对象很简单,比如一句话或者一张图片。设置cacheAs=bitmap不会提高性能,但会降低性能。

2.容器中的内容经常变化,例如容器中的动画或倒计时。如果再次将cacheAs=bitmap设置为该容器,将会降低性能。

您可以通过查看画布统计数据的第一个值来判断画布缓存是否已经刷新。

关于cacheAs

设置

设置cacheAs可以将显示的对象缓存为静态图像。当设置了cacheAs时,子对象将发生变化,并将自动重新缓存。同时,可以手动调用reCache方法来更新缓存。

建议将变化不频繁的复杂内容缓存为静态图像,这样可以大大提高渲染性能。缓存有三个值:“无”、“正常”和“位图”。

默认值为“无”,不进行缓存。

2.当值为“normal”时,画布缓存在canvas下,命令缓存在webgl模式下。

3.

当值为“bitmap”时,canvas下仍然使用canvas缓存,webGL模式下使用renderTarget缓存。这里应该注意的是,在webGL下,

RenderTarget缓存模式的大小限制为2048,这将增加内存开销。另外,连续重绘的代价也比较大,但是会减少drawcall和着色。

染色性能最高。webGL下的命令缓存模式只会减少节点遍历和命令组织,不会减少drawcall,性能中等。

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

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

以下示例绘制了10000个文本:

1

2

10

11

12

13

14

15

16

17

18

19

赖雅,550,400。web GL);

赖雅。stat . show();

新赖雅。sprite();

var文本;

for(var I = 0;我& lt10000;i++)

{

新赖雅。text();

text . text =(math . random()* 100)。to fixed(0);

text . color = " # CCCCCC ";

text . x = math . random()* 550;

text . y = math . random()* 400;

textBox.addChild(文本);

}

Laya.stage.addChild(文本框);

下面是作者电脑上的运行时截图,FPS稳定在52左右。

当我们将文本所在的容器设置为cacheAs时,如下例所示,性能大大提升,FPS达到60帧。

1

2

//…省略其他代码… var textBox =新赖雅。sprite();

textBox.cacheAs = " bitmap//…省略其他代码…

文本笔划

在运行时,有笔画的文本比没有笔画的文本多被调用一次绘图指令。此时,CPU使用的文本量与文本量成正比。因此,尝试使用替代方案来满足相同的需求。

对于几乎不变的文本内容,可以使用缓存来减少性能消耗。请参见“图形渲染性能-关于缓存”。

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

跳过文字排版,直接渲染。

大多数情况下,很多文字并不需要复杂的排版,只是简单的显示一行字。为了满足这种需求,Text提供的名为changeText的方法可以直接跳过排版。

1

2

var Text = new Text();

text.text = " text

Laya.stage.addChild(文本);

//只更新文本内容。使用changeText可以提高性能。

text.changeText("文本已更改。");

Text.changeText将直接修改绘图指令中的文本所绘制的最后一条指令。先前绘图指令仍然存在的这种行为将导致changeText仅在以下情况下使用:

总是只有一行文字。

文本的样式总是相同的(颜色、粗细、斜体、对齐等)。).

即便如此,这种需求在实际编程中也经常用到。

第5部分:降低CPU使用率

减少动态属性查找

JavaScript中的任何对象都是动态的,可以随意添加属性。然而,在大量属性中寻找一个属性可能很费时间。如果需要经常使用某个属性值,可以使用局部变量来保存它:

1

2

函数foo()

{

var prop = target.prop

//使用道具

process 1(prop);

process 2(prop);

进程3(prop);

}

计时器

LayaAir提供了两个定时器循环来执行代码块。

Laya.timer.frameLoop的执行频率取决于帧频,当前帧频可以通过Stat查看。每秒传输帧数

Laya.timer.loop的执行频率取决于参数指定的时间。

当对象的生命周期结束时,请记住清除其内部计时器:

1

2

Laya.timer.frameLoop(1,this,animateFrameRateBased);

Laya.stage.on("click ",这个,dispose);

函数dispose()

{

Laya.timer.clear(this,animateFrameRateBased);

}

获取显示对象边界的实践。

在相对布局中,经常需要正确获取显示对象的边界。获取显示对象边界的方法有很多种,需要知道它们之间的区别。

1.使用getBounds/ getGraphicBounds。、

1

2

var sp = new Sprite();

sp.graphics.drawRect(0,0,100,100," # ff 0000 ");

var bounds = sp . getgraphicbounds();

laya . stage . addchild(sp);

GetBounds可以满足大部分需求,但不适合频繁调用,因为它需要计算边界。

2.将容器的autoSize设置为true。

1

2

var sp = new Sprite();

sp.autoSize = true

sp.graphics.drawRect(0,0,100,100," # ff 0000 ");

laya . stage . addchild(sp);

上面的代码可以在运行时正确获得宽度和高度。当autoSize获得宽度和高度并且显示的列表的状态改变时,它会重新计算(AutoSize通过getBoudns计算宽度和高度)。因此,不建议将autoSize应用于具有大量子对象的容器。如果设置了size,autoSize将不起作用。

使用loadImage后获取宽度和高度:

1

2

var sp = new Sprite();

sp . loadimage(" RES/apes/monkey 2 . png ",0,0,0,0,Handler.create(this,function()

{

console.log(sp.width,sp . height);

}));

laya . stage . addchild(sp);

只有在回调函数被触发后,LoadImage才能正确获取宽度和高度。

3.直接调用大小设置:

1

2

laya . loader . load(" RES/apes/monkey 2 . png ",Handler.create(this,function()

{

var texture = laya . loader . getres(" RES/apes/monkey 2 . png ");

var sp = new Sprite();

sp . graphics . draw texture(texture,0,0);

sp.size(texture.width,texture . height);

laya . stage . addchild(sp);

}));

使用Graphics.drawTexture不会自动设置容器的宽度和高度,但是可以使用纹理的宽度和高度将其赋予容器。毫无疑问,这是最高效的方式。

注意:getGraphicsBounds用于获取矢量绘图的宽度和高度。

根据活动状态更改帧速率。

有三种模式的帧速率,阶段。FRAME_SLOW保持FPS在30;舞台。FRAME_FAST保持FPS在60;舞台。FRAME_MOUSE选择性地将FPS保持在30或60帧。

有时候没必要让游戏以60FPS运行,因为30FPS在大多数情况下可以满足人类视觉的反应,但是在鼠标交互的时候,30FPS可能会导致画面不连贯,所以Stage。FRAME_MOUSE应运而生。

下面的示例显示了以Stage的帧速率在画布上移动鼠标。FRAME_SLOW,使球随鼠标移动:

1

2

10

11

12

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(事件。MOUSE_MOVE,this,function()

{

sp.pos(Laya.stage.mouseX,laya . stage . mousey);

});

此时FPS显示30,鼠标移动时可以感觉到球的位置更新不连贯。将Stage.frameRate设置为Stage。框架_鼠标:

1

Laya.stage.frameRate = Stage。框架_鼠标;

此时鼠标移动后FPS会显示60,画面流畅度会有所提升。鼠标静止2秒钟后,FPS会回到30帧。

稍后使用呼叫

CallLater延迟代码块的执行,直到呈现此帧。如果当前操作频繁更改对象的状态,可以考虑使用callLater来减少重复计数。

考虑一个图形,在其上设置任何改变外观的属性都会导致图形被重画:

1

2

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

变化旋转= 0,

scale = 1,

位置= 0;

函数集旋转(值)

{

this.rotation = value

update();

}

函数集比例(值)

{

this.scale = value

update();

}

功能设置位置(值)

{

this.position = value

update();

}

功能更新()

{

console . log(' rotation:'+this . rotation+' \ t scale:'+this . scale+' \ t position:'+position);

}

调用以下代码来更改状态:

1

set rotation(90);set scale(2);set position(30);

控制台的打印输出如下

旋转:90度刻度:1度位置:0度

旋转:90度刻度:2度位置:0度

旋转:90度刻度:2度位置:30度

Update已经调用了三次,最后的结果是正确的,但是前面的两次调用是不必要的。

尝试将三个更新更改为:

1

Laya.timer.callLater(这个,更新);

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

图片/图集加载

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

在游戏的资源加载中,可以根据关卡、场景等加载资源。同时处理的图片越少,游戏的响应速度就会越快。资源被使用后,也可以卸载它来释放内存。

第6节:其他优化策略

减少粒子的使用数量,尽量不要在移动平台的画布模式下使用粒子;

2.在画布模式下,尽量减少使用旋转、缩放、alpha和其他属性,这些属性会消耗性能。(WebGL模式下可用);

3.不要在timeloop中创建对象和复杂的计算;

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

5.尽量少用try catch,try catch执行的函数会变得很慢;