随着小程序/小游戏的火爆,越来越多的游戏商家选择用新的营销方式来为自己的游戏吸粉,这也对传统前端/重构的技能广度提出了进一步的挑战。同名小游戏《雪鹰领主》是团队开发的第一款微信小游戏。由于是第一次做小游戏,在开发前我们花了不少时间研究分析了多个引擎,但还是遇到了一个大坑。接下来我们来回顾一下项目中遇到的性能问题以及对应的解决方案。
在项目接近尾声的时候,我们进行了真机测试,发现调用子域(开放数据域)的游戏场景出现严重掉帧(下文中的子域特指微信开放数据域)、平均帧率、所有道具位置渲染错误叠加在一起,无法正常体验游戏。具体情况如图所示:
(左图为正常演出;右图为冻结导致道具堆积)
经过一段时间的不断探索和测试,我们最终得出了一个比较稳定的解决方案。解决方案当然很重要,但我个人认为背后的探索和试错过程也是宝贵的经验。具体调优过程将在下面列出。
1. 优化开放数据域丢帧问题 1.1 从内部找原因
由于是我第一次使用该引擎,可能还不知道如何使用,因此我仔细阅读了子域名的实现代码,希望能找到一些线索。
这是子域的实现代码:
子域的实现很简单,就是不断的将玩家的实时成绩和好友的成绩进行对比,如果满足条件,就显示超越的好友。然后主域调用子域代码:
代码也简洁,没有什么刁钻的逻辑,除了最后一段。执行回调函数,在回调里实现删除纹理,就是正常的删除操作。没有发现任何异常,粗略搜了一下源码,发现这种方法在其他地方也经常用到。
我尝试测试这部分代码:
当我删除这部分代码后,游戏不再掉帧,但子域也消失了,这显然不符合要求。当我只删除这行代码时,发现在显示子域时,场景中同时出现了其他图片素材。我猜想可能是引擎缓存了游戏中所有的图片元素。所以在绘制子域时,还要不停地删除纹理。既然函数是高频操作,那么是不是高频操作导致了掉帧?我尝试过改用自定义定时器低频操作,但并没有任何改善。
引擎自身的实现、主域调用子域的方式、子域的实现都混杂在一起,一开始很难定位。我把代码 到了原版,然后理清了子域的逻辑和 UI 绘制代码,发现游戏场景还是很卡。推测丢帧问题和逻辑关系不大,可能是引擎导致的。
1.2 向互联网寻求帮助
有人说你遇到的问题99%别人都已经解决了,抱着这个信心我去官方论坛搜了一下,还好有人也遇到了同样的问题,悲剧的是官方还没回复这个问题,看吧:
1.3 向业内同行寻求帮助
深圳的同事最近也有开发小游戏的经历,不过没有用引擎,而是采用了其他引擎,部分子域采用原生 JS 实现。虽然我们还没有得到完整的解决方案,但我们有一个共识,主域频繁调用子域(子域本身也在不断渲染)本质上会造成卡顿。(感谢同事 DG、桂华、沙英、玉环、子聪等人解答问题)
1.4 探索+尝试
此时距离提交测试版只剩不到一周的时间,我们依然不知所措。于是我们主动向需求方说明了项目遇到的困难和我们尝试过的努力。需求方还表示如果实在不行,可以接受“取消游戏中的这个功能”作为备选方案。虽然我们有了定心丸,但教练却说“现在放弃就等于输掉整场比赛”。
回到第一阶段,我猜测可能是引擎对子域名的实现可能有问题,所以决定尝试用原生JS实现子域名(因为子域名除了可以超越友商之外,还有排名页面,考虑到重构这个的工作量以及重构之后未知的预期,所以一直没试过用原生JS重写)。
不过,我用原生 JS 写完《超越好友》的 demo 之后,在真机上进行了测试,性能确实有一定程度的提升(帧率),但还是达不到流畅游戏的标准。不过至少有一个方向,同时也有数据显示,尝试切换渲染模式可以提升性能。于是我从渲染模式和子域实现两个维度进行了组合测试,测试结果汇总如下:
渲染模式\子域名实现
引擎实现
原生 JS 实现
(默认)
经过多种组合调试,终于得到了一个令人欣慰的结果。为了确保不眼花缭乱,我立即就近找了一台测试机,让同事和需求方对新的改进方案进行测试。大家一致认可了这个方案。至此,对重写整个子域模块的恐惧也烟消云散了(我可以和 JS 划友谊小船了)。
1.5 重构开放数据领域
引擎帮助开发者解决各种设备的适配问题,但在原生 JS 中绘制时,设备尺寸与 DPR 不一致会导致最终绘制性能出现差异,为了保证原生绘制的 UI 能够与引擎绘制的主域 UI 兼容,Alex 在此处进行了封装,保证开发者可以复用原有 UI 的尺寸参数,提高重构效率。
回顾解决子域性能问题的过程,经历了自己推测、论坛/同事请教套路,再回到猜测、实践、组合验证,一路走来,不容易,但好在最后找到了合适的解决方案。后续 Alex 会把小游戏开发开源分享给有需要的同事;针对引擎测试问题,也第一时间在微信上和同事沟通、反馈,官方表示后续会联系官方团队进行优化。
2. 主域性能优化
在反复测试过程中,我们也发现游戏场景偶尔会出现掉帧的情况,通过面板监控后发现是部分异步请求导致JS执行时间过长,从而导致掉帧(熟悉性能优化的朋友都知道,小游戏的性能就是和16ms赛跑,原理我就不细说了)。
图中红色区域内的尖刺就是js执行的耗时部分,会造成掉帧。展开一下耗时部分的执行堆栈:
层层剥开之后,我们发现耗时比较大的地方是在调用微信本地缓存接口。经过和微信同事核实,这个接口是小游戏和微信底层通信用的,不同环境耗时会略有不同,平均一个peer耗时在5-10ms左右。一次本地存储的读写需要四次通信,所以前台会出现1~2次掉帧,这是底层实现决定的。基于这个不可抗拒的原则,我们优化了逻辑,尽量避免在高频场景调用本地存储。下图是优化后的性能监控图:
虽然最终通过使用原生 JS 重构子域、调整渲染方式解决了性能问题,但底层原理还需要我们深入分析引擎的源码实现。另外我们也希望微信同仁能尽快推动这方面的改进,让开发者更加专注于业务逻辑,提高开发效率。
(【奖励二维码】)