如何将HTML5性能发挥到极致

如何将HTML5性能发挥到极致,第1张

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

LayaAir

引擎支持AS3、TypeScript、JavaScript三种语言开发,然而无论是采用哪种开发语言,最终执行的都是JavaScript代码。所有

看到的画面都是通过引擎绘制出来的,更新频率取决于开发者指定的FPS,例如指定帧频率为60FPS,则运行时每个帧的执行时间为六十分之一秒,所以帧速

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

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

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

第2节:基准测试

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

1

2

Statshow(0,0); //AS3的面板调用写法

LayaStatshow(0,0); //TS与JS的面板调用写法

Canvas渲染的统计信息:

WebGL渲染的统计信息:

统计参数的意义:

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这三个参数,然后针对性地进行优化。(参见“图形渲染性能”)

第3节:内存优化

对象

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

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

以下代码演示使用

LayautilsPool:

1

2

3

4

5

6

7

8

9

10

11

12

ar SPRITE_SIGN = 'spriteSign';

var sprites = [];

function initialize()

{

for (var i = 0; i < 1000; i++)

{

var sp = PoolgetItemByClass(SPRITE_SIGN, Sprite)

spritespush(sp);

LayastageaddChild(sp);

}

}

initialize();

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

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

1

2

3

4

5

6

7

8

9

10

Layastageon("click", this, function()

{

var sp;

for(var i = 0, len = spriteslength; i < len; i++)

{

sp = spritespop();

Poolrecover(SPRITE_SIGN, sp);

LayastageremoveChild(sp);

}

});

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

使用Handlercreate

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

1

Layaloaderload(urls, Handlercreate(this, onAssetLoaded));

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

1

Layaloaderload(urls, Handlercreate(this, onAssetLoaded), Handlercreate(this, onLoading));

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

1

Layaloaderload(urls, Handlercreate(this, onAssetLoaded), Handlercreate(this, onLoading, null, false));

释放内存

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

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

1

2

var sp = new Sprite();

spdestroy();

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

圾回收期间可能占用大量CPU并影响性能。通过重用对象,尝试限制使用垃圾回收。此外,尽可能将引用设置为null,以便垃圾回收器用较少时间来查找对

象。有时(比如两个对象相互引用),无法同时设置两个引用为null,垃圾回收器将扫描无法被访问到的对象,并将其清除,这会比引用计数更消耗性能。

资源卸载

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

var assets = [];

assetspush("res/apes/monkey0png");

assetspush("res/apes/monkey1png");

assetspush("res/apes/monkey2png");

assetspush("res/apes/monkey3png");

Layaloaderload(assets, Handlercreate(this, onAssetsLoaded));

function onAssetsLoaded()

{

for(var i = 0, len = assetslength; i < len; ++i)

{

var asset = assets[i];

consolelog(LayaloadergetRes(asset));

LayaloaderclearRes(asset);

consolelog(LayaloadergetRes(asset));

}

}

关于滤镜、遮罩

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

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

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

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

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

第4节:图形渲染性能

优化Sprite

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

2非可见区域的对象尽量从显示列表移除或者设置visible=false。

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

4Panel内,会针对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”三个值可选。

默认为”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个文本:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Layainit(550, 400, LayaWebGL);

LayaStatshow();

var textBox = new LayaSprite();

var text;

for (var i = 0; i < 10000; i++)

{

text = new LayaText();

texttext = (Mathrandom() 100)toFixed(0);

textcolor = "#CCCCCC";

textx = Mathrandom() 550;

texty = Mathrandom() 400;

textBoxaddChild(text);

}

LayastageaddChild(textBox);

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

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

1

2

// …省略其他代码… var textBox = new LayaSprite();

textBoxcacheAs = "bitmap"; // …省略其他代码…

文字描边

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

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

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

跳过文本排版,直接渲染

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

1

2

3

4

5

var text = new Text();

texttext = "text";

LayastageaddChild(text);

//后面只是更新文字内容,使用changeText能提高性能

textchangeText("text changed");

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

文本始终只有一行。

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

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

第5节:减少CPU使用量

减少动态属性查找

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

1

2

3

4

5

6

7

8

function foo()

