转载

使用Unity开发安卓游戏怎么进行性能优化

  几周前,我开始写一款叫 Sky Blocks 的游戏,使用 unity 引擎并且发布在了安卓手机上,如果你有时间可以在 Google Play 上下载体验一下。

  在写这个游戏的过程中,我遇到的问题大部分都是性能方面的。下面我来介绍一下这款游戏,以及性能问题的解决方案。

  这款游戏混合了《俄罗斯方块》和《太空入侵者》这两个游戏的玩法。玩家将方块尽量的摆成一条线,方块会从下往上运动,最后停在屏幕的最顶端,但是不会想《俄罗斯方块》那样连成线以后就立即消失。你有 60 秒的时间摆出尽量多的线,时间一到就会有 UFO 入侵你完成的防御工事,一旦它们将你的防御都摧毁以后就会攻打地球,当地球的生命值为零游戏就结束了。

  这游戏听上去挺简单的,但要做好还得下些功夫,不过,这样才有意思,非常有意思!

  写代码前一定要计划好

  写代码之前一定要计划好,这非常重要。我在开始开发 Sky Blocks 之前就没有想好要做什么,没有考虑清楚游戏的玩法。在正式开发 Sky Blocks 之前我走了一些冤枉路,我用 JavaScript 和 HTML5 写了一个《俄罗斯方块》游戏,后来又用 C# 将其移植到 Unity,但是在移植的过程中没有考虑二维和三维的区别,仅仅是复制粘贴的法,遇到碰撞检测的 bug 也只是做了小幅的调整。

  因为没有简单的在每次刷新时更新整个游戏面板,所以我将整个面板都布满一个最简单的方块,这些方块用来作为背景网格定位。每次网格刷新时都会将所有方块销毁然后重新实例化,对于我来说,这样做已经很不错了,在电脑上运行的很流畅。

  一般,一个面板上会有 20 行 10 列的网格,有可能会不断地有 200 个背景方块需要渲染、销毁、重建。另外,还会有不断增加的防御线(10 个方块每行),每个方块都会有自己的材料,每个材料都会绘制。假如,一个游戏面板上有 150-200 个方块需要渲染,那么,就会调用大约 200 次的绘制函数。

  如果我在动手之前有明确的计划,我就会发现这么做游戏是不会运行太久,有可能就会在一开始就省去我大部分时间。

  解决

  在动手之前画一个草图是一个好主意,这样就可以通过整体分析找到哪些内容是不变的?这些内容有什么不同?在 Sky Blocks 这游戏中是游戏面板、防御线和 UFO。

  游戏面板用来控制游戏,包括可移动的方块和已经锁定住的静态方块。

  防御线由 10 个方块组成并排在一条线上。UFO 都是由网格组成,它们可以在防御线上面移动。抓住这几点,我就接近成功了!

  减少绘制函数调用

  我之前也提到过,我的实现方式会大量调用绘制函数。当在安卓设备(三星 galaxy S4)上运行游戏,随着游戏内容越来多,我发现游戏变得越来越卡了。    为了让游戏运行流畅,我首先考虑减少绘制函数的调用次数。我在网上查相关资料,绘制函数会消耗多少资源?怎么造成的?该怎么优化?    意识到效率问题,我不能出一个好的测试方案,找到一些测例会对 CPU 和 GPU 造成严重的压力就行。一些测例没有太大的影响,但有些则会让我的帧率变得很低。

  影响效率的问题还是比较容易发现,其根源就是不同的方块有不同的材质。

  要减少绘制函数的调用就得减少 object 的数量和材质数量。

  因此,我写了一段代码用来生成一个网格,这个网格就可以替代原来有 200 个立方体组成的游戏面板。然后,我用顶点着色的方法替代贴图方法。最后,我将材质的着色器换成在网上找到的一个不发光的顶点着色。现在我将所有重复的绘制函数调用都减少为只调用一次!

  对于防御线的处理,我用了类似的方法。我将所有材质都替换成了不发光的顶点着色,我没将它们用一个网格代替,而是保留了 10 个立方体,因为,替换了材质就已经不再多次调用绘制函数了。

  非常遗憾,我事先没有准备,所以这里没有提供优化前后的区别对比图片。下面是一个已经优化好的游戏截图:

使用Unity开发安卓游戏怎么进行性能优化

  上图目前这个方块只调用了一次绘制函数(Batches:20、SetPass calls:20),之前提到每个方块由4-5 个立方体组成,这个方块就处理了 4 次。

使用Unity开发安卓游戏怎么进行性能优化

  现在,已经放置了两个方块,但处理次数只增加了一次。若是之前的版本,每激活一个方块中的一个立方体都会处理一次。

使用Unity开发安卓游戏怎么进行性能优化

  防御线也是同一种模式,但实际上有 10 个四边形,它们采用顶点着色法并且共享材质。这个例子中,我们没必要将防御线做成一个网格来绘制,因为,unity 会通过“Saved by batching”来自动处理这个问题。

  UFO 比较棘手。每一个 UFO 都由上中下三块网格组成,因为,我想让 UFO 的外观是随机组成的。UFO 的每一部分包含3-4 个材质,那么一个 UFO 很有可能就会调用 12-17 次绘制函数,而实际上会调用 17-30 次。会有2-3 个 UFO 同时出现在屏幕上,那么就会调用 50-100 次绘制函数,该死!

