Unity性能优化1 2 3

堆内存优化

就目前Unity所使用的Mono版本而言,Mono堆内存分配特点是,一旦分配,就不会返还给系统,无论堆内存使用了多少,因此需要注意影响堆内存分配的因素

  1. 游戏中使用到的配置文件的大小
  2. 检测是否有较大的Container(Array,List,Dictionary)存在,比如缓存池…

资源加载

  1. 常用的资源应避免频繁加载和卸载,可以考虑将其常驻于内存
  2. 如果要使用加载AssetBundle,尽量使用 New WWW加载,而非LoadFromMemory方式,LoadFromMemory加载效率比New WWW低,除非有特殊需求

场景加载优化

简化资源
  1. 将项目中RGBA32和RGBA16的贴图转化成ETC1纹理进行加载,这样既可以减少App包体大小也可以提高加载速度
  2. 对场景中网格数据进行压缩,简化场景中的网格数据,减少不必要的数据占用,可以尝试使用AssetStore中的SimpleLOD等简化工具堆网络模型简化
  3. 根据机型采用不同的渲染LOD,降低低端设备的渲染压力

去掉Log输出

不单单是加载场景的时候需要关闭log输出在平时也应该注意删掉已经弃用的输出语句,尤其是在打包的时候一定要关掉log输出,或者只保留非常关键的输出语句

严格检查资源加载策略

尽量避免多次重复的加载卸载相同的资源,可以考虑内存常驻
注意处理依赖关系,规划AssetBundle加载方案(比如shader抽离等)

控制场景中材质资源使用数量

Shader优化

通过AssetBundle以来关系打包将Shader进行抽离,变成单独含有Shader的AssetBundle文件,这样在游戏启动时,加载AssetBundle并对Shader进行统一解析初始化,Shader占内存小,可以考虑常驻内存


UI优化

  1. 在内存允许的情况下,添加缓存机制,比如怪物,角色,或者预设,使用SetActive()方式进行,避免Instantic的高额开销
  2. UI元素的OnEnable和OnDisable都会进行比较多的操作,也会有较大的CPU开销,对于点击频率很高的界面,更高效的做法是 。 A:改变UI位置(以UIPanel为单位)实现隐藏显示,因为位置改变不会产生多余的堆内存和CPU消耗,同时也节省了Enable和Disable的CPU开销。B:通过设置摄像机的Culling Mask 来实现UI的显示隐藏,同样避免Enable/disable操作。(不过要记得将移出的panel设为static并停止所有UI元素的动画等)
  3. 添加延时机制避免大量重复性显示隐藏带来的消耗,对于打开一个界面要释放很多资源问题,可以考虑添加延迟机制,避免同一帧加载实例化的东西太多
  4. 对于动态出现和消失的UI元素可以考虑拆分到子UIPanel中,比如战斗中伤害飘字,通过拆分Panel的方法控制开销

物理系统优化

  1. Rigidbody和Collider是对物理性能影响比较大的因素,注意控制其数量
  2. 通过Layer梳理碰撞检测,避免发生大量不必要的碰撞检测
  3. 尝试开启“Optimize Mesh Data”选项,在Player Setting 的Other Setting 中,勾选后,引擎会在发布时遍历所有网格数据,将其多余的进行去除,从而降低数据量大小。需要注意的是,这里的多余是值得 Mesh数据中包含了渲染时Shader中所不需要的数据,例如,Mesh中包含Position,uv,color,normal等顶点数据,但其渲染所用的shader只需要Position,uv,那么color和normal将会被认为是 多余 的数据,引擎在发布游戏的时候会自动去除。但是。需要注意的是,对于Runtime情况下有更换Shader需求的Mesh,如果Runtime时需要为某一个Gameobject更换更复杂,需要访问更多订点属性的Shader 的话,建议次阿娘这些Shader挂载在相应的Prefab上,以免引擎去除Runtime中会进行使用的网格数据。
  4. 通过依赖关系对资源进行AssetBundle打包。不仅可以对资源热更新,同事可以降低资源在内存中的冗余度,以及将部分资源进行预加载(Shader等),尽可能避免重复加载的CPU占用

纹理格式

图集Alpha通道拆分(后面有机会在说)
项目中的RGBA32,ARGB32和RGBA16格式的纹理资源进行进一步检测,尽可能将其转换为两个ETC1纹理(RGB ETC1纹理 + Alpha ETC1 纹理)
注意纹理冗余
造成纹理冗余的原因一般有三种情况
1.资源重名
2.AssetBundle文件创建时将同种资源打包到不同的AssetBundle文件中
3.资源加载后未完全卸载


CPU优化

渲染模块

降低Draw Call

  • 降低Draw Call的方法则主要是减少所渲染物体的材质种类,并通过Draw Call Batching来减少其数量
  • 通过把纹理打包成图集来尽量减少材质的使用。
  • 尽量少的使用反光啦,阴影啦之类的,因为那会使物体多次渲染。

但是,需要注意的是,游戏性能并非Draw Call越小越好。这是因为,决定渲染模块性能的除了Draw Call之外,还有用于传输渲染数据的总线带宽。当我们使用Draw Call Batching将同种材质的网格模型拼合在一起时,可能会造成同一时间需要传输的数据(Texture、VB/IB等)大大增加,以至于造成带宽“堵塞”,在资源无法及时传输过去的情况下,GPU只能等待,从而反倒降低了游戏的运行帧率。

简化资源

  • 简化网格资源、不合规的纹理资源等,尽量不做作多余的渲染开销
  • LOD、Occlusion Culling和Culling Distance等等

UI模块
在NGUI的优化方面,UIPanel.LateUpdate为性能优化的重中之重,它是NGUI中CPU开销最大的函数
对于UIPanel.LateUpdate的优化,主要着眼于UIPanel的布局,其原则如下:

1. 尽可能将动态UI元素和静态UI元素分离到不同的UIPanel中(UI的重建以UIPanel为单位),从而尽可能将因为变动的UI元素引起的重构控制在较小的范围内; 2.尽可能让动态UI元素按照同步性进行划分,即运动频率不同的UI元素尽可能分离放在不同的UIPanel中; 3.控制同一个UIPanel中动态UI元素的数量,数量越多,所创建的Mesh越大,从而使得重构的开销显著增加。比如,战斗过程中的HUD运动血条可能会出现较多,此时,建议研发团队将运动血条分离成不同的UIPanel,每组UIPanel下5~10个动态UI为宜。这种做法,其本质是从概率上尽可能降低单帧中UIPanel的重建开销。

GC

1.字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。 2.尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。 3.不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。 4.使用“池”,以实现空间的重复利用。 5.最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。而且我很讨厌LINQ的一点就是它有可能在某些情况下无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。

代码效率

1.不要频繁使用GetComponent,尤其是在循环中 2.使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0); 3.对于方法的参数的优化:善于使用ref关键字。值类型的参数,是通过将实参的值复制到形参,来实现按值传递到方法,也就是我们通常说的按值传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。 4.使用“池”不只是UI上的组建场景中的动态资源也可以使用,减少Instantiate次数,还有就是更改位置要比隐藏显示要好

GPU优化

减少顶点数量,简化计算复杂度。

1.保持材质的数目尽可能少。这使得Unity更容易进行批处理。
2.使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
3.如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
4.使用光照纹理(lightmap)而非实时灯光。
5.使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。
6.遮挡剔除(Occlusion culling)
7.使用mobile版的shader。因为简单。

压缩图片

1.使用适当的图片格式
2.使用mipmap

作者: Josh Chen

技术引领潮流!