{

var prop = targetprop;

// 使用prop

process1(prop);

process2(prop);

process3(prop);

}

计时器

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

LayatimerframeLoop执行频率依赖于帧频率,可通过StatFPS查看当前帧频。

Layatimerloop执行频率依赖于参数指定时间。

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

1

2

3

4

5

6

LayatimerframeLoop(1, this, animateFrameRateBased);

Layastageon("click", this, dispose);

function dispose()

{

Layatimerclear(this, animateFrameRateBased);

}

获取显示对象边界的做法

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

1使用getBounds/ getGraphicBounds。、

1

2

3

4

var sp = new Sprite();

spgraphicsdrawRect(0, 0, 100, 100, "#FF0000");

var bounds = spgetGraphicBounds();

LayastageaddChild(sp);

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

2设置容器的autoSize为true。

1

2

3

4

var sp = new Sprite();

spautoSize = true;

spgraphicsdrawRect(0, 0, 100, 100, "#FF0000");

LayastageaddChild(sp);

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

使用loadImage后获取宽高:

1

2

3

4

5

6

var sp = new Sprite();

sploadImage("res/apes/monkey2png", 0, 0, 0, 0, Handlercreate(this, function()

{

consolelog(spwidth, spheight);

}));

LayastageaddChild(sp);

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

3直接调用size设置:

1

2

3

4

5

6

7

8

Layaloaderload("res/apes/monkey2png", Handlercreate(this, function()

{

var texture = LayaloadergetRes("res/apes/monkey2png");

var sp = new Sprite();

spgraphicsdrawTexture(texture, 0, 0);

spsize(texturewidth, textureheight);

LayastageaddChild(sp);

}));

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

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

根据活动状态改变帧频

帧频有三种模式,StageFRAME_SLOW维持FPS在30;StageFRAME_FAST维持FPS在60;StageFRAME_MOUSE则选择性维持FPS在30或60帧。

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

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

1

2

3

4

5

6

7

8

9

10

11

12

Layainit(Browserwidth, Browserheight);

Statshow();

LayastageframeRate = StageFRAME_SLOW;

var sp = new Sprite();

spgraphicsdrawCircle(0, 0, 20, "#990000");

LayastageaddChild(sp);

Layastageon(EventMOUSE_MOVE, this, function()

{

sppos(LayastagemouseX, LayastagemouseY);

});

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

1

LayastageframeRate = StageFRAME_MOUSE;

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

使用callLater

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

var rotation = 0,

scale = 1,

position = 0;

function setRotation(value)

{

thisrotation = value;

update();

}

function setScale(value)

{

thisscale = value;

update();

}

function setPosition(value)

{

thisposition = value;

update();

}

function update()

{

consolelog('rotation: ' + thisrotation + '\tscale: ' + thisscale + '\tposition: ' + position);

}

调用以下代码更改状态:

1

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改为:

1

LayatimercallLater(this, update);

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

/图集加载

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

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

第6节:其他优化策略

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

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

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

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

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

在游戏中,纹理不仅占据大量的包体,也占据了大量的内存。传统的压缩格式(如JPEG、PNG等)虽能减少资源大小,但是不能被GPU直接识别,还是需要先加载到内存通过CPU解码,转换成RGB/RGBA等能被GPU识别的格式,才能传送到GPU进行渲染。

为避免这些问题,压缩纹理,指的是一种针对GPU的纹理压缩方案,使纹理能够直接被GPU识别并进行渲染,它具有以下优点。

传统的压缩主要目的是 存储 和 传输 ,为了尽可能的高效压缩,使用了可变的压缩比率,因此在解压时需要解压更多的像素位才能读取某个像素的位置,不适合随机和快速读取,也发挥不了GPU的并行处理优势。

而压缩纹理使用一个固定的压缩比率,将纹理划分成多个像素块,每个像素块包含 22 或 44 个像素,然后对每个像素块进行压缩,被压缩的像素信息存储在一个像素集合中,每个像素块的索引位置存储在一个块索引图中。读取时,首先将纹理坐标转化为块索引值,然后在像素集合中查找对应的像素块,最后在这个像素块中找到纹理颜色值。

