H5游戏性能优化


前一段时间利用Phaser引擎做了几款2D的H5游戏,期间遇到了一些性能问题,总结下来分享给大家,欢迎讨论。

常见性能问题

H5游戏相对于一般的H5页面往往更加复杂,资源比较多、动画交互复杂,所以更容易导致性能问题。总结了下大概有这么几个常见的问题:

1、加载速度慢。游戏往往需要加载很多的资源,如果采用了游戏引擎,还需要加载引擎的代码。资源加载压力大,容易导致启动速度慢的问题。

2、发热、耗电。相对来说,游戏的交互场景往往更复杂,游戏资源更多,CPU计算和GPU的渲染消耗也更大,容易出现发热耗电的问题。

3、卡顿、掉帧。这也是一个很普遍的问题了,尤其是一些中低端的Android机型。

4、内存消耗大。游戏场景复杂,各种图片资源多,对手机内存的消耗也更大,再加上canvas元素本身还要消耗掉不少内存。

优化方案

H5游戏和普通H5页面的优化有很多相通之处,一些H5的性能优化策略同样也适用于游戏,比如减少请求、按需加载等等,游戏也有一些只适用于自身的一些优化方案。这里总结了常用的一些策略,有兴趣的同学可以参考。

尽可能减少请求

这是H5里面很常见的一个优化方案了。可以采取的方案包括合并js,css内联,图片内联,精灵图等。不过需要注意的是要把握好资源合并的尺度,否则js过大容易导致白屏时间过长。并且,精灵图过大在一些中低端Android机型上也可能导致性能问题。

按需加载、延迟加载

只加载首屏幕需要的资源,而对于一些弹层或者某些特定时机才会出现的资源可以在需要的时候在加载或者延时加载。比如规则弹层,只有点击了规则按钮才会出现,那就可以在用户点击的时候再去请求资源。

尽可能提前请求数据,并行请求数据

这个比较好理解,减少数据请求的时间也就能更快的渲染出主页面了。

动静分离,根据游戏状态适当适时降低帧率

游戏引擎一般都是调用requestAnimationFrame或者setTimeout不停的循环执行的,这是游戏做动画交互的基础。但实际上,有些游戏并不是一直都有动画的,比如某些时刻整个页面都是禁止的,如果继续做循环渲染就会导致没必要的性能消耗,这时候我们就可以停止掉游戏引擎的循环逻辑或者降低帧率。

使用内存池

这在频繁的创建和删除一些游戏对象的时候特别有用,尤其是游戏对象的创建、销毁开销比较大的情况。

尽可能减少渲染对象

减少屏幕中渲染的对象,比如有的游戏对象可以合并显示,不可见的对象及早销毁,只渲染可见区域的对象等等。另外,游戏中有的页面可能只是简单的静态内容呈现,没有复杂的动画交互,这类页面可以用dom来实现。

减少每一帧的计算

有的计算并不需要在每一帧都处理,这时可以每隔几帧或者间隔一定时间才做一次计算,比如检测某些对象是否不可见了,我们允许适当的延迟,没必要每一帧都去检测。

对于一些复杂的运算可以分帧计算,每帧只计算一部分,利用多次局部计算结果来实现整体的计算。

除此之外,还可以从游戏引擎内部的运行原理入手。为了通用性,游戏引擎在每一帧的循环中往往会有一些冗余逻辑,如果确定有些计算是该游戏不需要的,就可以去掉。比如我们发现在Phaser引擎中每一帧都有声音更新、屏幕size检测相关的逻辑,但是我们完全没有用到这方面的功能,就去掉了。

Canvas缓存、离屏Canvas

我们先将元素绘制到一个离屏的Canvas上,缓存起来,然后游戏循环中每次将离屏的Canvas绘制到真正显示的Canvas上,这样就大大减少了对Canvas api的调用。游戏引擎对文字的绘制一般都采用了这种方案,文字的绘制本身开销也比较大,采用离屏的方式能优化掉不少性能开销。

分层Canvas

分层Canvas其实也是一种动静分离策略。不同的元素需要刷新的频率很多时候是不一样的,比如背景可能绝大部分的情况都不需要刷新,而一些动画就需要每帧都刷新。这时候我们就可以按照刷新帧率的要求来对游戏元素进行分层,不同帧率要求的元素绘制到不同的层上。

游戏资源分离组合和加载

理论上来讲也是一种分离策略。稍微复杂的游戏中一般都会有比较多的资源,我们可以按照使用的时机、频率来对资源做分离处理,比如将首屏的资源和出现频率比较小一些弹层资源分离组合。这样有利于我们控制资源动态加载和销毁,还可以减少精灵图资源裁剪的消耗。

图片压缩,部分图片去掉alpha通道

对图片进行压缩也是必不可少的步骤。在图片清晰度可接受的前提下,适当采用有损压缩的方式最大限度地压缩图片。而对一些不需要透明度的图片可以去掉alpha通道,这可以大大减少图片大小和渲染的性能开销。

按照屏幕分辨率请求不同尺寸的图片

这个也很好理解,高清分辨率加载更高清的图片,低分辨率的手机上适当采用更低尺寸的图片。如果有必要,还可以尝试按照手机内存等实际情况加载不同分辨率的图片。

适当使用cacheAsBitmap

cacheAsBitmap是一种空间换时间的方案。如果一个游戏对象容器里面的对象元素是静态的,可以考虑把这个容器做成一个bitmap,这样整个容器只需要调用一次draw,能有效提高绘制效率,同时也减少了矩阵运算。不过cacheAsBitmap会显著增加内存的占用,所以也要注意权衡是否有必要开启。

尽量少切换绘制状态

如果是Canvas绘制方式,避免频繁切换Context的绘制状态,比如设置font,fillStyle等等。

如果是WebGL,尽量少切换shader。怎么知道是否会切换shader呢?最简单直接的方法就是查看源码看看游戏元素使用什么shader实现的。

其他可以注意的一些点

Canvas画布不要过大(注意是属性width/height,不是style的width/height),如果超过屏幕大小,在WebGL渲染下会明显降低性能。

图片的旋转缩放在采用Canvas渲染时可能有额外的性能开销,WebGL渲染没有这个问题。

游戏引擎的mask、filter等一些比较高级的特性会增加性能损耗,尽可能采用简单的纹理贴图的方式实现。

抗锯齿有一定的性能消耗,如果不是必须的,采用WebGL渲染时可以考虑关闭抗锯齿。Canvas模式下尽量采用整数坐标。

总结

其实很多优化方案都是从设计的角度来说的,比如按需加载、动静分离、内存池,这些都是我们平时项目中很常用的一些方案。很多时候只要改善了自己代码的设计,就能对性能有很大改善。在做代码设计之前一定要想清楚哪里可能会出现性能问题,脑子里始终要有性能这个意识,结合常用的一些设计思路,就能避免做出很糟糕的影响性能的设计。

首先保证代码代码设计上没有太大问题,再结合游戏本身的特性针对性的进行优化。不同的游戏,性能瓶颈也不一样,还是需要结合具体的游戏场景具体分析,针对性的解决。当然了,我本身不是专业做游戏的,自己做游戏的经验其实也不是很多,有不正确或者不完善的地方,欢迎讨论。

推荐阅读更多精彩内容