1、性能糟糕原因:帧频低,设备发热!PS:那么我们要知道为什么导致帧频低和设备发热的主要原因呢!

原因如下:
  1. 游戏渲染内容过多
  2. 渲染方式不当
  3. 计算量过大
  4. 创建大量对象没销毁
因为用的是egret,所以咋们使用不当导致底层WebGL问题忽略...(咋们按照egret的规则走就是了)
接下来就是优化的主要思路:
  1. 代码的呈现方式(代码的写法导致计算量过大);
  2. 其次,排查内存泄露;
  3. 降低常驻场景的 drawcall ,即游戏主场景、主界面UI ,界面窗口内容;
  4. 资源回收机制;
  • 代码的实现方式
  1. 同时生成的组件过多或者同时刷新的组件过多。(例如:游戏登陆成功后进入游戏主界面、游戏界面同时生成N个组件)
  2. 做UI时尽可能把所有的动态内容放在最上层,把图片放在下层,并将这些图片合成纹理集。
  3. 同时检测的东西过多。(例如:游戏中的元宝数据更新了,N个系统同时检测红点等数据)
    后端同一时间传N个协议过来,前端同时处理并且渲染。(例如:同屏场景战斗中的处理分帧处理、就是为了同时处理大量数据和渲染导致性能严重下降)
    逻辑帧与渲染帧分离;这个提升是比较明显的,因为很多游戏都是做30帧的,但是现在有些是60帧,所以要做一些逻辑帧与渲染帧分离,逻辑上可以是15帧,然后渲染上做60帧,那么逻辑的开销就可以少很多。
  • 屏幕外存在内容,并且没有隐藏应当设置隐藏。(例如:RPG游戏中的地图块在屏幕外的需要隐藏。全屏界面可以把背后的内容隐藏,并且停止刷新数据,一些看不见的组件可以)
  • 把不需要显示的特效、事件、组件进行释放销毁。(例如:打开了一个存在特效窗口,切换界面之后开不见应该停止动画并且隐藏、窗口关闭之后应该把特效进行释放、界面全局事件以及组件事件应该移除掉。)
  • 动态加载和卸载资源,比如在游戏内的时候,我们可以把游戏外的一些UI图集卸载掉。
  • 优化美术资源,比如合理规划图集,约定好模型的最大三角形面数,制作合理的粒子效果规范。这个可以说是游戏优化中最重要的一个,因此,技术美术在游戏开发中作用巨大。
  • 降低资源质量或屏幕分辨率,这是有损优化,一般作为最后的手段。
  • 网络数据包进行合包处理、分帧对回包进行处理、限制一定事件内的发包率等。
  • 还有好多就不一一细数了,例如停止不必要的TimerenterFrame事件,尽可能的避免使用alpha,适当时候使用cacheAsBitmap等等。
  • 减少渲染内容(index.htmlegret 的启动参数: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 点击即可开始打快照。

themeleaf 性能优化 egret性能优化_帧频

需要注意的是,假如在控制台中打印对象的话,也会把对象引用住,所以打快照前最好也清空一下控制台中的打印日志。此外,每次打快照之前都需要点击一下Collect garbage按钮,保证能被回收的资源都被回收了,这样后面对比快照查询泄露才准确。

对比快照:

当打出首个快照之后,我们会进行一些游戏内的操作,例如:打开一个界面或切换一下场景,然后再关闭界面或切回原来的场景。一番操作之后,点击 Collect garbage 按钮然后打出第二个快照。现在在 HEAP SNAPSHOT 下便有两个快照:

themeleaf 性能优化 egret性能优化_帧频_02

接下来是对比快照的步骤:
  1. 选中快照列表中需要进行对比的一个快照;
  2. 右侧操作类型选择 Comparison (对比);
  3. 再选择要进行对比的另一个快照。
定位泄露原因在对比结果展示表中有几项数据:
  • 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工具进行性能监控,得到下图。

themeleaf 性能优化 egret性能优化_themeleaf 性能优化_03

我们可以直观看到几个大波峰,表示这里严重占用cpu资源。这是我们的重点解决对象。
选择其中某一段,并且滚动鼠标滑轮可以进行放大,看到里面代码的调用堆栈。

themeleaf 性能优化 egret性能优化_数据_04

上面是白鹭的渲染逻辑,可以通过减少drawcall来减低这部分消耗。

这是其中一个优化点。

光靠上面的图,还不能很好定位发热。

之前一直认为ITEM_CHANGE消耗最厉害,移除了该事件后,还是比较发热。

所以我在SimpleDispatch类里面加了时间日志,计算出每个事件的执行时间和频率,最终得出ITEM_CHANGEROLE_EQM_INFOGAME_TIME_TICK调用最频繁,其中ITEM_CHANGE最消耗cpu。所以接下来重点监控这两个事件。

themeleaf 性能优化 egret性能优化_UI_05


existBetterEqmToWear该函数本身耗时极大,在打开角色界面后,每次挂机获得金币或者经验都会调用一次,大概是2秒/次。所以打开角色界面后,温度很快就上去了。

由此得出第二个优化点是,降低existBetterEqmToWearBagModel里的几个获取装备方法的调用频率。

GAME_TIME_TICK时间每秒分发一次。其中调用getPower比较频繁。

我直接全局搜索了绑定这个事件的地方,查看有没有内存泄漏的地方。

ROLE_EQM_INFO在刚开始进入游戏的时候只调用一次,但消耗非常大。

  • 解决
  1. 减少drawcall、减少事件派发
  2. 在频繁调用的函数里加入脏位优化算法。脏位标记算法技巧如下:
  • 一组原始数据随时间变化。
  • 一组衍生数据经过一些代价昂贵的操作由这些原始数据确定。
  • 一个脏标记跟踪这个衍生数据是否和原始数据同步。它在原始数据改变时被设置。如果它被设置了,那么当需要衍生数据时,它们就会被重新计算并且标记被清除。否则就使用缓存的数据。
  1. hashObject调试
    这个hashCount是由白鹭引擎内部 API,用于统计引擎对象的创建数量。在引擎的 HashObject 的构造函数这里添加一个打印日志,对比当前hashCount跟上一个hashCount,在运行时去检查调用堆栈 通过日志发现战斗特效、伤害飘字造成频繁的创建销毁,通过内存池进行优化。
  2. 分帧加载
    元素较多的界面、副本玩法创建较多怪物,进行游戏一次性加载过多界面时,进行分帧处理,避免同步计算造成瞬间掉帧严重。
资源回收

对于游戏中不同的资源进行记录使用和管理。例如区分开什么资源需要即使释放、什么资源需要做进行定时管理、使用引用次数管理等等。
当然,主动调用资源回收能降低内存占用,但回收太过频繁反而会导致发烫更严重。

四、egret性能优化之优化渲染
  1. 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事件派发太频繁了。
善用脏矩形是一种非常高效的优化手段,但它是把双刃剑。用的好,性能飙升,用不好,自取灭亡。