因为采用了固定的压缩比率,GPU内部可以并行处理,从而快速的解压缩。与之相对的是,纹理的压缩过程发生在程序运行之前,并不在意编码速度,因此在压缩时会遍历所有可能性,找到和原始像素差值最小的编码,这也是纹理压缩耗时较久的原因。

顺便说一下,普通格式中,PNG是无损压缩,JPEG是有损压缩。而压缩纹理都是有损压缩,只是在绝大部分情况下,手机上看不出来而已。

手机上使用压缩纹理依赖于OpenGL ES的支持,OpenGL ES 20本身并没有定义任何纹理压缩格式,它仅提供 glCompressTexImage2D() 方法供应用程序上传压缩纹理,压缩纹理的格式由各个GPU厂商定义和实现。

OpenGL ES 30提供了压缩纹理标准,使各个平台都可以使用同一种压缩纹理,但市面上的设备还需要很长时间才会全部过渡到OpenGL ES 30。因此,仍然需要对不同的平台和设备使用不同的压缩纹理格式。

手机游戏中常用的有以下格式。

ETC1把 44 的像素块压缩成固定的64位编码(8个字节), 44 像素块是16个像素,每个像素4字节,一共占64个字节,所以压缩比是 64/8=8。但是ETC1只能存储RGB信息,不适用带透明度的纹理,为解决这个问题,Creator在ETC1文件中额外写入了透明度信息,即ETC1+A格式,它的压缩比是 64/16=4。

ETC1/ETC1+A需要OpenGL ES 20(对应WebGL 10)环境,目前几乎所有Android手机都支持ETC1,但是iOS不支持。

ETC1/ETC1+A纹理的长宽可以不相等,但要求是2的幂次方。

ETC2是ETC1的扩展,压缩比率一样,但压缩质量更高,而且支持透明通道,能完整存储RGBA信息。

ETC2需要OpenGL ES 30(对应WebGL 20)环境,目前还有不少低端Android手机不兼容,iOS方面从 iPhone5S 开始都支持OpenGL ES 30。

ETC2和ETC1一样,长宽可以不相等,但要求是2的幂次方。

Creator中常用的是PVRTC4+A,压缩比和ETC一样,iOS全系列支持,但是Android不支持。另外PVR要求纹理长宽相等(正方形)且是2的幂次方,例如 1280720 的PNG,转换后变成 20482048 ,这一点会大大增加内存消耗。在实测中还发现转换后的质量不如ETC1,存在模糊、毛边现象,对画面要求高的游戏不适合。

压缩纹理的使用非常简单,根据构建平台添加需要的格式即可,具体参见Creator官方文档,本文不再重复了。

Creator编辑器还提供了转换压缩纹理的选项,根据转换速度分为Fast、Slow等好几档,速度越慢则画面质量越好。但不管选哪个,只影响显示效果和转换时长,显存占用都是一样的。一般情况下,显存占用就是压缩纹理的文件大小,例如文件大小是15M,则它占用的显存也是15M。

在设置压缩纹理格式时,目前Creator 2x版本还需手动一个一个设置。如果想一次性设置所有或部分资源,自己写个脚本遍历修改对应的 meta 文件也比较方便,这里是一个我写好的脚本 一键自动化设置压缩纹理格式

在实际项目中的测试结果是,单图、自动图集、TexturePack合图加起来超过两千张的Creator工程,使用PNG时打出来的apk包大小近500M,内存占用13G。采用压缩纹理后,包体大小降到150M,内存占用降到600M。

如何快速开发一款火爆的小游戏?“火爆”是一个偏运营的词,在小游戏上线120天《微信开发者》公众号有一篇推文,其中有几个数字或许可以用来描述“火爆”这个词。截止微信小游戏正式允许第三方开发者发布已有22天,对外发布的小游戏达300多款,注册用户总规模过亿的游戏有数款,安卓月流水过千万的也有数款。

该文还提到与火爆相关的两个姿势。一是社交匹配度,在小游戏这样一个去中心化的大背景下,让游戏内容和微信社交相结合是一个很重要的点,同时开发者也需要在利用社交互动提升用户体验和群聊分享造成用户骚扰之间选择一个平衡点,过犹不及。第二是 *** 作简便度,说的是游戏易上手 *** 作简单。这是我们根据游戏成为爆款后观察得出的结论,并不是说具备这两个特性就一定能开发出一款火爆的游戏,并且新的爆款游戏也不一定符合这些特点,仅供参考。

