做H5游戏其实比较大的一个考验就是性能问题,其它web应用的话顶多是卡一点,游戏性能一低直接就炸,什么花屏啦、黑屏啦、闪退啦,还不容易定位问题。
性能方面检测有两个工具,一个是chrome的performance面板,找出费时间的方法进行优化,找到内存泄露的地方进行修改,这方面资料也是一找一大片,但还是想推一下官方的文档:
https://developers.google.com/web/tools/chrome-devtools/rendering-tools/?hl=zh-cn
如果想进行全面系统的分析,performance面板是极好的,但是使用的时候其实也会降低部分性能的,record的时候明显感觉游戏会变卡,而且只能一段一段分析(虽然确实是建议record的片段越小越好的,这样容易定位问题)。另一个工具是stat.js,这个是three.js作者写的另外一个库,用来观测每秒帧数(FPS),在layaAir里对这个工具进行了扩展,laya.utils.Stat类提供了FPS、Sprite、DrawCall、CurMem的监听,具体见https://ldc.layabox.com/doc/?nav=zh-as-3-2-0 layaAir官方也给出了不少性能优化的方案。
下面谈几点实践中我们对内存进行的一些优化,还有一些可优化的点(因为离职前各种原因项目搁置了一段时间去支援另一个游戏项目,所以很多工作还没有做完)。
压缩素材
这个看起来简单粗暴毫无技术含量的工作,可能是所有优化里效果最好的一个方面。
- 3D素材减面数
- 一些特效渲染耗内存,抛弃之用缓动动画做
- 2D静态资源合并压缩,这里制作spritesheet的时候,laya是自带spritesheet合并并生成json映射文件的,Phaser需要找个工具制作,使用的是ShoeBox,体验还可以
减少实例数
- 减少sprite层级嵌套
- 开启静态缓存。针对一些不常改变的2D图层进行了一个整体缓存,减少sprite个数和渲染次数。
- 及时销毁对象。这个见(H5游戏构建和优化思路(一))[]。销毁需要判断下资源的使用频率,毕竟这个步骤开销比较大,频繁发生会高频率触发浏览器的垃圾回收,可能在某次垃圾回收时就出现意外,在同一个场景中,一般是把使用频率低的直接销毁,使用频率高的保存下来,仅在场景中移除。
对象池的使用
对象池其实也是一个比较好的概念,对于对象的频繁创建和销毁来说。因为如果每次创建时都实例化开销其实比较大,可以把这些对象放到对象池中,再创建时先判断对象池是不是为空,不为空则直接从对象池中取,否则创建,不过也要控制下对象池的大小,毕竟一直存在过大的话内存也会炸。
计算方面的优化
考验数学和算法时候来了,这个就要看具体场景了,很多东西都是要进行每帧计算的,所以这方面有个不错的算法的话性能提升也不止一两点,还有可以将一些计算的中间量进行缓存。
还有物理引擎本身其实计算量是很大的,所以需要谨慎使用。因为我们使用物理引擎主要需要获取速度矢量,而场景中每一个移动的物体都有一份自己的脚本,举个例子,假设场景中有10个人,A攻打B的时候,ABCDEF…都能看见B移动,而我们不必对每个人的脚本都进行物理计算位置,只要B的脚本上绑定物理物体进行计算,将计算值传到服务端再广播到其它客户端就可以了。由于物理引擎的world运行的时候,所有物理实例都在进行不断计算,所以需要减少物理实例的个数,还是以子弹为例,一个人一次只能发射一颗子弹,即使它在不断攻击别人,所以我们可以只分配给一个人一颗子弹,再次发射时只是改变子弹的位置速度等初始值。这方面的优化也是极大的一个点,至少优化后玩的时候手机没那么烫了。
代码层面的优化
之前提过计算中间量的缓存,还有可以使用闭包加快一些函数的执行速度,另外就是属性选择器的缓存,因为项目中对象真的特别多,有的属性嵌套也特别深。
谨防内存泄露
说到内存泄露分析,早上刚好看到一篇文章:
http://blog.404mzk.com/javascriptnei-cun-xie-lu-ji-fen-xi.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
动画性能优化
做动画的时候有些动画效果比较复杂,使用tween+setTimeOut/setInterval来做。也就是说一个完整的动画可能是多个图片+多个tween+多个定时器,前两者就是多个*2个实例,能重用的尽量重用,定时器一定!!!!!!!要记得及时暂停(循环动画隐藏的时候)和及时清除(场景销毁的时候)。
减少主循环的代码
道理是这么说,但是很多代码确实都是每帧都需要执行的,漏了某帧都可能出现大问题,看到过一些游戏代码设计方面的文章有提到进行行为预测、隔帧计算、数据修正等等,以减少主循环的计算量,但是感觉风险还是有的。这一块暂时没什么好的思路,唯一一点能想到的就是减少重复计算(比如之前的每个客户端都要用的数据只让一个脚本计算广播到所有客户端)。