内容将会持续更新,有错误的地方欢迎指正,谢谢!
拥有更好的学习体验 —— 不断努力,不断进步,不断探索 |
助力快速掌握 面试题 为面试者节省宝贵的学习时间,避免困惑! |
![请添加图片描述](https://img-blog.csdnimg.cn/direct/b1a7447201d740049458302fb61f865a.jpeg)
文章目录
- 一、什么是序列化?
- 二、Destroy和DestroyImmediate的区别?
- 三、Unity常用资源路径有哪些?
- 四、向量的点乘、叉乘以及归一化的意义?
- 五、内存泄漏
- 六、对象池原理?
- 七、请简述如何在不同分辨率下保持UI的一致性?
- 八、CPU优化
- 1、渲染模块
- 1.1、降低DrawCall
- 1.2、简化资源
- 2、UI模块
- 3、加载模块
- 3.1、场景卸载
- 3.2 、场景加载
一、什么是序列化?
在Unity中,序列化(Serialization)是指将对象转换为字节流或其他格式,以便于在内存、文件或网络中进行传输或存储的过程。
Unity中的序列化通常涉及以下几个方面:
-
Unity对象的序列化:
Unity中的许多对象(如GameObject、Component等)都可以通过Unity的序列化系统进行序列化。这意味着它们的属性可以在Inspector面板中显示,并且可以在场景中保存和加载。当你在Unity中编辑场景或预制件时,所做的更改都会被序列化并保存到场景文件或预制件中。 -
脚本中的字段序列化:
在Unity中,你可以使用C#中的属性来标记字段,以便它们可以被序列化。常见的序列化属性包括[SerializeField]和[System.Serializable]。这样标记的字段可以在Inspector面板中显示,并且可以在编辑器中修改。这对于配置游戏对象或保存数据非常有用。 -
自定义数据结构的序列化:
除了Unity提供的基本类型和对象之外,你也可以序列化自定义的数据结构,例如结构体、类、列表和字典等。为了实现这一点,你需要确保你的自定义数据结构是可序列化的,通常需要将其标记为[System.Serializable]。 -
.Json、XML和二进制等格式的序列化:
在Unity中,你还可以使用第三方库或Unity自带的JsonUtility类来将对象序列化为Json格式,或从Json格式反序列化为对象。类似地,你也可以使用XML或二进制格式进行序列化和反序列化。这种方式适用于在游戏中保存和加载数据、配置文件等场景。
二、Destroy和DestroyImmediate的区别?
- Destroy 销毁对象,内存中还存在,只有垃圾回收的时候释放内存。
- DestroyImmediate 立即销毁对象并释放内存。
三、Unity常用资源路径有哪些?
-
Application.dataPath
此路径在Unity的工程目录下,通过这个路径可以访问项目中任何文件夹中的资源。 -
Application.streamingAssetsPath
StreamingAsset文件夹中的内容则会原封不动的打入包中,因此StreamingAsset主要用来存放一些二进制文件。 -
Application.persistentDataPath
一个持久化数据储存目录的路径,可以在此路径下存储一些持久化数据文件,这个路径可读,可写,一般网络下载的资源都存在于这个路径。
特点:- 可读写,只能运行时才能写入或者读取,无法将数据提前存入这个路径
- 无内容限制,可从StreamingAsset中读取二进制文件或者从AssetBundle读取文件写入PersistentDataPath中。
- 可使用UnityWebRequest类来读取。
-
Resources
Resources文件夹是Unity里自动识别的一种文件夹,可在Unity编辑器的Project窗口里创建,并将资源放在里面,是作为一个Unity的保留文件夹出现的,Resources文件夹下的资源不管是否有用,全部都会打包,并且打包时会将里面的资源进行压缩。
特点:- 只读路径,不能动态修改,一般用于存放一下固定不更改的资源
- 放在这里的文件夹中的资源在打包的时候会进行压缩
- 可通过Resources.Load进行动态加载
四、向量的点乘、叉乘以及归一化的意义?
- 点乘dot
用来求向量之间的夹角,判断向量是否在同一方向、以及B向量在A向量上的投影- a·b>0 方向基本相同,夹角在0°到90°之间
- a·b=0 正交
- a·b<0 方向基本相反,夹角在90°到180°之间
- 叉乘cross
可以获得两个向量A和B所构成平面的垂直的向量C,可以用来判断角色移动方向,判断是顺时针还是逆时针
- 归一化normalized
用在只关系方向,不关心大小的情况下
五、内存泄漏
内存泄漏指由于疏忽或者错误造成程序不能释放或则不能及时释放已经不再使用的内存的情况,是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存不能回收或不能及时回收。当程序不能释放的内存越来越多时就会造成程序的性能下降。
- 第一个原因,当你的对象仍被引用但实际上却未被使用,由于它们被引用,因此GC将不会收集它们,这样它们将永久保存并占用内存。
- 第二个原因,当你以某种方式分配非托管内存并且不释放它们
- 订阅Events
订阅事件后,该对象将保留对你类的引用
public class MyClass
{
public MyClass(WiFiManager wiFiManager)
{
wiFiManager.WiFiSignalChanged += OnWiFiChanged;
}
private void OnWiFiChanged(object sender, WifiEventArgs e)
{
// do something
}
//假如wifiManager的寿命超过MyClass,那么你就已经造成了内存泄漏,
//wifiManager会引用MyClass的任何实例,并且垃圾回收器永远不会回收它们。
}
- 在匿名方法中捕获类成员
public class MyClass
{
private JobQueue _jobQueue;
private int _id;
public MyClass(JobQueue jobQueue)
{
_jobQueue = jobQueue;
}
public void Foo()
{
_jobQueue.EnqueueJob(() =>
{
//类成员_id是在匿名方法中被捕获的,因此该实例也会被引用
Logger.Log($"Executing job with ID {_id}");
// do stuff
});
}
}
-
静态变量 静态变量及其引用的所有内容都不会被垃圾回收
静态变量是全局变量,它们的生命周期与应用程序相同,而且它们不依赖于任何特定的对象实例,不会随对象实例的销毁而释放内存。因此静态变量不被GC回收。 -
缓存功能 使用字典时无限缓存导致内存增加
-
永不终止的线程 会永久持有对对象的引用
-
没有回收非托管内存(没有垃圾回收) 非托管堆不会自动进行垃圾回收,需要显示回收垃圾
解决方法:继承IDisposable接口,实现Disposable方法 -
添加了Dispose方法却不调用它
六、对象池原理?
对象池是游戏编程中非常常用的优化策略和设计模式
游戏中,常常会遇到频繁的创建和销毁大量相同对象的场景,频繁的创建对象会造成GC压力,可能导致卡顿,进而影响游戏体验。
-
核心思想
将需要重复创建的对象先缓存下载,需要时激活,不需要时停用,从而避免频繁创建对象 -
原理
当创建对象时,对象池将对象放入池管理的某种内存连续的数据结构中。当不需要对象时,对象池并不销毁对象,而是将对象回收到池中,下次需要的时候再次从池中拿出来。
因为对象存在内存连续的数据结构中,所以解决了内存碎片的问题。
因为对象每次用完以后就放回池中循环利用而不是再次创建和销毁这样解决了频繁的内存分配和销毁问题。
七、请简述如何在不同分辨率下保持UI的一致性?
- Canvas 画布设置组件Canvas Scaler的分辨率模式为Scale With Screen Size 保持画布一定比例下随屏幕改变,画布进行缩放。
- UI的位置的一致性通过设置锚点来完成。
八、CPU优化
CPU方面的性能开销主要可归纳为两大类:引擎模块性能开销和自身代码性能开销。其中,引擎模块中又可分为渲染模块、动画模块、物理模块、UI模块、粒子系统、加载模块和GC调用等等。
1、渲染模块
1.1、降低DrawCall
DrawCall是CPU向GPU发送绘制命令的接口调用,DrawCall越高,则渲染模块的CPU开销越大。
降低DrawCall的方法则主要是减少所渲染物体的材质种类,并通过Draw Call Batching来减少其数量。
但是,需要注意的是游戏性能并非Draw Call 越小越好,这是因为决定渲染模块性能的除了Draw Call之外还有用于传输渲染数据的总线带宽。
当我们使用Draw Call Batching将相同材质的网格模型合并在一起时,可能会造成同一时间需要传输的数据大大增加,导致带宽阻塞,在资源无法及时传输过去的情况下,GPU只能等待,从而反倒降低了游戏的运行帧率。
优化方法:
- 批处理
- 合并图集
- GPU Instancing
1.2、简化资源
简化资源是非常行之有效的优化手段,在大量的游戏中其渲染资源是过量的,过量的网格资源,不合规的纹理资源等等。比如Texture的内存占用、大小、压缩格式;mesh的顶点是否过多之类的。
- Texture
选择正确的纹理大小,压缩格式非常重要,不仅减少了内存的占用,也减少了CPU的压力 - Mesh 进行减面等操作
- LOD 多层次细节技术
- MipMap 多级渐远纹理技术
- Occlusion Culling 遮罩剔除
当场景中包含大量模型时,DrawCall就会非常大,造成的性能消耗就会比较大,从而造成渲染效率低下,使用遮罩剔除技术,可以使那些被阻挡的物体不被渲染,达到提高渲染效率的目的。
原理:在场景空间中创建一个区域,该区域由单元格组成,每个单元格构成场景的一部分,这些单元格会把整个场景拆分成多个部分,当摄像机能够看到该单元格时,表示该单元格中的物体会被渲染出来。
2、UI模块
CPU开销高主要原因之一是Canvas对UI网格的重建,有很对情况会触发Canvas对网格的重建,例如Image、Text等UI元素的Enable及UI元素的长、宽或则Color属性的变化等。
优化:
- 使用尽可能少的UI元素,在制作UI时,一定要仔细检查UI层级,删除不必要的UI元素,这样可以减少深度排序的时间,以及Rebuild的时间。
- 减少Rebuild的频率,将动态UI元素(频繁改变例如顶点、alpha、坐标和大小等的元素)与静态UI元素分离出来,放到特定的Canvas中
- 谨慎使用UI元素的enable和disenable因为它们会触发耗时较高的rebuild
- 谨慎使用Text的Best Fit选项,虽然这个选项可以动态的调整字体的大小以适应UI布局而不会超框,但其代价是很高的,Unity会为用到该元素所用到的所有字号生成图元保存在图集里,不但额外增加生成的时间,还会使得字体对应的图集变大
- 谨慎使用Canvas的Pixel Perfect选项,该选项会使得UI元素在发生位置变化时,造成Layout Rebuild
- 除了Rebuild过程之外,UGUI的Raycast处理消耗也可能会成为性能热点,因为UGUI在默认情况下会对所有的可见的Graphic组件调用raycast,对于不需要处理事件的Graphic一定要禁用raycast。
3、加载模块
加载模块的性能开销比较集中,主要出现在场景切换处,场景切换的主要性能开销主要体现在两个方面,前一场景的场景卸载和下一场景的场景加载。
3.1、场景卸载
对于Unity 引擎而言,场景卸载一般由引擎自动完成,其性能开销主要被以下几个部分占据:
-
Destory
引擎在场景切换时会收集未标识成DontDestroyOnLoad的对象及组件,然后进行Destroy,同时代码中的OnDestroy被触发执行,这里的性能开销主要取决于OnDestroy回调函数中的代码逻辑。 -
Resources.UnloadUnuseAssets
一般情况下,场景切换过程中,会调用该API,其耗时开销主要取决于场景中Asset和object的数量,数量越多,则耗时越多。
3.2 、场景加载
- 资源加载
资源加载几乎占据了整个加载过程90%以上的时间,其加载效率取决于资源的加载方式(Resource.Load或AssetBundle)、加载量(纹理、网格、材质等资源数据)和资源格式等等。 - Instantiate实例化
在场景加载过程中,往往伴随大量的实例化操作,比如UI界面实例化、场景建筑实例化等等,在Instantiate实例化,引擎底层会查看其相关的资源是否已经被加载,如果没有则会加载其相关资源,在进行实例化。
每一次跌倒都是一次成长 每一次努力都是一次进步 |
如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。
在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!