如何快速开发一款火爆的小游戏?“火爆”是一个偏运营的词,在小游戏上线120天《微信开发者》公众号有一篇推文,其中有几个数字或许可以用来描述“火爆”这个词。截止微信小游戏正式允许第三方开发者发布已有22天,对外发布的小游戏达300多款,注册用户总规模过亿的游戏有数款,安卓月流水过千万的也有数款。

该文还提到与火爆相关的两个姿势。一是社交匹配度,在小游戏这样一个去中心化的大背景下,让游戏内容和微信社交相结合是一个很重要的点,同时开发者也需要在利用社交互动提升用户体验和群聊分享造成用户骚扰之间选择一个平衡点,过犹不及。第二是 *** 作简便度,说的是游戏易上手 *** 作简单。这是我们根据游戏成为爆款后观察得出的结论,并不是说具备这两个特性就一定能开发出一款火爆的游戏,并且新的爆款游戏也不一定符合这些特点,仅供参考。

今天介绍的内容更倾向于技术方面,所以“火爆”就从标题里面去掉了,并且也不会介绍具体的游戏逻辑如何开发,而是更偏向于如何利用好微信的开放能力开发一款小游戏。

什么是“小游戏”?小游戏是什么?

首先为大家介绍一下小游戏是什么。从普通用户的视角看,小游戏是小程序的一个子类目,可在微信内被便捷的获取和传播,即点即玩,具备出色的用户体验。小游戏是小程序,普通用户分不清也无需分清。

小游戏Runtime

如果放大小游戏的Runtime可以看到很多的细节,这是一个典型的分层架构:

最上层蓝色部分,是游戏代码,分为游戏逻辑,游戏引擎、weapp-adapter三部分。大部分游戏开发会用到一些引擎的工具、工作流,以及利用引擎封装的高层API去实现游戏逻辑。其次是weapp-adapter,因为小游戏的底层一方面不是webview,可以简单看成是webview经过精简、优化过后的平台;另一方面核心能力的实现上却参考了webview。所以这里如果有一个适配器,把小游戏的底层API——wxAPI适配到一个接近webview的接口,对上层引擎、已存在的游戏接入微信小游戏平台则会更加容易,这个就是weapp-adapter的作用。其中只有游戏逻辑是必要的。

可以看到,在架构上小游戏和小程序是有差别的,小游戏没有页面概念的,wxss/wxml不再存在。其次,底层实现也不是webview,小游戏和webview的关系只能说是渲染相关的核心能力可以通过weapp-adapter的简单适配保持接口一致,但同时很多webview上存在的功能并没有对等的实现,比如小游戏就没有DOM/BOM的概念,也没有全局的document/window对象。

小游戏的入口为gamejs文件,语言为Javascript,但有一些限制,比如禁止执行动态代码,因此eval、newFunction等能力是不支持的。配置为gamejson,可以配置横竖屏、接口超时等参数。js里面可以组合wxAPI的能力来实现游戏逻辑,非代码类的资源应该尽量放到cdn,减少整个代码包打包后的大小,以加快用户首次进入时的速度,微信对首包的大小目前限制为4MB。

WebviewAdapter

下面来说一下WebviewAdapter,它的初衷是为了让游戏开发者更好地熟悉我们的平台,所以我们的平台在能力上会尽可能地与webview做一些适配,其实这个适配也是很简单的一层。比如说我们在浏览器里面使用image对象创建一个,而在小游戏里是通过wxcreateimage来创建的,在代码中需要做一个简单的适配。

以此类推,常见的Canvas、document对象都是在Adapter中通过一个简单的适配实现的,大家可以研究链接中的代码。之后官方不会继续维护这个Adapter,我们会更专注于底层能力的建设。

小游戏能力概览

下图是小游戏能力的概览,小游戏能力的迭代比较快,部分能力还没有来得及罗列出来。比如最近刚发布的游戏圈、健康系统防沉迷相关的一些接口。

