在我国,移动游戏曾经倒退成为千亿规模的产业,近十年我国游戏产量呈井喷式增长。随着游戏行业的倒退,游戏数量越来越多,玩家数量越来越多,面对诸多的游戏产品,玩家选择面也很多,于是玩家对游戏内容和品质提出了新的要求。
通常,一款游戏是否胜利,是否留住玩家,最间接的因素就是美术、玩法、性能。美术间接关系到玩家会不会下载、会不会关上游戏试玩;玩法则是关系到你的游戏产品是否是“见光死”,玩家是否会静下心来的玩。性能则是关系到游戏体验,能不能因为游戏加载慢、卡顿、解体等问题间接让玩家败兴。明天重点谈谈对于性能的问题。
作为一个游戏开发者,接触最多的就是游戏应用逻辑,游戏引擎层面的货色咱们能做出的优化和魔改的中央很少,毕竟很少人有更好的解决方案来优化一款高度商业化的游戏引擎。所以咱们能做的就是在应用层对游戏做优化,上面解说一些相干的问题:
一、游戏资源
在 2D 游戏中游戏资源以图片、动画、字体为主,3D 游戏资源以模型、贴图、光照数据以及 UI 用到的 2D 游戏资源。游戏中应用游戏资源可分为三个阶段:加载、应用 / 显示、卸载。
1. 加载:
玩家下载一款手机游戏,玩家带着冀望关上游戏。游戏通常在启动阶段会预加载绝大部分必要的游戏资源,这样做的益处是进入游戏后一旦须要加载资源,间接就在内存里读取流式资源数据,然而代价就是启动的时候变得迟缓,甚至卡顿。如果这时候手机加载资源黑屏几秒,那肯定是很蹩脚的事件。
绝大多数游戏产品在上线初期这种状况是不足为奇,解决这种景象的罕用办法是设置一个启动屏,启动屏能够是一个独自的场景,也能够是一个独自的层,不过我倡议应用独自的场景来做,这是因为这样的启动场景须要的资源少,加载快,这部分的资源在切换场景的时候通常能够间接开释掉。在加载进度的时候设置进度条实时反馈游戏正在解决的进度,甚至能够展现一些动画和游戏中须要理解的信息,有条件最好采纳异步加载的形式,转移玩家的注意力,加强玩家的体验。
加载资源的程序应该遵循先大后小,因为在引擎底层首先是将文件数据读进内存,而后是内存拷贝到咱们的游戏资源缓存区,这会使内存在某一瞬间增大一倍
2. 应用 / 显示
游戏资源通常会反复利用,例如一张游戏中金币的图标会在很多个界面呈现,一个角色动画能够在多个场景里呈现,当不须要展现这些内容的时候,尽量不要开释掉这些游戏对象,而是把这些资源用一个对象池储存起来,采纳工厂模式进行存取,管制好资源对象的生命周期。
通常场景切换是无缝连接的,这就要求下一个场景资源加载实现行将显示的时候再开释上一个场景的资源。这样就会呈现一个问题,两个场景之间至多会有一帧是重叠的,那这一帧的内存就是两个场景的资源所占内存之和。也就是说场景切换可能霎时让内存翻倍,硬件不好的手机可能间接就解体了。通常做法也是做一个过渡场景。原理如下图:
显著上图所示,采纳过渡场景会大大减少内存开销。
3. 卸载
游戏引擎具备主动治理生命周期和内存的设计,大大减少了开发难度,从而开发者不须要关怀内存开释问题,但这不是相对的,长时间的游戏过程也会呈现脱离了引擎追踪的碎片式内存,所以有必要采纳第三方工具来定时清理这些“不受管制”的内存片段,以 coco2dx-lua 为例,collectgarbage 办法提供了定时清理内存的性能。卸载资源通常咱们只卸载了游戏中援用的资源,仅仅这样是不够的,因为这些资源尽管没有被援用,然而仍然贮存在内存区,所以有必要将这段内存开释掉。
二、渲染
在 3D 游戏中,渲染过程的计算量更大,性能优化显得更为重要。以空幻引擎为例,游戏帧率很低,甚至有卡顿的景象,可能起因是各种各样的,比方是不是人物太多了或者渲染的货色太多了,这样猜没有依据不能妄下结论,可能会节约很多工夫还找不到后果,当然如果也有可能很快就找到了但这不是解决问题的办法,这个时候咱们能够借助第三方工具来查找性能问题。调试性能的第三方工具本人自行百度一下,包含调试 CPU 性能和 GPU 性能。
查找性能问题能够别离从 CPU 和 GPU 下手,启动你的非 Debug 版本游戏程序,在控制台输出 Stat unit 命令行, 能够看到如下的显示:
这里解释一下,Frame 显示从逻辑到渲染实现一帧所花的总工夫。Game 是逻辑线程的工夫开销,Draw 是渲染线程的工夫开销,GPU 是从这一帧开始到完结的工夫开销。能够看到上图中逻辑线程所用工夫占用了总工夫的大部分,由此推断性能瓶颈呈现在了逻辑线程上。
渲染线程上,适当设置场景中物体的 LOD、可见间隔,反面剔除,少应用半透明的对象都能够明显降低性能耗费。Shader 里适当应用曲面细分,缩小不必要的或者反复的像素渲染,敞开不必要的物体倒影,也能大大降低性能耗费。
在光照零碎中,动静光照的确能取得更好的光照成果,这给硬件的性能带来微小的压力。对于大场景中的光照成果,能够事后烘焙光照,将光照数据预存下来,应用的时候间接从文件读取。光照局部内容网上很多,在 3D 游戏中是很有必要思考的内容。
三、算法
这里的算法次要是指在游戏逻辑中的算法,引擎层的算法不须要咱们去批改,在游戏业务逻辑中抉择高效的算法就曾经足够撑持应用程序的性能了。
- 应用适合的排序算法。
- 应用适合的寻路算法。
- 防止应用除法。
- 防止应用开根号 sqrt 算法,尤其要防止开根号再倒数。
- 防止在 update 办法里频繁更新,特地是在渲染线程里,最好是监听到变动再更新。
-
代码中用到的容器首选高效的 map,set,array。
四、内存治理
应用程序在启动的时候都应该申请一段内存空间,以供应用程序在整个程序过程中应用,如果内存变大超过了申请的内存空间,记得要被动申请更大的内存空间。为了防止应用程序呈现解体,卡死等景象。解决内存透露,内存溢出,野指针,空指针 / 援用尤其重要。
- 内存透露:这里思考须要手动治理的状况,在内存块应用完结的时候应该显示开释,以 C ++ 为例,类中的成员应该在析构函数中被显示开释内存(堆内存)
- 内存溢出:这种状况通常产生在递归函数的递归深度太大,或者递归没有完结条件,导致函数有限申请指令内存和占内存。该当防止这种状况产生,适当设置完结条件。如果失常申请内存呈现溢出,这就是大问题了,可能是硬件内存的确太小,或者零碎出了问题。
- 野指针:以 C ++ 为例,P 指向一段内存 A,在某个时候,你将 P 的指向批改为指向内存 B。这时候 A 内存块并没有被开释,而且它所对应的指针也无从通晓,这段内存就成了一段无法控制的内存,如果多处呈现这种状况,那你程序的内存就莫名其妙的就增大,性能就降落了。只有在重启利用或者重启零碎的时候能力将这段内存开释掉。
- 空指针:当你应用某个对象或者每个指针的时候,尽量先做空判断,否则程序就会出错。当然如果你有信念保障它不会为空,也能够不做判断。
五、解体 / 卡顿 /ANR/OOM/ 启动慢等信息收集
收集这些数据能够应用第三方 SDK 来实现,罕用的有 Testin 云测,阿里的 TokenData 等等。这些 SDK 提供了相当齐备的各种日志的收集,收集线上应用程序即时信息。
以上就是自己多年程序经验总结的程序优化和解决方案,分享参考,匆匆下笔草草写下,如有有余,欢送指教。
作者:张鹏程
一个狂热的游戏程序开发者