白鹭引擎王泽:重度H5游戏性能优化技巧标题的文章
我们的引擎架构师做某一沙龙活动的演讲速记,纯纯的干货,分享给大家。
王泽:各位开发者下午好!我叫王泽,是白鹭引擎的首席架构师。
今天给大家分享的题目是《重度H5游戏性能优化技巧》。之所以决定用这个题目,是因为我最近几周在广深一带拜访了很多使用白鹭引擎的开发者,发现特别是在广州一带,大部分开发者都在做重度H5游戏。在几周的拜访过程中,我协助许多广州的游戏开发团队进行游戏性能优化,并在在这个过程中发现很多开发者遇到的问题是非常相似的,于是我借这次开发者沙龙的机会,把这些优化技巧整理出来与广大开发者分享。
这些技巧都是我在实际项目中得到验证过的优化技巧,并且都是投入产出比很高的优化项,建议做重度H5游戏的开发者重点针对您们的游戏进行这些优化。
在开讲之前,我首先为大家回顾一下白鹭引擎的历史上几个比较重要的版本。2014年3月白鹭引擎发布了0.9版本,并产生了《围住神经猫》这一爆款。紧接着一鼓作气发布了1.0版本引入了完整的GUI,这使得开发者可以开始做具备大量复杂 UI界面的游戏,《愚公移山》就是这样的一款产品,他成功的在 2014年底就取得了每月180万流水的成绩。
接着在 2014年11月发布了 2.0版本,重点包含了白鹭引擎的工作流工具集,特别是 UI 编辑器与动画编辑器,这对游戏开发效率带来了重大提升,因此在 2014年底,使用了白鹭引擎 2.0 版本的《传奇世界H5》 成为了第一款月流水超过千万的 H5 游戏。2016年1月,白鹭发布了3.0版本,这个版本引入了 WebGL 渲染器,大幅提升了游戏渲染效率,这个版本得到了开发者的普遍欢迎,《决战沙城》更是用这个版本实现了 H5 游戏月流水三千万的成绩。
2016年底,白鹭引擎自研了一款名为《莽荒纪》的产品,并同步推出白鹭引擎4.0版本。这款游戏使用了大量的骨骼动画,对动画渲染性能和资源加载效率的要求非常高,得益于4.0版本针对这两点的优化,这款产品在绝大部分设备包括千元机上都有非常好的表现。2017年5月份,白鹭引擎发布了 5.0版本,市场上也出现了第一款月流水过亿的H5游戏——《传奇来了》。
紧接着,在微信发布小游戏后,第一时间发布了引擎5.1版本正式支持微信小游戏,《海盗来了》通过白鹭引擎的底层支持,仅用3天便将游戏从H5版本发布到微信小游戏平台,成为首款月流水过亿的微信小游戏。
回顾白鹭引擎的历史,我们就会发现,随着软硬件的不断升级以及白鹭引擎每个版本迭代的持续努力,不断的刷新着 H5游戏行业的新高度。但我们并未躺在过去的功劳簿上,仍然在持续高效率的进行版本迭代。
白鹭引擎目前按照双周迭代的节奏,同时发布5.2稳定版与5.3特性版本,如果开发者想要开发商业化游戏,可用5.2稳定版本来做;如果你想使用一些新的特性,比如说3D以及一些2D的高级优化技巧,就可以使用白鹭引擎最新的5.3版本来做。
白鹭引擎目前在H5游戏的市场占有率达到了70%。统计方法是:取样爱微游、疯狂游乐场以及QQ空间三个业内公认的头部渠道的Top30的游戏,逐一去看这些游戏采用了哪些游戏引擎。
除了H5游戏之外,白鹭引擎在微信小游戏上的市场占有率53%。这里有两种统计方法:第一种统计方法是把所有能统计到的微信小游戏都统计起来,一个一个抓包看是什么引擎做的。
通过这种方式,统计到的白鹭引擎的市场占有率高达80%,但是我认为这是有失偏颇的数据,因为能统计到的小游戏有很大一部分是因为与白鹭官方人士有微信好友关系,而这部分好友几乎都在使用白鹭引擎开发微信小游戏,所以我认为这种统计方式是并不准确的。于是我们采用了另一种更为公平的统计方法,通过相对权威的阿拉丁统计数据,把阿拉丁榜单的Top50拉出来,得出白鹭引擎市场占有率为53%。
据不完全统计,这五年中,白鹭引擎累计运转的H5游戏和微信小游戏的流水数据约为200亿。非常感谢大家对白鹭引擎的支持。今天能有这个成绩并不是完全是依靠白鹭自己做的,引擎技术并不能解决所有的问题,最大的外因是依靠各位一起努力的结果,特别是各位游戏开发者,浏览器底层技术提供方、渠道方、以及很多支持伙伴的帮助。
除了外因的帮助之外,白鹭自身也帮助开发者做了很多事情,我个人是Flash页游研发出身,白鹭引擎团队的大部分研发也都是游戏行业出身,所以白鹭的研发团队更了解也被称为“手机页游”的H5游戏开发者的痛点需求,并且这五年为各地的H5游戏开发者提供了总计1400天的驻场支持,飞到各个城市,包括广州、深圳、上海、厦门、福州、成都、武汉、南京、西安,当然还有北京。我平均基本上有三分之一的时间是不在公司里的,都在为各地的开发者解决各种各样的问题。通过这些比较务实、接地气的方式,保证白鹭引擎可以运行在尽可能多的设备上,并提升在低端机上的游戏体验。
今天广州开发者沙龙的主题是“怎样做一款赚钱的小游戏”。我认为解决技术上的短板决定了游戏是否能赚钱的下限。针对游戏运营的几个关键数据,游戏的运行性能主要影响玩家的在线时长,游戏加载性能主要影响玩家的前期留存。我今天重点介绍如何提升游戏的运行性能。
我见过的大部分寻求技术帮助的 CP 都会提及游戏性能需要提升。但是我认为性能糟糕是一个技术术语,它在产品上一般有如下四种体现方式:
1、帧频很低。
2、设备发热。
3、不定期卡顿。
虽然看起来这些问题都是性能问题,但是产生这些问题背后的原因则是完全不一样的。
在解决问题之前,需要将这些问题首先输出一个可量化的数据指标。
帧频很低可以被量化为:在特定设备上的帧频是XX帧,其中 JavaScript 逻辑开销 XX毫秒,渲染开销YY毫秒,这些数据在白鹭引擎的性能面板中都有体现。
设备发热看似是很难量化的,并不是所有操作系统都提供了设备温度的 API。因此我们向开发者推荐另一种方法作为量化方式,首先将设备充满电,然后统计游戏在 XX分钟后的剩余电量。由于耗电量和发热基本成正比,所以解决耗电问题,发热问题就也能同步得到解决。
至于不定期卡顿。一定要记录卡顿是否存在规律。比如是播放动画的瞬间?打开UI面板的瞬间?或者是毫无规律?
上述问题量化之后,接下来再来逐一地尝试解决这些问题。
帧频低和发热主要有如下四个原因:
1、渲染内容过多。
2、渲染方式不当。
3、计算开销过大。
4、 大量创建对象。
这四点又分属两个类别,分别是 JavaScript逻辑开销和引擎渲染开销。关于渲染内容和渲染方式不当最终是可以在引擎渲染层这个环节想办法解决的。而计算开销过大和大量创建对象都是在用户逻辑的JavaScript层去解决的。这两块的解决方式是完全不一样的,对渲染来说,你需要去尝试理解WebGL底层的渲染原理是什么,而对于JavaScript,你需要了解JavaScript底层的一些原理。
首先聊聊引擎渲染层面的东西:
1、渲染内容过多。在屏幕之外的内容,可以设置隐藏,不要执行渲染。这就提到一个很有意思的问题了,看似很简单优化方法为什么不在白鹭引擎内部实现呢?其实这涉及到白鹭引擎的一个核心设计理念:不要替开发者去做“自作聪明”的优化。这样才能保证优秀开发者做出更好的游戏。除了屏幕外的内容不进行渲染之外,游戏普遍有很多UI弹窗,当你打开弹窗的时候,强烈建议你把游戏背景隐藏,这同样可以节省大量的渲染开销。
2、渲染方式不当。来看看底层原理:
白鹭引擎2D是如何渲染游戏的多张纹理的? 在白鹭引擎里,2D是一次性提交所有的数据,然后设置渲染模式,执行渲染批次,再设计渲染模式,再执行渲染批次。如果你能保证渲染模式这个东西是没有发生变化的,就可以一次尽可能多地渲染,在这种情况下就可以做一次的渲染批次,这个优化听起来很简单,我说说在实际游戏里的典型案例。
这张图是我昨晚自己画的示例图。做游戏时经常会遇到这样的场景,就是有很多人、很多怪,每个人都包含了影子、模型动画、血条三个部分。最简单的渲染方式是,将一个人设置成一个 DisplayObjectContainer,这个对象有三个子对象:一个人、一个影子和一个血条,这样每个人的渲染次数就是3,进而8个人的渲染次数就是24。优化后是10,如何做这个优化?方法非常简单,就是你把所有的影子放在一个Container上,把人放在一个Container上,再把血条放在一个Container上。
由于所有影子的纹理都是一样的,所以引擎底层会自动开启批次合并,渲染次数是是1,然后渲染8个人,这8个人的纹理一般都是不一样的,所以就是8,上面就是血条的纹理也是一样的所以也是1。把这三者加起来,最终的优化结果就是从 24降低到10。
第二个示例。这是大家做的重度游戏的典型UI,DrawCall是30,这种游戏可以做很多优化,就是把所有的图片、文字合成一张纹理集。这个全做完之后,渲染批次就从30变成2,之所以不是1而是2,是因为右上角的lv888肯定是个动态文本,无法参与批次合并。
所以这就是一个简单的例子,希望大家以后做UI 时可以尝试着去把所有的动态文本都尽可能放在最上层,把图片都放在下层,并将这些图片合并成纹理集。特别是在游戏的 ListItemRenderer 之中,一般游戏中的一个 List 至少会显示 5个 ListItemRenderer,如果你能将 ItemRenderer的DrawCall降低5,那整体的 DrawCall就能降低 25,所以针对ListItemRenderer的优化是投入产出比非常高的,强烈推荐各位开发者重点优化这里。
3、计算开销过大。对骨骼动画使用缓存,优化骨骼开销;避免大量的数学计算与浮点数计算;逻辑帧与渲染帧分离。这个提升是比较明显的,因为很多游戏都是做30帧的,但是现在有些是60帧,所以要作一些逻辑帧和渲染帧的分离,逻辑上可以是15帧,然后渲染上做60帧,那么逻辑的开销就可以少很多。
4、还有一个是非常重要的大家可能不太注意的,就是大量创建对象。JavaScript虚拟机有一个特点,就是对象创建的开销远远大于对象计算的开销,并且对象创建会导致垃圾回收,而垃圾回收会导致游戏不定期卡顿,所以有一个很重要的原则就是不要在你的主循环里创建任何对象,强烈建议游戏中的人物、怪物、技能特效统统做成对象池,这样可以大幅降低游戏的不定期卡顿现象的出现。分享一个常用的测试函数。
来看这个函数的原理。它就是显示了每一秒钟去拿一个hashCount跟上一个hashCount作对比,这个hashCount是由白鹭引擎内部 API,用于统计引擎对象的创建数量。如果你的游戏静止放置不动,那么理论上hashCount diff的结果应该是0,实际上要尽可能控制在120以下,我给大家分享一个数据,我见过的最赚钱的那一批游戏的 hashCount diff 都控制在120以下的。
如果这个数字超标,应该如何去解决呢?只需要在引擎的 HashObject 的构造函数这里添加一个断点,在运行时去检查调用堆栈就可以了。
我协助优化过一款产品,它的hashCount diff数字高达4000,每秒创建4000个对象,我调试他的代码后发现,其实只是一个很小的问题导致了这个结果,花了15分钟修复之后,游戏的发热、卡顿等问题都得到了大幅的缓解。
接下来我跟大家介绍一下白鹭的3D引擎的核心功能,以及内部优化技巧,也给大家做重度游戏时以一些启发。
Egret3D内部的所有资源都采用了GLTF文件格式。这是一种对OpenGL ES、WebGL非常友好的3D内容格式标准。面向实时渲染,尽量提供可直接传输给图形API的数据格式,而不再需要反序列化。
刚刚我提到了尽量提供可直接传输给图形API的数据格式,在 Egret3D内测版本中,在3D引擎加载一个模型文件,需要首先加载了模型文件,然后解析模型文件,这就像配置文件一样。第三步要生成WebGL所需要的数据格式,最后把它提交到GPU。而在正式版本的流程变成了加载新的 GLTF文件,进而由于GLTF的文件格式和GPU想要的文件格式是几乎一样的,所以不需要解析也不需要生成,直接把它作一个简单的ArraryBuffer切割,然后提交到 GPU就可以了。
通过这个优化达到什么样的效果?模型解析速度提升170%,内存占用降低1倍,加载速度提升30%,所有这些优化的底层的本质原因是由于底层采用了GLTF的模型文件的标准范式。这就相当于是白鹭引擎3D版本的比较常见的引擎优化。
《泡泡学园OL》是白鹭自研团队打造的一款标杆品质的3D微信小游戏。在这款游戏制作过程中,不断挑战 Egret3D 与微信小游戏的性能极限极限,具体技术指标包括: 100,000 Vertex , Lightmap贴图、GPU骨骼动画,GPU粒子动画,碰撞引擎,帧同步网络通讯,基于行为树的AI 等。
这款游戏前期开发过程中使用了 Unity3D 编辑场景,然后通过白鹭引擎的 Unity3D导出插件发布到 Egret3D 中。目前已经使用白鹭引擎正在研发的 3D 编辑器进行后续开发和维护。
目前白鹭科技已经发布了 Egret3D的1.1版本,即将在9月底发布1.2版本,这个版本重点针对开发者的开发效率进行优化,首先是推出一款可视化的调试工具 Egret Inspector 3D ,其次就是将3D编辑器提供给更多开发者进行试用并收集反馈,如果您已经使用 Egret3D 立项并进入项目开发阶段,可以优先试用3D编辑器。
以上就是我为大家分享的全部内容。谢谢大家!