我们先看一下基础能力,在渲染这部分WebGL10和Canvas2D都是支持的,这里的Canvas更接近于浏览器里面的标准。同时,这里提到的可控帧率的概念,如果小游戏在后台运行的话,可以尽量将帧率降低。

在多媒体部分,小游戏还不能像小程序一样实现实时的音频视频流,这是我们在后续要进一步支持的。网络IO的部分与小程序也是类似的,我们也提供了一些UI的组件,比如说拉起键盘,模态对话框等。

小游戏的社交开放能力现在已经对外了。其中最重要的一个能力是在开放域将微信的好友关系开放出去,给开发者使用,考虑到对用户隐私的保护会有一些设计上的限制。

因为小游戏去中心化的特点,分享这一部分也是非常重要的,开发者要考虑如何将这个能力利用起来。在代码方面,因为首包限制是4MB,但部分小游戏的代码量可能比较大。我们最近也在规划一个分包的能力,允许异步加载代码并执行,但这个代码是一定要经过我们审核的。

如何开发一款小游戏?

那么如何开发一款小游戏?因为我本人也只是开发过一些简单的游戏,并不是专业进行游戏开发,所以接下来我会更多地介绍一下如何利用微信的能力来开发小游戏。

选择小游戏引擎

微信跟引擎商也有比较密切的合作,一般现在的游戏引擎都会支持发布到多个平台,对微信小游戏这个新平台而言,已经有一部分引擎做了适配,比如CocosCreator、EgretEngine以及LayAirEngine。适配的主要工作,类似之前提到的weapp-adapter,把wxAPI的能力,和引擎衔接起来。

比如引擎一般会把小游戏平台和webview平台对标,适配过程就是把wxAPI对应到webview的能力,同时把只存在于webview能力的依赖去除,比如不再依赖BOM、DOM。已适配的引擎都有相应的文章介绍如何把游戏发布到微信小游戏平台。

设备/环境适配

小游戏会有API提供获取屏幕的宽高、设备像素比等能力。小游戏开发完成后,在开发者工具也可以发起真机测试的请求,微信提供了不同设备的测试集群,帮助开发者提前去发现问题。基础库提供的wxAPI本身是一个不断迭代更新的过程,对于使用了新能力的小游戏,需要做低版本兼容。

微信登录

小游戏的登录过程,跟小程序是类似的。需要用户自己去定义登录状态。appsecret/session_key代表的是小游戏开发者和微信平台之间的一种信任约定,比如支付、上报托管数据,平台方需要验证access_token(只有appsecret才能换得到),和用户相关的还要验证session_key的签名,才能保证请求来自于小游戏开发者/用户,而不是恶意的第三方和随意捏造的用户。

access_token是一种应用态的access_token,和用户无关,需要保证全局维护一份,应该有一个中控的模块去保证access_token有效,同时在有效期内直接使用本地cache的access_token,而不是每次使用都去生成新的access_token,否则可能遇到调用频率限制的错误而影响服务。切记appsecret/session_key不要放到前端代码中去,否则可能会被坏人利用损坏小游戏开发者/用户的权益。

缓存

缓存类型包括数据缓存和文件缓存两类。数据缓存即key-value存储,适合结构化类型的小数据存储,上限为10MB。文件缓存提供了一个完整的文件系统API,包括目录/文件的增删改读,适合针对经常使用的网络资源做本地缓存,上限是50MB。

和浏览器不同的是,微信只提供了基本的存储管理能力,并不对存储什么,和存储满时删除什么做一些 *** 作。开发者自行灵活定义缓存以及淘汰策略,比如对经常访问的资源存储到文件系统以及在文件存储满时,清理一些最近不常访问的文件。

开放数据域

开放数据域是一个封闭、独立的JavaScript作用域,和执行游戏逻辑的环境——称为“主域”隔离。其目的是在保证用户隐私的前提下开放用户数据给第三方,提升小游戏的整体用户体验。以下为物理视图,主域的入口为gamejs,开放数据域则是一个独立的目录,其入口文件为indexjs。

主域和开放数据域的通信受到严格的管制,基本原则是只进不“出”。

