unity性能优化-实际开发中需要注意的点

unity性能优化-实际开发中需要注意的点

性能优化建议

在进行下面优化之前,建议先使用 Unity Profiler 定位真实的瓶颈。

优化核心原则:

能缓存的就缓存(变量、组件、材质)。

能不更新就不更新(降低 update 频率,使用事件驱动或Coroutine)。

避免在循环中分配内存(减少 new 关键字,使用对象池)。

1.复杂数学计算,比如mesh顶点数据的创建,大小和归一化属性都非常耗费 CPU(都涉及计算平方根)

可以使用对象池来空间换时间处理,涉及开根号的用平方比较

2.shader的合批处理

3.一些List,Vector不要用集合,而是指定它的大小

4.不要再update里面去大量调用Component(新手容易忽略重要)

5.优化字符串对比

6.常量的字符串写成类的成员变量

7.裁剪引擎,去掉音频

8.裁剪引擎,去掉物理,注意此时包括包围盒都不能使用

9.固定帧率,减少帧率

10.避免使用拆箱装箱

在C#中,装箱(Boxing)和拆箱(Unboxing)是指值类型与引用类型之间的转换。装箱是将值类型转换为对象类型(通常是将值类型包装在堆上),而拆箱则是从对象类型还原为值类型。

装箱(Boxing)开销

内存分配

装箱过程中,会在堆上分配内存来存储值类型的数据。堆内存的分配通常比栈内存的分配慢,因为堆内存需要处理垃圾回收(Garbage Collection),而且堆内存分配涉及更多的管理操作。

数据复制

装箱需要将值类型的数据从栈上复制到新分配的堆对象中。这涉及到数据的复制操作,增加了额外的开销。

垃圾回收

装箱产生的堆对象最终会由垃圾回收器(GC)回收。垃圾回收是一个耗时的过程,会影响应用程序的性能。

拆箱(Unboxing)开销

类型检查

拆箱过程中,需要进行类型检查,以确保对象确实是预期的值类型。这涉及到运行时的类型元数据访问和检查,增加了开销。

数据复制

拆箱需要将堆上的数据复制回栈上。这同样涉及数据的复制操作,带来了额外的性能开销。

避免装箱和拆箱的方法

使用泛型:泛型可以减少装箱和拆箱的需要,因为泛型可以在编译时确定类型,而不需要在运行时进行类型转换,但是泛型带来另一个问题,即转换为il2cpp的时候,代码会膨胀,这个后面可以再说

// 使用泛型避免装箱和拆箱
List<intnumbers = new List<int();
numbers.Add(42);
int number = numbers[0];

 


避免频繁的值类型到引用类型的转换:在设计程序时,尽量减少值类型与引用类型之间的转换。如果可能,尽量使用值类型本身来进行计算和处理。

使用struct而不是类:在某些情况下,可以使用struct来代替类,因为struct是值类型,不需要装箱和拆箱。

避免接口调用:接口调用通常需要装箱,特别是在接口接受值类型参数时。尽量避免在接口方法中使用值类型参数

11.GameObject.Instantiate 和 SetParent

 1 public class ESDRoadEdgePool
 2 {
 3     //public int amountToPool = 100; // 池中对象的数量 
 4     public int amountToPool = 25;
 5     
 6     pooledObjects = new List<GameObject>();
 7 
 8     for (int i = 0; i < amountToPool; i++)
 9     {
10         GameObject obj = Instantiate(objectToPool); // 实例化可以缩减
11         obj.SetActive(false);                       // 可以预先设置预制体的activeSelf
12         obj.transform.parent = this.transform;      // 可以合并到Instantiate中
13         pooledObjects.Add(obj);
14         
15            
16         GameObject obj = Instantiate(objectToPool, transform);
17         pooledObjects.Add(obj);
18     }
19 }

 

12.嵌套循环中new Vector3等

 
 1 for(int i = 0; i < count; i++)
 2 {
 3     ...
 4     Vector2 p1l_uv = new Vector2(offest.x, uvStart / 10f);
 5     Vector2 p1r_uv = new Vector2(offest.y, uvStart / 10f);
 6     Vector2 p2l_uv = new Vector2(offest.x, (uvStart + distance) / 10);
 7     Vector2 p2r_uv = new Vector2(offest.y, (uvStart + distance) / 10);
 8     ...
 9 }
10 
11 // 可以将对象创建挪到循环外进行复用
12 
13 Vector2 p1l_uv = new Vector2();
14 Vector2 p1r_uv = new Vector2();
15 Vector2 p2l_uv = new Vector2();
16 Vector2 p2r_uv = new Vector2();
17 
18 for(int i = 0; i < count; i++)
19 {
20     ...
21     // 同时可以使用Vector3.Set来一次性对向量的每个分量进行赋值
22     p1l_uv.Set(offest.x, uvStart / 10f);
23     p1r_uv.Set(offest.y, uvStart / 10f);
24     p2l_uv.Set(offest.x, (uvStart + distance) / 10);
25     p2r_uv.Set(offest.y, (uvStart + distance) / 10);
26     ...
27 }

 

13.每帧循环调用的计算逻辑,可以使用对象池替代

 1 for (int i = 0; i < len - 1; i++)
 2 {
 3     ...
 4     SquareCell cell = new SquareCell();
 5     ...
 6  }
 7  
 8  // 加入对象池
 9  public class SquareCellPool
10  {
11     public static SquareCell Allocate()
12     {
13         if (pool.TryDequeue(out var cell))
14         {
15             totalCount--;
16             return cell;
17         }
18     
19         for (int i = 0; i < poolInterval; i++)
20         {
21             SquareCell newCell = new SquareCell();
22             pool.Enqueue(newCell);
23             totalCount++;
24         }
25     
26         if (totalCount > 65535)
27         {
28             Debug.LogError("Pool Out of Count!" + totalCount);
29             return null;
30         }
31         cell = pool.Dequeue();
32         totalCount--;
33         return cell;
34     }
35  }

 


14.Shader中处理逻辑能不使用高精度可以使用低精度的

15.避免 Camera.main

在 Unity 中,Camera.main 实际上是调用了 GameObject.FindWithTag("MainCamera"),这是一个非常耗时的操作

发现unity文档里面,这块其实也是做了缓存gameobject

但是缓存肯定还是最快的,因为gameobject也有开销

本文档会陆续补充更新,优化无止境

posted @ 2026-01-19 22:55  pipicfan  阅读(3)  评论(0)    收藏  举报