建设一个网站需要,创建全国文明城市要求街巷,台州网站制作费用,长沙网站排名提升一. Disable unused scripts and objects
场景中激活的物体或者脚本越多#xff0c;开销越大。对于很多并没有产生作用的脚本和物体#xff0c;可以隐藏掉从而提升性能#xff0c;比如FPS游戏中视野外的部分。
1.Disabling objects by visibility
有时我们希望脚本和物体…一. Disable unused scripts and objects
场景中激活的物体或者脚本越多开销越大。对于很多并没有产生作用的脚本和物体可以隐藏掉从而提升性能比如FPS游戏中视野外的部分。
1.Disabling objects by visibility
有时我们希望脚本和物体在不可见的时候能处于disabled状态。Unity在内部渲染时提供了Frustum Culling技术来裁剪摄像机视野外的部分也提供了Occlusion culling来剔除被遮挡的部分。但是这两个技术只是渲染上的优化并没有影响到物体上运行在cpu的脚本比如AI脚本逻辑脚本都都依然会运行产生开销我们需要自己控制这些行为。
一个不错的解决方法是使用OnBecameVisibe()和OnBecameInvisible()函数。这俩个函数会在renderable物体变看见或者不可见时被调用对于多个相机的场景当物体被任意一个相机可见就会调用OnBecameVisible当物体在所有相机都不可见时会调用OnBecameInvisible。
要注意的是这俩个函数必须和渲染管线打交道所以必须要有个renderable component在物体上比如MeshRender。
实现脚本的关启或者整个Gameobject是否enable的代码可以如下 要注意的一点是对于隐藏掉的gameobject就无法再被becameInvisible调用所以一种解决方法是我们需要将脚本放在子物体上将renderable 物体一直保持visible。
2.Disabling objects by distance
有时我们希望对于距离玩家足够远的脚本或物体变成disabled状态一个很好的例子是AI巡逻功能当离玩家很远时可以保持Idle状态不进行AI处理。下边的代码是一个简单的示例 3.Consider using distancesquared over distance
CPU计算开放运算的开销要远大于乘法当调用Distance函数或者Vector3的magnitude函数时都会进行开方运算。Vector3还提供了sqrMagnitude属性这个数值是未开方的这意味着使用它进行一些距离的判断在大多数情况下会得到同样的结果但是开销却小很多。举个例子 在绝大多数情况下使用平方进行距离的判断会得到同样正确的结果但是当需要精度极高时会产生误差因为使用平方会减少精度。
对于除了distance 外其他的开方操作这个技巧同样适用sqrMagnitude property就是Unity为我们提供的使用这个技巧的一个方式。
二. Minimize Deserialization behavior
Unity的序列化系统主要用于场景prefabsScriptableObjects和各种各样的Asset类型。当这些object 类型被保存到硬盘中时会序列化成YAML格式的文件YAML可以在之后被反序列化回原始的类型。
当一个prefab或者场景被序列化时它所有的gameobjects和脚本都将被序列化包括private和protected类型的字段所有的子物体及子物体上的脚本。
当应用被构建时这些序列化的数据会被一起打包成一个大的二进制文件从disk读取和反序列化这些数据相当慢开销很大会造成明显的性能开销。
当我们调用Resources.Load时就会产生反序列化操作一旦数据从disk加载到内存后再次加载这个引用的数据就会很快。数据越大加载越慢比如UI prefab上层级结构越复杂就会开销越大。第一次加载大的序列化数据时有可能产生很大的CPU开销导致掉帧接下来介绍几种可以降低这种反序列化方法的开销。
1.Reduce serialized object size
尽量减小序列化物体的大小或者将它们拆分成更小的单元使得它们能以更小的单元加载。Unity不支持嵌套prefab最新的已经支持UI prefabs是很好的优化对象因为我们大多数情况下在某一时刻我们并不需要整个UI可以每次加载一些所需的。
2.Load serialized objects asynchronously
prefabs和其他序列化数据可以通过Resources.LoadAsync方法进行异步加载这将会减轻主线程的负担。使用异步方法时需要一些时间来处理使得序列化object变成可用状态。
这种方式对于游戏一开始就立刻需要的prefabs不太合适但是之后所有的prefabs都是很好的异步加载候选对象。
3.Keep previously loaded serialized objects in memory
一旦序列化object被载入到内存后将会一直保持在内存中可以通过instantiating复制更多的prefab。通过Resources.Unload,将会释放掉序列化object所占用的内存空间。
如果我们游戏的内存预算还很充足可以考虑将序列化Object常驻内存中这样可以在需要使用时不必每次都要从硬盘中读取减少读取数据所带来的时间损耗但是这种方案也给内存管理带来了风险随着序列化数据的越来越多所占用的内存将会越来越多所以我们应该具体情况具体分析在有需要的时候使用这个方式。
4.Move common data into ScriptableObjects
如果我们有很多不同的prefab但是都带有包含了很多共享数据的脚本比如游戏策划使用的数值比如速度力量等这些数据都将会被序列化到每一个使用它们的prefab中。对于这种可以考虑将共享的数据通过ScriptableObject序列化成一个通用的数据这将减少序列化数据的量以及可以明显的缩短加载的时间。
5.Load scenes additively and asynchronously
加载场景可以使用替换掉当前场景的方式也可以使用增量加载的方式加载新增的内容到当前场景中不卸载之前的场景。可以通过勾选SceneManager.LoadScene中的LoadSceneMode来启用这个功能。
加载场景还可以选择是异步加载还是同步加载通常最好的方式是两种混合使用。
通过SceneManager.LoadScene可以进行同步加载同步加载将会阻塞主线程直到所需的场景完全加载完毕。这通常使得用户体验很差。同步加载最好用在我们想让用户尽快操作或者没有时间去等待场景物体出现时这通常被用在加载游戏的第一个场景或者返回到主菜单时。
通过SceneManager.LoadSceneAsync进行异步加载可以让场景的加载在背后偷偷进行用户没有明显的感知可以有效提升用户体验。
值得注意的是场景并不等同于游戏的关卡在大多数游戏中玩家在某一时刻只在一个关卡中但是Unity可以通过增量加载的方式支持多个场景同时被加载每个场景只是关卡中的一部分。例如我们可以在刚开始时加载第一个场景Scene-1-1a当玩家接近下一个区域时异步增量加载下一个场景Scene-1-1b当玩家在关卡中游玩时重复这个操作。
想实现这个功能需要一套可以实时检查关卡中player位置的系统当playe接近下个区域时异步增量加载下个场景但要注意的是异步加载需要一部分时间来处理也就是下个场景中的物体需要一些帧数后才加载好因此一定要保证在触发加载时有足够的时间提前量来让异步加载完成来避免用户看到物体是突然出现在场景中的。
场景可以通过卸载来释放内存。卸载同样也可以有两种方式同步卸载和异步卸载。卸载时要注意对于大场景如果进行卸载就会卸载全部物体如果想分部卸载需要将原始场景切分成多个小场景。卸载时还要注意确保玩家确实看不到该场景的所有内容否则玩家会看到物体突然消失的这种现象。另一个要注意的是卸载场景时会销毁物体释放很多内存有可能触发GC因此也需要对内存进行高效的管理来满足多场景加载的这种方案。
这种方案需要很强的场景设计代码编写测试等工作但是对于用户体验的提升也是很显著的平滑的场景区域过渡常常能收到用户和鉴赏家的赞赏如果方案能使用得当还可以大大提高运行时的性能表现更加提升了用户体验。
三. Create a custom Update() layer
假设上千个MonoBehaviour脚本在场景一开始一起进行初始化同时各自启动一个Coroutine来处理耗时500ms的AI运算任务它们会在同一帧中触发这很有可能产生CPU一个瞬时的巨大开销随后cpu的开销会降低等到下个AI运算循环时又会产生极大的CPU瞬时开销。
有三种解决方法
1每次生成随机的时间去等待Coroutine触发
2将Coroutine的初始化时间点分散开来来使得每一帧只有部分在处理
3用God Class来进行控制将调用Update的责任丢给God Class来限制每帧最多被调用的数量。
前两个方法非常有吸引力是因为非常简单但是这些方法会有不少隐患和副作用。
最好的方式就是根本不要用Update或者准确的说只用一次。Unity调用Update时会有很多副作用它需要之前提到过的Native-Managed Bridge来完成因此开销会比普通函数大很多大概是1000倍。因此我们应该尽可能减少调用Native-Managed Bridge自定义Update系统来替代调用Unity的Upate。
实际上很多Unity开发者很喜欢在项目之初就设计使用它们自己的Update系统这可以让他们控制Update何时在系统中传播控制菜单暂停控制重要tasks的优先级等比如发现在当前帧中cpu消耗超过了预算可以将低优先级的任务在之后帧中再运行。
接下来我们实现一个简易的Update 系统
1.IUpdateable接口规定了实现该接口的类需要定义OnUpdate函数 2.UpdateableComponet继承Monobehaviour以及IUpdateable接口定义OnUpdate的virtural方法以便继承类可以自定义实现该方法。
2.
3.注册Update系统 4.定义Initialize virtual函数为继承类提供初始化的功能避免继承类覆盖Start函数 5.用单例模式实现GameLogic Update系统 如果场景中有n个继承于UpdateableComponent的类通过我们自定义的Update系统可以将调用Native-Managed Bridge的次数从n次减少为1次效率将大大提升。这个系统还可以扩展成提供优先级功能的系统以及加入其他更多功能。
对于已经开发比较久的项目加入自定义的Update系统会是一件复杂耗时的工作但是带来的好处也是大大的我们可以自己评估花些时间来进行这部分改造是否值得。
四. Summary
这一章提供了许多Unity中关于提升编码实践的方法来提高性能。但是其中的一些技巧并不是什么时候都适用有些时候工作流的顺畅与性能和设计同样重要所以在决定使用哪些优化方法前需要先思考这些牺牲是否值得