只进:允许外部的数据进入开放数据域,即主域可以随时postMessage到开放域,以及开放域引用主域准备好的本地资源

不“出”:不允许开放数据域的数据被上传到第三方服务器去。因为开放数据域里面,indexjs是可以直接访问到用户敏感数据的,比如同玩好友数据。当然最终开放数据域需要indexjs在综合各种数据后把数据以图形图像的方式渲染到sharedCanvas上,在主语sharedCanvas允许draw到主域的上屏Canvas上,最终用户会在显示屏上看到gamejs画出来的好友排行榜、群排行榜或好友超越等社交互动信息。

在开发数据域中的数据,开发者没法把数据拿出去和游戏数据做关联,所以如果需要在开放域下展示的游戏数据,比如分数,开发者需要将该数据通过上报接口把游戏数据托管到平台。这样就可以在开发数据域里面就取到相关数据,其应用场景有好友排行、群排行榜、超越好友提示等。

分享

包括自定义分享和系统菜单分享,可以分享到群聊、单聊。也可以把分享上下文与特定的群关联,实现一些群PK、群排行榜的场景。分享是一把双刃剑,需要谨慎使用,一方面避免过度骚扰用户/群聊,另一方面增强社交互动提供好的游戏体验,需要找到一个合适的平衡点。

支付

小游戏在安卓下支持虚拟支付,它的方式目前只有一种:即货币托管的方式。主要分为2个流程:

1充值:RMB->游戏币,这里开发者只需要拉起支付的流程,平台负责把用户RMB兑换成对应的游戏币,存储到用户对应的游戏帐号上

2使用游戏币购买道具:开发者可以扣除对应的游戏币,给用户发放游戏内道具,扣除游戏币的过程需要有一定的事务机制,去保证在网络异常的情况下交易正常。扣除游戏币的接口支持根据订单id去重,意味着网络超时等情况下,开发者可用同样的订单id去重试扣除,直至返回明确的响应。

以下为简单时序图,部分角色针对开发者无需关心的部分做了相应简化处理:

性能

小游戏常见的性能问题,一般是内存造成的。如果内存占用太多会被微信客户端主动关闭,因此开发者在用户游戏过程中要及时释放不再使用的内存(js代码去除引用,或主动调用对应资源的释放接口,如果有的话),特别是Canvas和Image类大型对象,同时可以主动调用wxtriggerGC触发底层回收对应资源。

对于和游戏逻辑相对独立的工作,可以考虑在worker中去实现,小游戏提供了独立的worker线程执行js逻辑的能力。

版本更新机制

小游戏启动的过程分为冷启动和热启动。冷启动是指内存中无该小游戏的运行实例的情况下,启动小游戏的过程;热启动是指小游戏的运行实例在内存中还存在,只是暂时切换到了后台,这时用户再次触发小游戏回到前台的过程。

小游戏会在冷启动时检查小游戏的版本,如有新版本,在下载回本地后,下一次冷启动即可使用最新版。当然,我们也提供了API可以供开发者决策在有版本可用时,是否需要强制更新。

运维

特别提醒,小游戏有完善的后端监控,可以通过“运维中心”开启,比如脚本错误监控。脚本错误主要由运行过程中未捕获的异常触发,需要重点关注。该类异常,可能会导致用户小游戏前端的js逻辑暂停执行。

同时,平台也提供了完善的数据分析服务,可以通过“小游戏数据助手”进行数据分析。

在OpenGL ES3上实现MSAA的主要思想是创建一个用于多采样FBO,用它来接受所有渲染指令。当需要上屏时,将多采样的FBO resolve 回默认的FBO,在这个降采样过程中得到的像素值会更平滑,从而减少锯齿感。

苹果的一片文档写的足够详细: Using Multisampling to Improve Image Quality

不过苹果用的是ES2,但MSAA的相关接口在ES3上也只是换了签名而已,迁移成本不高。

有一些小游戏在开抗锯齿后会出现闪屏的Bug,经排查应该是FBO还没有blit完成,就被标记成了invalid,所以读不出来值了。

