1、性能糟糕原因:帧频低,设备发热!PS:那么我们要知道为什么导致帧频低和设备发热的主要原因呢!
原因如下:
- 游戏渲染内容过多
- 渲染方式不当
- 计算量过大
- 创建大量对象没销毁
因为用的是egret,所以咋们使用不当导致底层WebGL问题忽略...(咋们按照egret的规则走就是了)
接下来就是优化的主要思路:
- 代码的呈现方式(代码的写法导致计算量过大);
- 其次,排查内存泄露;
- 降低常驻场景的
drawcall
,即游戏主场景、主界面UI ,界面窗口内容; - 资源回收机制;
- 代码的实现方式
- 同时生成的组件过多或者同时刷新的组件过多。(例如:游戏登陆成功后进入游戏主界面、游戏界面同时生成N个组件)
- 做UI时尽可能把所有的动态内容放在最上层,把图片放在下层,并将这些图片合成纹理集。
- 同时检测的东西过多。(例如:游戏中的元宝数据更新了,N个系统同时检测红点等数据)
后端同一时间传N个协议过来,前端同时处理并且渲染。(例如:同屏场景战斗中的处理分帧处理、就是为了同时处理大量数据和渲染导致性能严重下降)
逻辑帧与渲染帧分离;这个提升是比较明显的,因为很多游戏都是做30帧的,但是现在有些是60帧,所以要做一些逻辑帧与渲染帧分离,逻辑上可以是15帧,然后渲染上做60帧,那么逻辑的开销就可以少很多。
- 屏幕外存在内容,并且没有隐藏应当设置隐藏。(例如:RPG游戏中的地图块在屏幕外的需要隐藏。全屏界面可以把背后的内容隐藏,并且停止刷新数据,一些看不见的组件可以)
- 把不需要显示的特效、事件、组件进行释放销毁。(例如:打开了一个存在特效窗口,切换界面之后开不见应该停止动画并且隐藏、窗口关闭之后应该把特效进行释放、界面全局事件以及组件事件应该移除掉。)
- 动态加载和卸载资源,比如在游戏内的时候,我们可以把游戏外的一些UI图集卸载掉。
- 优化美术资源,比如合理规划图集,约定好模型的最大三角形面数,制作合理的粒子效果规范。这个可以说是游戏优化中最重要的一个,因此,技术美术在游戏开发中作用巨大。
- 降低资源质量或屏幕分辨率,这是有损优化,一般作为最后的手段。
- 网络数据包进行合包处理、分帧对回包进行处理、限制一定事件内的发包率等。
- 还有好多就不一一细数了,例如停止不必要的
Timer
,enterFrame
事件,尽可能的避免使用alpha
,适当时候使用cacheAsBitmap
等等。 - 减少渲染内容(
index.html
中egret
的启动参数:data-show-fps="true"
看drawcall
信息)
优化图集
我们项目使用 egret + eui
来开发的,有相关优化经验的应该知道,通过打图集的方式可以达到同一图集图片可以通过同一个批次绘制出来,然而 Egret
并不能像 Unity
那样通过判断 mesh
是否相同来实现卡节点合批。
例如:两个图片在同一个图集中,但在两个图片中间插入了一个文本组件,这样合批就被打断,所以尽可能的减少合批处理的打断。
文本合批
其次,基本文本使用的字体相同,但是在 egret
中,每个文本都需要单独占用一个绘制批次。要实现文字合批,只能通过自定义字体,使用图片字体的方式代替原生的字体,但这样也存在局限性,即只能针对内容变化较小的情况。
例如:一些标题和数值、文字类图片。
动静分离
动态内容与静态内容分离,把不需要改动的部分和需要经常动态改变或者特效部分分离开。
例如:游戏中需要点亮的筋脉系统可以分开,减少drawCall
。
内存泄露
主要借助 Google Chrome
浏览器调试面板中的 Heap Profiling
工具来进行排查,实现方式是通过记录当前的堆内存(heap
)快照,并生成对象的描述文件,给出当时 JS
运行所用到的所有对象、对象占用的内存大小和引用层级关系等。
在调试面板的 Memory
页签便是 Heap Profiling
工具,下面是具体的使用操作:
如何打快照:选中左侧菜单中的 Profiles
选项,在 Select profiling type
中选择第一项 Take heap snapshot
,点击 Take snapshot
点击即可开始打快照。
需要注意的是,假如在控制台中打印对象的话,也会把对象引用住,所以打快照前最好也清空一下控制台中的打印日志。此外,每次打快照之前都需要点击一下Collect garbage
按钮,保证能被回收的资源都被回收了,这样后面对比快照查询泄露才准确。
对比快照:
当打出首个快照之后,我们会进行一些游戏内的操作,例如:打开一个界面或切换一下场景,然后再关闭界面或切回原来的场景。一番操作之后,点击 Collect garbage 按钮然后打出第二个快照。现在在 HEAP SNAPSHOT
下便有两个快照:
接下来是对比快照的步骤:
- 选中快照列表中需要进行对比的一个快照;
- 右侧操作类型选择
Comparison
(对比); - 再选择要进行对比的另一个快照。
定位泄露原因在对比结果展示表中有几项数据:
New :
Deleted :
Delat :
Alloc. Size :
Freed Size :
Size Delta :
对比技巧:
- 可以直接在对比数据上方的
Class filter
中输入打开过的UI
的类名; - 假如存在,则表示
UI
关闭后仍被引用住没有被回收,展开数据可以看到引用链;
引起泄露的原因通常是对象被全局的变量引用住导致gc
时没办法自动释放掉,排查引用链时其实可以定位到具体引用住此UI
的对象和引用的代码所在的位置,下面是分析引用链的操作:
先选中对象并展开对象的引用链,在Retainers/Object
中第一个对象通常就是引用此UI
的对象:
不难看出此UI
是被gEventCenter
这个对象引用住了。
接下来排查引用代码的位置,可以将鼠标直接悬停在引用对象上,然后展开对象的fn
属性:
直接点击[[FunctionLocation]]
后面的脚本信息,即可直接打开引用对象的代码所在的位置。
小结:
在JavaScript
中提及的内存泄露,其实并非正真意义上的泄露,通常只是对象被引用住无法无法被回收而导致内存累积。这里需要注意的就是全局引用住的对象,除非是要重复使用,否则会导致对象无法被内存自动回收,从而导致占用内存一直累加。
可以利用chrome的performance工具进行性能监控,得到下图。
我们可以直观看到几个大波峰,表示这里严重占用cpu
资源。这是我们的重点解决对象。
选择其中某一段,并且滚动鼠标滑轮可以进行放大,看到里面代码的调用堆栈。
上面是白鹭的渲染逻辑,可以通过减少drawcall
来减低这部分消耗。
这是其中一个优化点。
光靠上面的图,还不能很好定位发热。
之前一直认为ITEM_CHANGE
消耗最厉害,移除了该事件后,还是比较发热。
所以我在SimpleDispatch
类里面加了时间日志,计算出每个事件的执行时间和频率,最终得出ITEM_CHANGE
、ROLE_EQM_INFO
和GAME_TIME_TICK
调用最频繁,其中ITEM_CHANGE
最消耗cpu
。所以接下来重点监控这两个事件。
existBetterEqmToWear
该函数本身耗时极大,在打开角色界面后,每次挂机获得金币或者经验都会调用一次,大概是2秒/次。所以打开角色界面后,温度很快就上去了。
由此得出第二个优化点是,降低existBetterEqmToWear
和BagModel
里的几个获取装备方法的调用频率。
GAME_TIME_TICK
时间每秒分发一次。其中调用getPower
比较频繁。
我直接全局搜索了绑定这个事件的地方,查看有没有内存泄漏的地方。
ROLE_EQM_INFO
在刚开始进入游戏的时候只调用一次,但消耗非常大。
- 解决
- 减少
drawcall
、减少事件派发 - 在频繁调用的函数里加入脏位优化算法。脏位标记算法技巧如下:
- 一组原始数据随时间变化。
- 一组衍生数据经过一些代价昂贵的操作由这些原始数据确定。
- 一个脏标记跟踪这个衍生数据是否和原始数据同步。它在原始数据改变时被设置。如果它被设置了,那么当需要衍生数据时,它们就会被重新计算并且标记被清除。否则就使用缓存的数据。
-
hashObject
调试这个hashCount是由白鹭引擎内部 API,用于统计引擎对象的创建数量。在引擎的 HashObject 的构造函数这里添加一个打印日志,对比当前hashCount跟上一个hashCount,在运行时去检查调用堆栈 通过日志发现战斗特效、伤害飘字造成频繁的创建销毁,通过内存池进行优化。
- 分帧加载
元素较多的界面、副本玩法创建较多怪物,进行游戏一次性加载过多界面时,进行分帧处理,避免同步计算造成瞬间掉帧严重。
资源回收
对于游戏中不同的资源进行记录使用和管理。例如区分开什么资源需要即使释放、什么资源需要做进行定时管理、使用引用次数管理等等。当然,主动调用资源回收能降低内存占用,但回收太过频繁反而会导致发烫更严重。
四、egret性能优化之优化渲染
egret
在内核中是如何来处理渲染部分的
MainLoop
——> EnterFrame
(帧事件) ——> clear
——> stageUpdateTransForm
——> stageDraw
1、 Egret每刷新一帧的时候,会执行四步操作。
- 执行一次
EnterFrame
,此时,引擎会执行游戏中的逻辑。并且抛出EnterFrame
事件。如果在这里编写了大量消耗性能的代码,那么游戏的帧频就会开始下降。 - 引擎会执行一个
clear
,将上一帧的画面全部擦除。 -
Egret
内核会遍历游戏中所有的DisplayObject
,并重新计算所有显示对象的transform
;visible = false
的显示对象也会参加计算。 - 将所有的图像全部
draw
到画布中。
2、了解了egret的渲染机制,优化游戏:
不想要的DisplayObject
,请removeChild
掉,如果是设置了它的visible
属性的false
,确实这个显示对象不会被渲染出来,但是,它还是会参与到第三步的计算过程。所以也无形中增加了性能的开销。如果某一个图像被其他图像遮蔽,那么你就需要移除被遮盖的对象。
太多的显示对象不仅会在第三步会消耗性能,更重要的是,在第四步的时候,会严重影响性能,让帧频下降。
可以将画面中的元素进行合并,合并不是将两个Bitmap
塞到一个Sprite
中,这样并不起作用,无论是嵌套好事并列,都会消耗大量性能。如果可以,最好调整游戏元素图片的拆分方式,尽量减少DisplayObject
数量。
使用cacheAsBitmap
,让你的矢量图在运行时以位图形式进行计算。这回大大减少你的矢量图运算。
尽量不要在EnterFrame
事件中做过多的操作,EnterFrame
事件派发太频繁了。
善用脏矩形是一种非常高效的优化手段,但它是把双刃剑。用的好,性能飙升,用不好,自取灭亡。