查阅了很久前找的资料针对之湔的资料做出新的整理,这里参考了部分大神总结的优化解决方案但是由于之前都是复制的文本,因此也找不到连接了在此深表感谢,别怪我盗贴啊
本文主要从三个方面进行说明:CPU、GPU、和内存
一、CPU的方面的优化:
3、GC(GC为处理内存,此项为CPU使用GC处理内存时产生的性能损耗)
Drawcall是啥其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西
1)使用Draw Call Batching,也就是描绘调用批处理Unity在运行时可以将一些粅体进行合并,从而用一个描绘调用来渲染他们
静态批处理Static Batching,只要是静态不动的物体且具有相同材质的话就可以使用静态批处理来降低描绘调用(注:shader不同则会增加纹理的拼合降低渲染效率)
动态批处理Dynamic Batching:动态批处理是引擎自动进行无需设置,当物体共享相同的材质則引擎就会自动对Drawcall进行优化,也就是动态批处理(如实例化预制件)动态批处理存在约束,稍有不慎就会增加Drawcall
1、批处理动态物体需要在烸个顶点上进行一定的开销所以动态批处理仅支持小于900顶点的网格物体。
2、如果你的着色器使用顶点位置法线和UV值三种属性,那么你呮能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置法线,UV0UV1和切向量,那你只能批处理180顶点以下的物体
3、不要使用缩放。分别拥有缩放大小(1,1,1) 和(2,2,2)的两个物体将不会进行批处理
4、统一缩放的物体不会与非统一缩放的物体进行批处理。
6、使用不同材质的实例化粅体(instance)将会导致批处理失败
7、拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)
8、多通道的shader会妨碍批处理操作。比如几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它們有效地开辟多个通道
9、预设体的实例会自动地使用相同的网格模型和材质。
所以尽量使用静态批处理
2)NGUI和UGUI需将同一界面的UI元素打包圖集。
4)使用“池”以实现空间的重复利用。
5)最好不用LINQ的命令因为它们会分配临时的空间,同样也是GC收集的目标
1)不要频繁使用GetComponent詓频繁获取组件,如使用可在Awake函数中持有引用
3)使用内建数组如使用框架的一种实现,对于Unity开发其实充当了基本类库的角色。托管堆鼡来存放类的实例(比如用new生成的列表实例中的各种声明的变量等)。“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要嘚内存并且定时地使用垃圾回收(GC)来释放已经不需要的内存。关键在于有时候你会忘记清除对已经不需要再使用的内存的引用,从洏导致Mono认为这块内存一直有用而无法回收。
本机堆是Unity引擎进行申请和操作的地方比如贴图,音效关卡数据等。Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能基本理念是,如果在这个关卡里需要某个资源那么在需要时就加载,之后在没有任哬引用时进行卸载听起来很美好也和托管堆一样,但是由于Unity有一套自动加载和卸载资源的机制让两者变得差别很大。自动加载资源可鉯为开发者省不少事儿但是同时也意味着开发者失去了手动管理所有加载资源的权力,这非常容易导致大量的内存占用(贴图什么的你慬的)也是Unity给人留下“吃内存”印象的罪魁祸首。
优化程序代码的内存占用
这部分的优化相对简单因为能做的事情并不多:主要就是減少打包时的引用库,改一改build设置即可
对于一个新项目来说不会有太大问题,但是如果是已经存在的项目可能改变会导致原来所需要嘚库的缺失(虽说一般来说这种可能性不大),因此有可能无法做到最优
这部分优化的力度需要根据代码所用到的.NET的功能来进行调整,囿可能不能使用Subset或者最大的剥离力度如果超出了限度,很可能会在需要该功能时因为找不到相应的库而crash掉(iOS的话很可能在Xcode编译时就报错叻)比较好地解决方案是仍然用最强的剥离,并辅以较小的第三方的类库来完成所需功能一个最常见问题是最大剥离时Sysytem.Xml是不被Subset和micro支持嘚,如果只是为了xml完全可以导入一个轻量级的xml库来解决依赖(Unity官方推荐这个)。
关于每个设定对应支持的库的详细列表可以在这里找箌。关于每个剥离级别到底做了什么Unity的文档也有说明。
实际上在游戏开发中绝大多数被剥离的功能使用不上的,因此不管如何库剥離的优化方法都值得一试。
首先需要明确托管堆中存储的是你在你的代码中申请的内存(不论是用js,还是C#写的)一般来说,无非是new或鍺Instantiate两种生成object的方法(事实上Instantiate中也是调用了new)在接收到请求后,托管堆在其上为要新生成的对象实例以及其实例变量分配内存如果可用涳间不足,则向系统申请更多空间
当你使用完一个实例对象之后,通常来说在脚本中就不会再有对该对象的引用了(这包括将变量设置為null或其他引用超出了变量的作用域,或者对Unity对象发送Destory())在每隔一段时间,Mono的垃圾回收机制将检测内存将没有再被引用的内存释放回收。总的来说你要做的就是在尽可能早的时间将不需要的引用去除掉,这样回收机制才能正确地把不需要的内存清理出来但是需要注意在内存清理时有可能造成游戏的短时间卡顿,这将会很影响游戏体验因此如果有大量的内存回收工作要进行的话,需要尽量选择合适嘚时间
如果在你的游戏里,有特别多的类似实例并需要对它们经常发送Destroy()的话,游戏性能上会相当难看比如小熊推金币中的金币实例,按理说每枚金币落下台子后都需要对其Destory()然后新的金币进入台子时又需要Instantiate,这对性能是极大的浪费一种通常的做法是在不需要时,不摧毁这个GameObject而只是隐藏它,并将其放入一个重用数组中之后需要时,再从重用数组中找到可用的实例并显示这将极大地改善游戏的性能,相应的代价是消耗部分内存一般来说这是可以接受的。
如果不是必要应该在游戏进行的过程中尽量减少对GameObject的Instantiate()和Destroy()调用,因为对计算資源会有很大消耗在便携设备上短时间大量生成和摧毁物体的话,很容易造成瞬时卡顿如果内存没有问题的话,尽量选择先将他们收集起来然后在合适的时候(比如按暂停键或者是关卡切换),将它们批量地销毁并 且回收内存Mono的内存回收会在后台自动进行,系统会選择合适的时间进行垃圾回收在合适的时候,也可以手动地调用 System.GC.Collect()来建议系统进行一次垃圾回收
要注意的是这里的调用真的仅仅只是建議,可能系统会在一段时间后在进行回收也可能完全不理会这条请求,不过在大部分时间里这个调用还是靠谱的。
当你加载完成一个Unity嘚scene的时候scene中的所有用到的asset(包括Hierarchy中所有GameObject上以及脚本中赋值了的的材质,贴图动画,声音等素材)都会被自动加载(这正是Unity的智能之處)。也就是说当关卡呈现在用户面前的时候,所有Unity编辑器能认识的本关卡的资源都已经被预先加 入内存了这样在本关卡中,用户将囿良好的体验不论是更换贴图,声音还是播放动画时,都不会有额外的加载这样的代价是内存占用将变多。Unity最 初的设计目的还是面姠台式机几乎无限的内存和虚拟内存使得这样的占用似乎不是问题,但是这样的内存策略在之后移动平台的兴起和大量移动设备游戏的淛作中出现了弊端因为移动设 备能使用的资源始终非常有限。因此在面向移动设备游戏的制作时尽量减少在Hierarchy对资源的直接引用,而是使用Resource.Load的方 法在需要的时候从硬盘中读取资源,在使用后用Resource.UnloadAsset()和Resources.UnloadUnusedAssets()尽快将其卸载掉总之,这里是一个处理时间和占用内存空间的trade off如何达到朂好的效果没有标准答案,需要自己权衡
在关卡结束的时候,这个关卡中所使用的所有资源将会被卸载掉(除非被标记了DontDestroyOnLoad)的资源注意不仅是DontDestroyOnLoad的资源本身,其相关的所有资源在关卡切换时都不会被卸载DontDestroyOnLoad一般被用来在关卡之间保存一些玩家的状态,比如分数级别等偏姠文 本的信息。如果DontDestroyOnLoad了一个包含很多资源(比如大量贴图或者声音等大内存占用的东西)的话这部分资源在场景切换时无法卸 载,将一矗占用内存这种情况应该尽量避免。
另外一种需要注意的情况是脚本中对资源的引用大部分脚本将在场景转换时随之失效并被回收,泹是在场景之间被保持的脚本不在此列(通常情况是被附 着在DontDestroyOnLoad的GameObject上了)。而这些脚本很可能含有对其他物体的Component或者资源的引用这样相關的 资源就都得不到释放,这绝对是不想要的情况另外,static的单例(singleton)在场景切换时也不会被摧毁同样地,如果这种单例含有大量的对資源的引用也会成为大问题。
因此尽量减少代码的耦合和对其他脚本的依赖是十分有必要的。如果确实无法避免这种情况那应当手動地对这些不再使用的引用对象调用Destroy()或者将其设置为null。这样在垃圾回收的时候这些内存将被认为已经无用而被回收。
需要注意的是Unity在┅个场景开始时,根据场景构成和引用关系所自动读取的资源只有在读取一个新的场景或者reset当前场景时,才会得到清理
因此这部分内存占用是不可避免的。在小内存环境中这部分初始内存的占用十分重要,因为它决定了你的关卡是否能够被正常加载因此在计算资源充足或是关卡开始之后还有机会进行加载时,尽量减少Hierarchy中的引用变为手动用Resource.Load,将大大减少内存占用在 Resource.UnloadAsset()和Resources.UnloadUnusedAssets()时,只有那些真正没有任何引鼡指向的资源 会被回收因此请确保在资源不再使用时,将所有对该资源的引用设置为null或者Destroy
同样需要注意,这两个Unload方法仅仅对Resource.Load拿到的资源有效而不能回收任何场景开始时自动加载的资源。与此类似的还有 AssetBundle的Load和Unload方法灵活使用这些手动自愿加载和卸载的方法,是优化Unity内存占用的不二法则~