Unity性能优化专题

2015年07月29日 13:51 0 点赞 0 评论 更新于 2025-11-21 18:45

本文将从代码层面、贴图层面和框架设计层面三个维度,分享Unity内存优化的经验。

一、代码层面

1. foreach的使用

在Mono环境下,使用foreach需谨慎。频繁调用foreach容易触及堆内存上限,导致垃圾回收(GC)过早触发,进而引发卡顿现象。特别要注意的是,在Update方法中,若非必要,应避免使用foreach,尽可能用for循环来替代。因为foreach在调用GetEnumerator()时会进行堆内存上的操作,如newdispose,从而产生GC Alloc。

2. string修改

熟悉C++的开发者应该了解,每次使用string时,都会在内存中创建一个新的字符串对象,需要为其分配新的内存空间。特别是在循环中需要修改string对象时,会频繁分配新的空间。此时,推荐使用StringBuilder.Append等操作来处理字符串。在C++中,通常也是通过分配一个固定的字符内存来处理字符串操作。

3. gameObject.tag的替代

gameObject.tag会在内部循环调用对象分配的标签属性,并拷贝额外的内存。因此,推荐使用gameObject.CompareTag("XXX")来替代.tag操作。

4. 使用ObjectPool对象池

使用ObjectPool对象池来管理对象,可以避免频繁的InstantiateDestroy操作,从而减少内存开销。

二、贴图层面

代码层面的内存优化效果往往不及贴图层面的优化。有时候,对一张贴图进行优化就能节省几兆的内存。

1. 调整纹理资源

可以通过一些巧妙的方法来调整纹理资源,从而改变图的大小。例如,使用9宫格、部分缩小后在Unity里再放大等方式。曾有案例,主要调整了两个小元素,就节省了一半的内存。

2. 选择合适的压缩格式

  • iOS平台:使用PVRT压缩纹理。
  • Android平台:使用ETC1格式压缩。这两种压缩方式均可以将纹理内存大小减至原来的1/4,优化效果非常明显。目前主流的Android机型基本都支持ETC1格式压缩,但ETC1只能支持非Alpha通道的图片压缩。所以,一般将Alpha通道图分离出来,在绘制到GPU显存时,从Alpha图中获取a值,无Alpha通道的图则可以使用ETC1压缩。而ETC2以上的格式压缩虽然支持含Alpha通道的图片,但支持的机型较少,目前不推荐使用。

下面以一张1024*1024的png图为例说明压缩前后的内存占用情况:

  • 未压缩前:该图片占用10.7M内存(包含了Editor中的内存占用以及mip map内存占用)。mipMap是在3D游戏中用内存换性能的一种有效方式,它会将大图变成若干小图存储在内存中,当摄像机离得较远时,只需使用小图。对于UI、2D场景,可以去掉Texure的mipMap设置。这样,在实际游戏中,未压缩的1024×1024的图在内存中占用4M(在Unity Profiler下查看应该是8M)。
  • 使用ETC1压缩后:场景图片一张大小只有1.3MB,加上通道图为2.6M,几乎是原来的1/4,甚至文件大小也缩小了1/4。

3. 减色压缩

很多UI使用的色彩较少,用不到256色。对于这类图片,可以通过减色的方式进行压缩,以减少图片大小。

三、框架设计层面

对于相对中大型的游戏,系统众多。合理、适时地释放内存有助于保证游戏的正常体验,甚至可以防止内存快速达到峰值,导致设备崩溃。

主流平台机型可用内存指标

  • Android平台:在客户端最低配置以上,均需满足以下内存消耗指标(PSS):
  • 内存1G以下机型:最高PSS <= 150MB
  • 内存2G的机型:最高PSS <= 200MB
  • iOS平台:在iPhone4S下运行,消耗内存(real mem)不大于150MB

具体优化措施

1. 场景切换时避开峰值

当前一个场景还未释放时就切换到新的场景,两个场景的内存叠加很容易达到内存峰值。解决方案是,在屏幕中间遮盖一个Loading场景。当旧场景释放完毕,且新场景初始化结束后,隐藏Loading场景,从而有效避开内存大量叠加超过峰值的情况。

2. GUI模块加入生命周期管理

一个游戏通常包含主角、强化、技能、商城、进化、背包、任务等多个系统。如果全部打开这些系统,再加上点击世界地图等操作,以及一些逻辑数据的内存占用,内存很快就会达到峰值。因此,对系统模块的生命周期进行有效管理非常必要。

首先,将模块进行划分:

  • 经常打开的模块:标记为Cache_10
  • 偶尔打开的模块:标记为Cache_5
  • 只打开一次的模块:标记为Cache_0

创建一个ModuleMananger类,其内部的Render方法每分钟轮询一次。对于Cache_0类型的模块,关闭后直接Destroy释放内存;Cache_10类型的模块在10分钟后自动释放内存;Cache_5类型的模块在5分钟后自动释放内存。每次打开模块时,该模块会重新计时,这样可以实现内存的有效合理分配。

作者信息

洞悉

洞悉

共发布了 3994 篇文章