Unity知识

Monobehaviour生命周期

一图流

碰撞体和触发器的区别

使用条件的区别

  • 触发器使用时需要在碰撞体上勾选isTrigger,勾了之后将不会产生碰撞效果,碰撞体和触发器两者不能共存
  • 触发器的主动方需要携带RigidBody和碰撞体,被动方只需要携带一个触发器即可;或主动方携带RigidBody和触发器,被动方携带碰撞体
  • 碰撞体则需要主动方携带RigidBody,两者都需要有碰撞体

用途的区别

碰撞体能够模拟真实的物理碰撞;而触发器更适合做一个触发功能:比如主角走到了保存点,主角接触到地面的物品自动捡起

Unity中的GC

Unity中使用C#进行编程,内存部分分为栈区和堆区。

其中栈区的内存由操作系统负责分配和回收,堆区的内存需要手动申请和通过GC机制来回收。

堆区申请和回收的速度都比栈区要慢。在GC时,需要扫描整个堆区的内存,然后找出其中被标记为可回收的,最后执行回收操作

什么会导致GC

  • 手动调用GC
  • 系统会按照一定的频率自动GC
  • 当申请堆空间但是当前空间不足的时候

申请堆空间后可能发生的事情是什么

  • 内存空间足够,正常申请
  • 内存空间不够,正常申请后失败,然后进行GC,最后成功申请
  • 内存空间非常缺乏,进行GC后仍然失败,需要扩充堆空间

GC是比较耗时的,扩充堆空间也耗时,所以如果不注意优化的话,GC频繁,游戏性能下降

GC的步骤是什么

  • 首先会扫描所有堆上的对象
  • 然后根据对象的引用数量判断是否可以删除
  • 标记可以删除的对象
  • 删除标记的对象

也就是说,堆上的对象越多,GC的开销越大

GC频繁的害处是什么

首先频繁的GC代表多次的内存申请和回收,它和先进后出的栈不同,频繁的GC可能会导致内存碎片的产生,从而导致更频繁的GC

进行GC的时候最直接的表现就是掉帧

如何减少GC

把GC放在不为人知的地方

比方说可以在游戏的设置界面或者在加载界面手动进行GC,这样用户不易察觉到

减少GC

  • 最基本的,装箱拆箱操作就会导致产生更多的内存垃圾,导致GC的发生,所以应该尽量避免装箱拆箱

  • string类型是引用类型,也就是说需要在堆上分配空间,那么使用频繁的使用+号进行字符串拼接,肯定会导致垃圾的产生。所以应该尽量使用StringBuilder来完成这易操作

  • 不要频繁的在Update函数中申请堆空间

  • 协程也会产生内存垃圾,因为Unity会创建一个实体来管理协程

    // 携程中使用不当也会导致GC
    // 装箱操作
    yield return 0;
    
    // 每次调用都构建了一个新的堆区对象
    yield return new WaitForSecond(1f);
    
  • LinQ表达式采用装箱方式实现,也会造成GC

一图流

协程与多线程

Unity中的性能优化

脚本方面

  • GetComponentInstantiateSetActive,IO操作等耗时较长的接口应该在Loading的时候做
  • 用移除屏幕代替UI的SetActive(false)。因为SetActive会去遍历当前物体及它的所有子物体,然后执行对应的OnEnableOnDisable操作
  • 减少使用==号对gameObject的使用
  • 子物体过多的Gameobject应该减少对其进行Transform变换
  • 使用StringBuilder代替string,减少gc
  • 减少临时对象的创建,减少gc
  • 减少装箱拆箱的操作,使用装箱拆箱操作的容器有:ArrayListHashtable
  • 在不影响游戏体验的时候(例如Loading、设置界面)主动通过System.GC.Collect()调用垃圾回收

图像方面

降低DrawCall

  • 批处理:将使用相同渲染状态的网格合并,一次性交给GPU进行渲染

    静态批处理:适用于场景中不会运动的物体,材质相同的物体会被合并在一起

    动态批处理:由Unity自动完成。我们做的工作就是辅助Unity自动合并更多的物体:在shader中减少使用顶点属性,或尽量减少模型的顶点数量

  • GPU Instancing:将同一材质,同一网格的物体合并,一次性交给GPU进行渲染。

    使用了不同的颜色或者贴图的(uniform)都不属于同一材质;物体缩放不会影响Instancing

Unity中快速移动物体穿墙

https://www.zhihu.com/question/39177106

C#装箱拆箱

装箱拆箱是什么

理解层面来讲:值类型转换为引用类型叫做装箱;引用类型转化为值类型叫做拆箱

装箱拆箱的坏处

首先装箱和“拆箱”带来的内存分配和数据拷贝是耗时的,而且传参时产生的装箱拆箱会产生内存垃圾,导致内存占用,从而导致GC的发生

什么会导致装箱拆箱

使用非泛型的容器可能会,例如ArrayList和HashTable

LinQ表达式也是使用装箱实现的,所以也可能会

装箱拆箱执行了什么

从定义层面来讲:

  • 装箱
    • 首先需要在堆中分配内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)
    • 然后将栈中的数据拷贝至堆
    • 最后返回一个引用指向该内存空间
  • 拆箱
    • 获取到箱子中属于值类型字段的那个地址
    • (将该地址中的数据拷贝到栈中的值类型中)——这一步其实不属于拆箱,但是是”拆箱“的必经之路

如何改变箱子中物体的值

public struct A
{
    public int data;
    public void Change(int num) { data = num; }
}
public interface IChange
{
    void Change();
}
public struct B : IChange
{
    public int data;
    public void Change(int num) { data = num; }
}
// 尽管有new但是还是分配在栈上 不要被C++误导
A a = new A();
object o = a;
((A)o).Change(300);
// 结果还是0 无法修改a中的数据 因为这是拆箱之后产生的一份拷贝
Debug.Log(((A)o).data);
B b = new B();
object o = b;
((IChange)o).Change(100);
// 成功修改
Debug.Log(((B)o).data);
public class C
{
    public int data;
    public void Change(int num) { data = num; }
}
C c = new C();
// 不存在装箱的操作 只是单纯的类型转换
object o = c;
((C)o).Change(500);
// 成功修改
Debug.Log(((C)o).data);

C#中的struct和class

  • struct是值类型,按值传递,内存分配在栈上;class是引用类型,按引用传递,内存分配在堆中
  • struct不能有无参构造函数和析构函数,只能继承接口。调用struct中的有参构造函数需要使用到new
  • struct创建时可以不适用new,但是要需要对它的成员(全部)进行赋值,然后才能使用。class的对象创建时必须使用new后才能使用
  • struct中若包含了引用类型,那么该引用类型还是存放在堆上的。class中如果包含了值类型,那么值类型是存放在堆上的
  • struct中默认的修饰符是public但是class中是private
  • struct中不能使用abstract,virtual和protected修饰符
原文地址:https://www.cnblogs.com/tuapu/p/15226394.html