在刚开始做游戏的时候,在网上乱七八糟找些素材就放进去了,无论是3D还是2D,到后来都会出现各种过多渲染,内存占用太高等情况。电脑上跑跑也许问题不明显,一到手机上就漏洞百出。所以,正确处理优化是很重要的一环。
附注:以下方法针对性不统一,有些方法优化了游戏性能,增强了画面要求,但是有可能也相对增加了内存的占用。至于怎么取舍,大家根据自己项目来吧
2D—Texture
在制作2d游戏的时候,如果我们使用NGUI来制作界面,那么必然会用到打包图集,简单来说图集的优点在于能将所有的图片合成一张大图,而我们引擎渲染的时候,就只用绘制这一张图。而UGUI并没有加入大包的工作,我们可以和NGUI一样使用TexturePacker来打包图集。Unity商店里面也同样有一个免费的插件TexturePacker Importer,我们只需将打包好的图集文件添加到Unity中,就能自动帮我们将每个图片切割开,当然,也可以自己通过SpriteEditor来切割,之后便可以直接使用。
做一个测试,我们将插件中原本作为单独存在的图片加入到场景中,有多少张图,就绘制了多少次,图中左边SetPass calls为5,而将图集中的图片加入进去,同样的个数甚至更多的情况下,SetPass calls为1,在游戏中如果使用了大量UI,那么对图片的打包是必要的。
帧数
FPS是我们衡量一个游戏性能的标准。有时候我们做一个游戏,不同的设备的处理器的效果可能不一样,如果没有限制,那么达到几百也是有的。如在《英雄联盟》中,我们经常看到FPS的值几十到几百不等的都有。但是有时候,我们的游戏并不需要那么高的帧率,在手机上过高的帧也会导致游戏卡顿,耗电量高等情况。所以,一般情况下,我们可以针对FPS做一个限制。
不仅如此,我们的游戏有时候需要在后台也运行,保持暂停的情况,这时候游戏作为静止的画面,显然是不需要这么高的帧数的,那么我我们在此时可以判断在后台的情况下,设置帧率为1,减少消耗。
另外,帧率的大小也受到垂直同步的影响,垂直同步直接影响帧率,优先级高于代码,也就是说,如果设置了垂直同步,我们在代码中编写的设置垂直同步的语句可能就会失效。如垂直同步设置值为1,帧率就为60,为2,则帧率为30。
是否设置垂直同步可以在Edit-PlayerSetting-Quality中找到 V-Sync 来设置。
LOD Group
LOD Group的作用的,就是在不同的距离下显示不一样的内容。我们做一个测试,在场景中分别建立Capsule,Cube,Sphere3个物体,将Cube和Sphere作为子物体添加到Capsule下,给Capsule添加LOD Group, 然后分别对应设置。如图:
大家在场景中测试,拖动摄像机,发现不同的距离显示的是不一样的物体。那么针对这样的需求,我们在游戏中,远距离的物体就不需要高面数的模型,由此我们可以使用低模来替代,达到优化的效果。虽然有时候我们会用这个,但是LOD也会相应的增加内存。
MipMap
Mipmap类似于LOD,但LOD是针对于模型来处理,Mip是针对贴图纹理来处理。
在使用MipMap时,贴图会根据摄像机距离的远近,选择使用不同精度的贴图。但同样的,虽然MipMap可以优化显存带宽,用来减少渲染需求,但是和LOD一样会增加内存。
MipMap可以应用在跑酷游戏上,远距离与近距离的贴图可以通过这个来优化。当然,用或不用,或者怎么用,都应该根据需求来判断。
ObjectPool
使用对象池是很重要的一个点。例如,我们在做跑酷游戏的时候,永无止境的道路是不可能一来就生成好了的,也不会一边跑一边生成,这样会大大降低游戏性能。而对象池正好弥补这一点。
使用对象池的方法,简单来说就是 我们在游戏加载时便开始创建出大部分会用到的对象,于是在使用的时候,修改对象的位置让他出现在摄像机的视野中,当不需要的时候修改位置移动到视野外。
这样避免了每次生成一个物体,引擎都要去重新渲染一次。对象池的每个对象需要我们用代码来管理,这也是面向对象编程重要的一点。
Draw Call合并
合并分为静态合并和动态合并2种。
Unity为我们完成了动态操作,也就是说不需要我们手动去完成,只要使用相同材质的对象,都可以被合并,且保持旋转移动i自由。但动态合并也对模型的顶点有要求。如需要相同缩放比例,模型点数目限制,单pass的shader等。
静态合并需要我们自己操作,对于模型,我们勾选组件MeshRender上的Batching Static,也可以通过UnityEngine.StaticBatchingUtility方法来实现,具体可以查阅官网。通过各类书和资料得出的结论,最终推荐代码+动态方法完成合并。
至于为什么要合并,也是因为避免重复对相同的材质进行多次渲染,这样可以降低显卡的计算量,提高性能。
优化代码
代码也会影响性能?那是肯定的。如我们在脚本种使用刚体组件的时候,可以直接使用GetComponent<Rigidbody>()来调用刚体组件的方法和属性,但是我们没一次get的时候,或多或少也会有一点性能的消耗,因为我们在告诉我们的设备,需要去get这个组件,那么设备就会去找,找到了再拿过来,但是这是属于一次性的用法。对于这种,我们也一般会事先声明 一个 Rigidbody 的对象,将GetComponent<RIgidbody>()赋值给对象,这种方法再常用不过。虽然好像一点点看不出来什么影响,但是如果调用次数太频繁,建议使用后面的方法。
同样的道理,对于一些固定的数组,如果需要频繁遍历数组,也可以将数组的值记录下来,不必每次遍历都去获取。类似的还有Unity 中的Find(),FindChild()等方法,在多次使用对象的时候尽量避免重复获取。
还有关于for 和 foreach方法,网上已经有很多介绍了,总归一句话,update函数里面最好还是别用foreach。
光照(不完整)
光照问题是个很严肃的话题,之前弄了一个小项目,但是发现Batch和set Pass竟然最高到了300,最低也是100多,在手机上这种情况应该无法忍受。后来发现,因为游戏场景中的光照是选择的RealTime,也就是说是用的实时阴影,物体太多的时候,阴影的渲染也会很多,所以在尝试了很多方法后选择把光照弄成Baked,然后通过烘焙光照贴图来完成。这样场景中不添加光照,也能看见场景,但是缺失了阴影,至于针对阴影的解决办法,正在钻研,还没找到很好的解决方案。
总之,对于光照来说,适当选择光照方式,会对游戏性能有很大影响,要慎重选择实时阴影的光照。
如果有错或疑问,欢迎提出,我会做改正。
网友评论