glInvalidateFramebuffer 可以让客户端标记哪些RBO是不需要的,这样OpenGL状态机不用再维持一些不必要的状态更新,而且在TBR/TBDR架构的GPU上,渲染时Tile不用将不再需要的数据写回显存,可以减少带宽的使用,提高性能。

但貌似在某些驱动 glBlitFramebuffer 这个函数是异步的,导致FBO还没有传完,就被invalidate了,产生有偶现的黑屏。我猜测这是一些驱动的优化导致的,在iPhone 6s上会复现,在Mac和一些安卓机上没有复现。

所以为了保证多采样FBO能正确resolve,需要在 glBlitFramebuffer 之后加 glflush ,这样可以保证命令发送到GPU后再将一些不需要的RBO标记成invalidate。

有小游戏开发商在设置WebGL时,将多采样的sampleCount设成了不支持的值,这在WebGL里是可以运行的,但在OpenGL里不支持的采样值会直接输出黑色像素。

所以在创建OpenGL时,先用 glGetInternalformativ 读取一下当前驱动支持的采样值,若开发商设置的值不支持,会就近选一个近邻的采样值,不然产生黑屏的话很难查Bug。

天际线,天际线又称城市轮廓或全景,是指天空与观察点周围的表面以及要素相分离的界线。天际线分析功能可根据观察点,生成当前场景窗口中建筑物顶端边缘与天空的分离线,主要用于城市建筑规划等场景中

###iClient3D for WebGL提取天际线

1创建天际线分析,可设定相关展示属性参数

// 创建天际线分析对象

var skyline = new CesiumSkyline(scene);

//设置颜色

skylinecolor =CesiumColorCYAN;

// 设置天际线的显示模式,DisplayModeLINE表示天际线的线模式,DisplayModeFACE表示天际线的面模式

//默认为线模式

skylinedisplayStyle=CesiumSkylinedisplayModeLINE

2设置观测点参数并执行(以场景当前位置为例,可按照需求进行设定)

// 获取场景的当前相机位置

var cartographic = scenecamerapositionCartographic;

var longitude = CesiumMathtoDegrees(cartographiclongitude);

var latitude = CesiumMathtoDegrees(cartographiclatitude);

var height = cartographicheight;

//天际线分析的视口位置设置成当前相机位置

skylineviewPosition = [longitude, latitude, height];

// 设置俯仰,单位:度,取值范围为0-90

skylinepitch = CesiumMathtoDegrees(scenecamerapitch);

// 获取或设置相机与正北方向的夹角。单位:度,取值范围0-360

skylinedirection = CesiumMathtoDegrees(scenecameraheading);

// 天际线分析半径设置为10000米,单位:米。默认值为-10,表示无穷远

skylineradius = 10000;

// 执行天际线分析

skylinebuild();

这样我们就能在场景中看到一条天际线了

上面我们已经在场景中展示出了天际线,那除了这个,我们还能从天际线分析的结果里面做什么应用尼,接下来我们来看看吧,可结合webgl官方实例-天际线分析('>

整理了一些有关canvas的属性( ’ - ’ )

canvas画布

用于绘制图像,本身不具备绘制能力,需要通过脚本来完成绘画,通常这个脚本就是javascript

canvas的默认宽高为300150,不能通过css或行内样式修改它的宽高,它有自己的宽高属性

canvas是一个html元素,通过js来执行绘制

getContext()是用来获取canvas上下文渲染环境和绘画功能的方法,目前只有一个参数:'2d',代表返回一个2d的绘制环境,将来有可能3d,但是目前不支持

它可以做什么?

  html5小游戏

  绘制,图标,图表,曲线图等等,例如地图上的热力图等等

  以及各种酷炫的动画效果都可以通过canvas来完成(可以了解一下WEBGL<>

查阅信息说不要使用unity自带的字体即可。

webgl不显示字体,开始导出来发现一些字不显示,还以为分辨率压缩Text文本导致字不显示,后来发现原来是webgl的问题,查阅信息说不要使用unity自带的字体即可。

以上就是关于如何将HTML5性能发挥到极致全部的内容,包括:如何将HTML5性能发挥到极致、一个 *** 作让游戏内存立减50+%-CocosCreator性能优化之压缩纹理、如何快速开发一个小游戏等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9560573.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存