赞
踩
未经优化的Unity项目常会导致如下问题:
因此,对项目优化是必要的且必须的,一些Unity项目的优化方案如下:
本文将从Unity自带的优化工具入手,介绍如何对Unity项目进行优化,如Profiler窗口、Stats窗口、Frame Debugger窗口、Occlusion Culling(遮挡剔除)以及其它的一些优化方法。
Profiler 窗口是一个非常强大的性能诊断工具,它提供了项目运行时的详细信息,包括FPS(每秒帧数)、 CPU利用率、内存占用等,以便分析性能瓶颈、进一步识别和优化性能问题,提高游戏的帧率和整体性能。
在Unity的菜单栏Window > Analysis > Profiler或者使用快捷键Ctrl+7
打开Profiler窗口:
打开之后,在 Editor 窗口中运行项目,以便 Profiler 可以开始从项目中收集性能数据:
在Profiler窗口的左侧,有一系列的分析器,如 CPU Usage(CPU 的使用)、GPU Usage(图形处理器的使用)、Rendering(渲染)、Memory(内存的使用)、Audio(音频)、Video(视频)、Physics(物理)、Network(网络)、UI(用户界面)、Realtime GI(实时全局光照)、Virtual Texturing(虚拟纹理)、File(文件)和Asset(资源加载)。
Profiler窗口的下半部分显示当前选择的Profiler的详细信息。
如选中Rendering,显示的则是场景渲染相关的详细信息:
一般来说,可以根据项目目前遇到的瓶颈而选择合适的Profiler进行分析。如果资源的内存占用过大,则可以使用Memory Profiler得知哪些资产耗费了最多的资源;如果项目运行缓慢,则可以使用CPU Usage Profiler开始分析。
在Profiler窗口的顶部,有一些控件,允许我们启动和停止分析、启用和禁用分析功能以及浏览Profiler收集的数据。
Play Mode:对运行中的项目进行性能分析。
Edit Mode:对 Unity 编辑器进行性能分析。
<Enter IP>:在其他电脑或设备上对项目进行远程性能分析。
:Record,记录Profiler以便进一步的调试和分析。
:Deep Profiler,如果启用,Profiler将会分析所有的脚本。这样我们能够准确地知道在脚本中花费的时间。但是需注意,开启Deep Profiler会占用大量的内存,因此,项目的运行速度会较之前更慢。
:Clear,清除所有Profiler收集的数据。
:Clear on Play,每次运行项目时清除之前Profiler收集的数据。
:Load,从文件中加载二进制的分析信息,单击 Shift 将把信息追加到Profiler中。
:Save将当前分析信息保存到二进制文件。
:Arrows,在Profiler内捕获的数据中,按帧向前或向后逐步移动,可对每帧进行具体的定位和分析。
:Current,返回当前帧。
这些控件可以让我们在项目运行的过程中收集产生的数据,在收集到所需数据后停止收集。然后我们可以使用Arrows控件逐步浏览收集的数据,直到找到可能存在性能问题的帧。
Stats 窗口能实时显示游戏帧率、渲染性能和资源使用等统计数据,这对于优化性能非常有用。
在Unity的Game视图的右上角可以找到Stats,点击以打开Stats窗口:
统计信息分为两类: Audio和Graphics。
Audio 部分包含关于场景当前帧的四个重要信息:
Level:当前声音分贝的等级。如果已静音,那么Level旁会显示(MUTED)。
DSP load:DSP(Digital Signal Processing)负载表示同时处理的音频流的百分比。
Clipping:音频失真的百分比。
Stream load:读取和加载场景或GameObject所需的时间,通常应该保持较低的值。
Graphics 部分包含如下的信息:
Stats 窗口提供了关于项目运行中实时反馈和统计的信息。我们可以通过它确定项目中可能导致性能问题的位置,并在 Profiler 窗口中进一步找到关键的问题所在。
Frame Debugger(帧调试器)是一个用于分析和调试游戏画面渲染的工具。它能让我们深入了解游戏画面的渲染流程,并查看每个像素的颜色和深度信息,方便我们识别并找到性能瓶颈。在某些情况下,它还可以检测到一些常见的渲染错误,如材质设置错误、渲染状态错误等等。
在Unity的菜单栏Window > Analysis > Frame Debugger打开Frame Debugger窗口:
在弹出的窗口中点击左上角的Enable按钮以启用该功能:
单击窗口左侧的任意 DrawMesh 调用。当点击了一个 DrawMesh,它会用 DrawMesh 实际渲染的内容来更新游戏窗口:
Frame Debugger窗口被大致划分为左右两个部分,左侧是 DrawMesh 调用序列和后处理效果等其他事件,右侧提供了关于所选DrawMesh调用的进一步信息,例如几何细节和用于渲染的着色器等信息。
Frame Debugger窗口的工具栏:
另外,可以通过色彩通道和亮度进行渲染分离,再通过 Channels 和 Levels 功能进行更精细的调整。
注意,当渲染到 RenderTexture 时,Channels 和 Levels 功能才能使用。
当我们选择DrawMesh调用时,我们可以在右侧查看 ShaderProperties ,它可以显示正在使用的着色器(Shader)的状态和属性。
在Unity中,着色器可以分为多个阶段,例如顶点着色器(Vertex Shader)、片元着色器(Fragment Shader)等等。在渲染过程中,每个阶段的输入和输出都可能会产生变化,因此了解当前着色器的状态和属性非常重要。
通过ShaderProperties,我们可以查看目前使用的着色器、当前阶段、材质属性设置等信息。这样可以检查是否正确使用了着色器和材质属性,进而识别并解决一些渲染相关的问题。
最后,需要注意的是,由于Frame Debugger会降低游戏的性能,因此建议在开发时使用,而不是在发布版中使用。
批处理(Batching)是一种将类似处理任务分组处理的技术。
在Unity中,使用Sprite Atlas(图集)的Batching技术将每个合并的Sprite和UI元素打包成一个或多个贴图,这些贴图合并成一个大的整体,这有助于减少GPU的渲染数量,从而提高游戏性能。可以通过在Inspector中勾选"Batching"来启用批处理。
在编写Unity脚本的过程中,内存管理是一个重要的问题。尤其是在开发手机游戏时,内存使用率对游戏性能有很大影响。
在Unity中,内存有两种管理方式:堆(Heap)和 栈(Stack)。
栈是一个临时存储和较小变量的内存区域,具有更快的访问速度,除了全局变量之外,最好只在需要的时候在使用变量的函数中声明变量。
int myEnergy;
一旦没再引用,栈中的内存就会自动释放。
而堆则用于较大或需要长时间存放的内存对象,但访问速度较慢。在使用关键字“new”创建对象时,内存从堆中分配。
List<string> myGuests = new List<string>();
堆中变量占用的内存需要是一个连续的块。如果声明的变量的连续内存不足,则释放不需要的堆内存,避免内存泄漏,影响游戏性能。如果可用内存仍然不足,则扩展堆。
不再需要用于存储变量数据的堆内存称为垃圾。释放这个不需要的内存称为垃圾回收(Garbage Collection)。垃圾回收通常根据需要进行,但也可以手动触发。垃圾回收的频率取决于多种因素,如需要释放的内存量等,但可能会在性能上造成一些影响。手动触发垃圾回收通常用于在性能影响较小的情况下释放内存。
对象池(Object Pooling)是一种优化垃圾回收的方法,用于在游戏中重复利用GameObject。
在游戏中,有些对象需要频繁地创建和删除,如子弹、火焰等特效,或是程序生成和可破坏的地形块。而在非游戏场景中,如动态填充和刷新的用户界面元素也可以采用对象池的方式。这样会导致内存分配和垃圾回收的频繁操作,从而导致游戏性能下降。
使用对象池可以在游戏初始化时预先创建游戏对象,然后在需要时重复利用这些对象,而不是每次都重新创建。这样可以减少内存分配和垃圾回收,从而提高游戏性能。
具体的使用方法可参考博客:【Unity Optimize】使用对象池(Object Pooling)优化项目
资源优化是指减少Unity在加载、访问和卸载资源时需要执行的操作,从而使项目运行更加平稳和响应迅速。
Unity 要加载、访问和卸载资源所需的时间越少,项目就运行得越顺畅。
有以下的这些技巧对Unity中的资源进行优化:
将所有 2D (Sprites 和 UI) 图形元素应打包到 Sprite Atlases 中。
所有未打包在 Sprite Atlas 中的纹理应具有 2 的幂次尺寸,以利用 GPU 上的内存处理优化。它们不一定需要是正方形,但如果它们是正方形且为 2 的幂次进一步有助于优化。
较长的音频(如背景音乐)应该将其导入设置中的 Load Type 设置为 Streaming,而不是 Decompress on Load 或 Compressed in Memory。这样做会造成可能稍有延迟的启动,但好处是只需要一小部分 RAM 来流式传输,而不是在内存中加载和解压缩整个歌曲。
如果遵循这些技巧,可以更好地处理内存,减少 Unity 对资源的负载,优化项目性能,进而提供更好的用户体验。
缓存(Caching)是指将数据暂时存储起来,以便能够更快地重复使用。缓存所有需要经常访问的东西可提高游戏性能、加速读取速度并减少资源占用的内存,因为从硬盘加载大量资源是一个耗时的过程。通
例如,在Update循环中,不要使用GetComponent,而是声明该组件类型的数据成员,而在Start或Awake中调用GetComponent一次将其分配给数据成员。在Update中,只需引用数据成员即可。
Update、LateUpdate和FixedUpdate是Unity中重要的循环事件。
Update事件会在每一帧中触发,可用于控制游戏对象的移动、旋转和动画等。
LateUpdate事件是带有“Late”前缀的另一个事件,它在每一帧之后触发。它可以用来进行相机操作或其他需要在玩家输入操作之后进行的操作。
FixedUpdate事件调用频率与帧速率无关,它被用于物理引擎的模拟,在处理物理时使用。
不建议将所有 MonoBehavior 的逻辑全都放在 Update() 循环中,原因如下:
在Unity中,有时候某些代码片段可能需要在单个帧(frame)中运行完成并立即返回其结果,这些代码通常在 Update、FixedUpdate 或 LateUpdate 的回调函数中运行。然而,有些任务可能需要比一个帧长的时间来完成,或者只有在特定条件下才会执行。为了解决这些问题,Unity 提供了协程(Coroutine)机制,允许我们在适当的时候挂起并恢复代码执行。
协程(Coroutine)是使用 IEnumerator 返回类型的函数,并通过调用 yield return 语句进行挂起以在稍后恢复执行。通过将代码放置在协程中,使Unity的主循环更新其他游戏对象的帧的同时调用协程的函数,从而对执行时间没有限制。我们可以使用协程来执行一些长时间的任务,例如动画、复杂的算法、迭代生成程序(如地形或随机地图)或创建无限奔跑游戏的屏幕外平台。
另外,当需要定期执行一个任务时,例如在固定间隔时间内执行某个函数,Unity 提供了一种更简单的方式来实现这个目标:InvokeRepeating函数。这个函数仅需要两个参数:要调用的函数和调用之间的时间间隔。这个函数通常与 Start 函数一起使用,以避免每一帧都需要重新调用该函数,从而使游戏效率更高。它特别适用于周期性任务(例如波浪控制器在固定时间间隔内检查是否所有敌人已死亡的任务),或用于更新游戏中的UI元素(如屏幕计分板或生命计数器)。
当我们开发一个项目时,不要一开始就想去优化所有的东西,这会干扰我们建立一个具备所需功能且可测试的项目;不必要的优化会浪费时间,甚至会导致一些错误。相反,应该在开发的过程中从一开始就将优化技巧融入到工作流程中。当完成一个可运行的项目时,应该使用Profiler窗口监控性能 并在目标设备上进行测试,只在需要的时候才进行优化。
换句话说,优化应该是一个持续的过程,应该在确保项目功能正常的基础上进行,而不是一次性完成。我们应该时刻监控项目的性能,并结合目标设备的特性有选择地针对特定的瓶颈进行优化,而不是匆忙地对整个项目进行不必要的优化。这样才能在保持项目可维护性和稳定性的同时,最大化地提升项目性能。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。