使用Unity开发安卓游戏怎么进行性能优化

  对于这点,我迫切的想要减少绘制函数的调用次数,所以我在网上找到了一个脚本,它可以将这些网格合并为一个。但是这个脚本不可以处理材质的问题,于是我最终将 UFO 都着成一个颜色,这样 UFO 变得非常丑。我手动改了一下那个脚本,现在 UFO 可以绘制最多 2 种颜色了。来看看行得通吗?现在只调用了 30 次绘制函数,但是我也不确定运行效率是否真的有所提升,不过至少我将每个 UFO 绘制函数的调用次数减少到了 10 次以下。

使用Unity开发安卓游戏怎么进行性能优化

  减少绘制函数的调用次数是不是适合所有的情况呢?当然不是。如果你可以减少当然最好,但是对于有些非常棘手的部分,只有通过牺牲显示效果来提升效率。

  现在,对于绘制函数调用次数的优化,我已经从 150-200 次减少到了 75-90 次,还是很不错的!

  最后,轮到 UFO 发射的激光,它们也都是单独的材质,当 UFO 进入狂暴状态时,绘制函数需要调用 30-40 次。非常幸运,这次只需要建立一个原始的四边形,它使用不发光的顶点着色法,这样,所有的激光只需要调用一次绘制函数,即使是 UFO 都进入了狂暴状态。

  现在,游戏中所有的绘制函数减少到了 30-45 次,不错不错!剩下的就是 UI 中的绘制函数了,但是 UI 的处理我没有找到好的办法,不过我还是非常满意现在的效率了,现在游戏比之前运行得流畅许多。

使用Unity开发安卓游戏怎么进行性能优化

  UFO 还是占了一部分绘制函数,但是考虑到已经从 30 减少到了不到 10 次,还是有很大进步。

  一个首要的原则就是使用尽量少的材质,如果可以,最好共享材质而不是每一个都实例化,这样,会减少很多绘制函数调用次数。

  资源只加载一次

  在我的一些代码中,我使用了一些 Resource.Load 函数,但是由于 Unity 不会将已经加载的资源缓存下了,所以游戏运行中就会反复加载资源,这样非常消耗效率。我之前就会把激光的材质反复加载,这样虽然不会在 PC 上有什么影响,但是在安卓设备上就不行了。

  为了避免反复加载游戏资源,我建立了一个静态的 Dictionary(资源名称为 Key,资源为 value),当我要用某个资源的时候我会先去查询字典中是否有对应的 Key,若是没有才会加载资源,反之,我就直接用缓存的资源。

  避免多次调用 Instantiate 函数

  我没有考虑过 GameObject.Instantiate 函数的开销,在代码中大量的使用了它,我本以为这个函数的开销也就和 new 一个类对象一样,但是,我错了!在 GameObject 的新建与销毁时都比较消耗 CPU。然而,我在实现 UFO 攻击的时候就遇到了这个问题,每当 UFO 开火时都会新建一个激光束,然后再非常短的时间内又将其销毁,这样,在短短 1.5 秒内就有可能调用 20-40 次新建与销毁函数。太棒了!我终于找到 UFO 攻击时性能不高的原因了。

  解决这个问题的方法就是做一个对象池。我构建了一个空的对象池,当我需要新建激光束的时候,我会先去检查对象池中是否已经存在,如果存在,改变一下它的位置与血量就可以直接用了。

  如果对象池中没有我想要的,我就会像之前一样新建一个。当需要销毁这个对象时,我并没有直接销毁,而是又将其交还给对象池,然后将其设置为非激活状态。通过对象池的方法,在 UFO 攻击的时候 CPU 的开销降低了 25-30%!

  总结

  绘制函数开销非常大,你应该尽量减少调用次数。减少材质数量可以非常有效的减少绘制函数调用。少用不同的贴图,可以将多张贴图烘培在一个图集里面。如果是 2D 游戏就少用光照,甚至可以像我这样做,不要光照只用色彩描述就可以了。着色器不要带有参数,而是用不发光的顶点着色,我这个项目中就是这样做的。以上这些可以大大减少绘制函数的调用次数。如果你觉得这样视觉效果达不到你的要求,可以试试增加一些网格。

  Instantiate 函数非常慢,所以尽量少用它。在游戏一开始你可以将大部分经常需要用到的对象构建好放在对象池中,要用的时候将其激活。绘制函数和 Instantiate 函数并不是唯一优化对象,绘制函数销耗 CPU 和 GPU,Instantiate 函数消耗 CPU。

  如果你某个非常精细的模型或者过程非常耗时,这时减少绘制函数并不能起到什么作用。优化 CPU 的消耗是重中之重,你可以梳理代码,看看是否有大量重复执行的内容,然后想办法优化它们。

正文到此结束
Loading...