【参考原文】Unity3D之优化(待续)
【参考原文】Unity中的优化技术
代码层面
- foreach
Mono下的foreach使用需谨慎。频繁调用容易触及堆上限,导致GC过早触发,出现卡顿现象。
特别注意的是在Update里面如果非必要,不要使用foreach。尽可能用for来代替foreach。会产生GC Alloc,说明foreach调用GetEnumerator()时候有堆内存上的操作,new和dispose - string 修改
每次使用string的时候,都要在内存里面创建一个新的字符串对象,就需要为该对象分配新的空间。
特别在循环中需要修改string对象的时候,会频繁的分配新的空间。推荐使用StringBuilder.Append等操作来处理。 - gameObject.tag
gameObject.tag会在内部循环调用对象分配的标签属性以及拷贝额外的内存,推荐使用
gameObject.CompareTag(“XXX”)来代替.tag - 使用ObjectPool对象池来避免频繁Insatnce,Destory
框架设计层面
一个相对中大型的游戏,系统非常的多。这时候合理的适时的释放内存有助于游戏的正常体验,甚至可以防止内存快速到达峰值,导致设备Crash。
目前主流平台机型可用内存:
Android平台:在客户端最低配置以上,均需满足以下内存消耗指标(PSS):
- 内存1G以下机型:最高PSS<=150MB
- 内存2G的机型:最高PSS<=200MB
iOS平台:在iPhone4S下运行,消耗内存(real mem)不大于150MB
1.场景切换时避开峰值。
当前一个场景还未释放的时候,切换到新的场景。这时候由于两个内存叠加很容易达到内存峰值。解决方案是,在屏幕中间遮盖一个Loading场景。在旧的释放完,并且新的初始化结束后,隐藏Loading场景,使之有效的避开内存大量叠加超过峰值。
2.GUI模块加入生命周期管理。
主角、强化、技能、商城、进化、背包、任务等等。通常一个游戏都少不了这些系统。但要是全部都打开,或者这个时候再点世界地图,外加一些逻辑数据内存的占用等等。你会发现,内存也很快就达到峰值。
这时候有效的管理系统模块生命周期就非常有必要。首先将模块进行划分:
1)经常打开 Cache_10;
2)偶尔打开 Cache_5;
3)只打开一次 Cache_0。
创建一个ModuleMananger 类,内部Render方法每分钟轮询一次。如果是“Cache_0”这个类型,一关闭就直接Destroy释放内存;“Cache_10”这个类型为10分钟后自动释放内存;” Cache_5”这种类型为5分钟后自动释放内存。每次打开模块,该模块就会重新计时。这样就可以有效合理的分配内存。
贴图层面
代码上的内存优化,很大层面上都不及贴图上的优化。有时候改一张图就帮你省了大几兆的内存。
1.巧妙通过调整纹理资源,来调整图的大小。比如:通过9宫格、部分缩小后Unity里在拉大等方式。
比如:(主要调整了两个小元素)就省了一半的内存。
优化前:
优化后:
2.Ios平台使用PVRT压缩纹理。Adroid平台使用ETC1格式压缩。均可以减至1/4的内存大小。优化非常明显。
目前主流的Android机型基本都支持ETC1格式压缩。但ETC1只能支持非Alpha通道的图片压缩。所以一般把Alpha通道图分离出来,绘制到GPU显存时,a值从Alpha图里获取,无Alpha通道的图就可以使用ETC1压缩。
而ETC2以上的格式压缩虽然支持含Alpha通道的图片,但是支持的机型还比较少。目前不推荐使用。
未使用ETC1压缩前的内存占用大小1024*1024的png图占用10.7M( 包含了Editor中的内存占用,以及mip map内存占用 )。
mipMap是摄像机离得远近用不同的图片,3D游戏中用内存换性能的一种有效方式。它会将大图变成若干小图,存储内存中,当摄像机离的比较远的时候,只需使用小图。
UI、2D场景可以把Texure这个设置去掉。
这样实际游戏中未压缩纹理1024×1024的图在内存中占用是 4M。(Unity Profiler下看应该是8M)
使用ETC1压缩后,场景图片一张大小只有1.3MB,加上通道图2.6M。几乎是用来的1/4。
甚至文件的大小也小了1/4。
3.通过减色的方式减少图片大小。很多UI其实使用的色彩很少,用不到256色。这类图片就可以进行减色压缩。
优化总结
更新不透明贴图的压缩格式为ETC 4bit,因为android市场的手机中的GPU有多种,
每家的GPU支持不同的压缩格式,但他们都兼容ETC格式,对于透明贴图,我们只能选择RGBA 16bit 或者RGBA 32bit。
减少FPS,在ProjectSetting-> Quality中的
VSync Count 参数会影响你的FPS,EveryVBlank相当于FPS=60,EverySecondVBlank = 30;
这两种情况都不符合游戏的FPS的话,我们需要手动调整FPS,首先关闭垂直同步这个功能,然后在代码的Awake方法里手动设置FPS(Application.targetFrameRate = 45;)
降低FPS的好处:
1)省电,减少手机发热的情况;
2)能都稳定游戏FPS,减少出现卡顿的情况。当我们设置了FPS后,再调整下Fixed timestep这个参数,
这个参数在ProjectSetting->Time中,目的是减少物理计算的次数,来提高游戏性能。尽量少使用Update LateUpdate FixedUpdate,这样也可以提升性能和节省电量。
多使用事件(不是SendMessage,使用自己写的,或者C#中的事件委托)。待机时,调整游戏的FPS为1,节省电量。
图集大小最好不要高于1024,否则游戏安装之后、低端机直接崩溃、原因是手机系统版本低于2.2、超过1000的图集无法读取、导致。
2.2 以上没有遇见这个情况。
注意手机的RAM 与 ROM、小于 512M的手机、直接放弃机型适配。
VSCount 垂直同步
unity3d中新建一个场景空的时候,帧速率(FPS总是很低),大概在60~70之间。
一直不太明白是怎么回事,现在基本上明白了。我在这里解释一下原因,如有错误,欢迎指正。
在Unity3D中当运行场景打开Profiler的时候,我们会看到VSync 这一项占了很大的比重。
这个是什么呢,这个就是垂直同步,稍后再做解释。
我们可以关闭VSync来提高帧速率,选择edit->project settings->Quality。在右侧面板中可以找到VSync Count,把它选成Don’t Sync。
这就关闭了VSync(垂直同步),现在在运行场景看看,帧速率是不是提高很多。
现在来说说什么是垂直同步,要知道什么是垂直同步,必须要先明白显示器的工作原理,
显示器上的所有图像都是一线一线的扫描上去的,无论是隔行扫描还是逐行扫描,
显示器都有两种同步参数——水平同步和垂直同步。
什么叫水平同步?什么叫垂直同步?
垂直和水平是CRT中两个基本的同步信号,水平同步信号决定了CRT画出一条横越屏幕线的时间,
垂直同步信号决定了CRT从屏幕顶部画到底部,再返回原始位置的时间,
而恰恰是垂直同步代表着CRT显示器的刷新率水平。
为什么关闭垂直同步信号会影响游戏中的FPS数值?
如果我们选择等待垂直同步信号(也就是我们平时所说的垂直同步打开),
那么在游戏中或许强劲的显卡迅速的绘制完一屏的图像,但是没有垂直同步信号的到达,
显卡无法绘制下一屏,只有等85单位的信号到达,才可以绘制。
这样FPS自然要受到操作系统刷新率运行值的制约。
而如果我们选择不等待垂直同步信号(也就是我们平时所说的关闭垂直同步),那么游戏中作完一屏画面,
显卡和显示器无需等待垂直同步信号就可以开始下一屏图像的绘制,自然可以完全发挥显卡的实力。
但是不要忘记,正是因为垂直同步的存在,才能使得游戏进程和显示器刷新率同步,使得画面更加平滑和稳定。
取消了垂直同步信号,固然可以换来更快的速度,但是在图像的连续性上势必打折扣。
这也正是很多朋友抱怨关闭垂直后发现画面不连续的理论原因。
合并材质球
unity 3d中每倒入一次模型就多一个材质球,可我的这些模型都是共用一张贴图的就想共用一个材质球,所以每次都要删除再附上,很麻烦。怎么才能合并这些材质球?
采用TexturePacking吧
1、遍历gameobject,取出material,并根据shader来将material分类
2、调用Unity自带的PackTextures函数来合并每个shader分类中的material所对应的textures(PackTextures函数有缺陷,不过可以将就用)
3、根据合并的大的texture来更新原有模型的texture、material已经uv坐标值。
需要注意的是:需要合并的纹理应该是物体在场景中距离相近的,如果物体在场景中的距离较远,
则不建议合并纹理,因为这样做很有可能非但起不到优化的作用,反而降低了运行效率。
mesh合并
分为2种方式合并
1.自带的合并必须勾选静态
所有被勾选了“Static”的GameObject,其中的Mesh Filter中的mesh都会被合并到 “Combined Mesha (root: scene)” 中