Unity 2017 Game Optimization 读书笔记 Scripting Strategies Part 5

一. 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的序列化系统主要用于场景,prefabs,ScriptableObjects和各种各样的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中关于提升编码实践的方法,来提高性能。但是其中的一些技巧并不是什么时候都适用,有些时候工作流的顺畅与性能和设计同样重要,所以在决定使用哪些优化方法前,需要先思考这些牺牲是否值得

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246164.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码实现sql编译器_TiDB-Wasm 原理与实现 | Hackathon 优秀项目介绍

作者:Ti-Cool上周我们推送了《让数据库运行在浏览器里?TiDB WebAssembly 告诉你答案》,向大家展示了 TiDB-Wasm 的魅力:TiDB-Wasm 项目是 TiDB Hackathon 2019 中诞生的二等奖项目,实现了将 TiDB 编译成 Wasm 运行在浏…

C# int[,] 和 int[][]

原文链接:https://www.cnblogs.com/ILoveMyJob/p/9211102.html

java停车收费系统 源码开源_Java开源商城源码推荐,从菜鸡到大神,永远绕不开的商城系统

每个Java程序员,从懵逼菜鸡,再到懵懂菜鸟,再到小鸟,大鸟,最后到技术大神,始终绕不开商城系统,里面蕴含了大量的业务,涉及到了大量的知识点和解决方案。今天介绍一款Java开源商城源码…

安卓ps2模拟器_安卓PSP模拟器评测:合金装备 和平步行者

哈喽,大家好,好久没有玩PSP模拟器了,昨晚下载了一款PSP游戏的3A大作,这款游戏名字叫做《合金装备 和平步行者》,这款游戏发售时间是:2010年4月29日。本作是真真正正由小岛秀夫亲自主刀开发的系统正统作品&a…

UnityEngine.UI.dll 路径

2019.2之后,路径已经从 Unity安装目录下的Editor\Data\UnityExtensions移动到了 package里,通过package编译后生成的dll在工程目录下的library里

c++ 异步下获取线程执行结果_这份阿里技术官强推的java线程池笔记,建议你看一下

线程池线程是宝贵的内存资源,单个线程占1MB空间,过多分配易造成内存溢出频繁的创建及销毁线程会增加虚拟机回收频率、资源开销、造成程序性能下降因此线程池出现了线程池的概念线程容器,可设定线程分配的数量上限将预先创建的线程对象存入池中,并重用线程池中的线程对象避免频繁…

此应用无法在你的电脑上运行_能直运行iOS应用!苹果新macOS翻车 正式版下载后无法安装...

原标题:能直运行iOS应用!苹果新macOS翻车 正式版下载后无法安装 今天,苹果正式推出了macOS Big Sur正式版,其是苹果为Mac电脑设计的下一代操作系统,最大的变化是在性能上,因为Big Sur是第一个以苹果自研处理…

springcloud 整合 gateway_从Spring Cloud到Kubernetes的微服务迁移实践

写在前面要出发周边游(以下简称要出发)是国内知名的主打「周边游」的在线旅行网站,为了降低公司内部各个业务模块的耦合度,提高开发、交付及运维效率,我们在 2017 年就基于 Spring Cloud 完成了公司内部业务微服务化的…

leetcode 191. 位1的个数

经典题目,位操作的小技巧:与操作 我们可以把前面的算法进行优化。我们不再检查数字的每一个位,而是不断把数字最后一个 1 反转,并把答案加一。当数字变成 00 的时候偶,我们就知道它没有 1 的位了,此时返回…

程序员如何跟领导提离职_如何优雅地跟老板提加薪?按照这3个步骤来,也不是什么难事...

通常情况下,如果你在一个制度比较健全的单位上班,基本上不需要自己提出加薪的要求,达到规定的条件自然就涨工资了。但是我们绝大部分人,是在中小企业上班,如果不跟老板提加薪要求,老板很难主动给你涨工资。…

leetcode 打印从1到最大的n位数

书上原题本想考的是大数问题,但是leetcode上要求的返回值是int,不可能出现当n过大时大数越界的问题, 失去了本题的意义。按大数问题处理,用string和递归来处理该问题。 public class Solution {public int[] PrintNumbers(int n) …

强制关机对电脑的影响_电脑强制关机,对电脑有影响吗?你被伪科普骗了多久?...

相信大多数人都会遇到这么个情况,就是电脑用着用着死机、卡顿,在毫无反应又动弹不得的情况下,采用的招数就是长按电源键10秒强制关机重启。这时候身边的“电脑高手”就会告诉我们说,这样关机会毁害电脑硬件。但它却是处理电脑假死…

vb.net限制软件使用次数_新增投屏及倍数播放,这款软件iOSAndroid全都有,影视神器,抓紧体验...

小小影视 Android、iOS版小小影视以前也分享过,最近貌似版本更新了,新增投屏和倍数播放功能,还没用过的可以下载体验!小小影视APP是一款最全,最新,关键不止有Android版,还有iOS版的影音播放软件…

leetcode 调整数组顺序使奇数位于偶数前面

解法一&#xff1a;双指针 public int[] Exchange(int[] nums) {int head 0;int tail nums.Length - 1;while(head < tail){if((nums[head] & 1) 1){head;continue;}else if((nums[tail] & 1) 0){tail--;continue;}else{int temp nums[head];nums[head] nums[…

pytorch test单张图片_PyTorch版EfficientDet比官方TF实现快25倍?这个GitHub项目数天狂揽千星...

EfficientDet 难复现&#xff0c;复现即趟坑。在此 Github 项目中&#xff0c;开发者 zylo117 开源了 PyTorch 版本的 EfficientDet&#xff0c;速度比原版高 20 余倍。如今&#xff0c;该项目已经登上 Github Trending 热榜。机器之心报道&#xff0c;项目作者&#xff1a;zyl…

Xcode and Unity missing library ‘lGoogleUtilities‘

https://stackoverflow.com/questions/58187800/xcode-and-unity-missing-library-lgoogleutilities

c++ 字符串合并_C语言输入字符和字符串(所有函数大汇总)

C语言输入字符和字符串(所有函数大汇总)C语言有多个函数可以从键盘获得用户输入&#xff0c;它们分别是&#xff1a;scanf()&#xff1a;和 printf() 类似&#xff0c;scanf() 可以输入多种类型的数据。getchar()、getche()、getch()&#xff1a;这三个函数都用于输入单个字符。…

appimage文件怎么安装_bauh:在一个界面中管理 Snap、Flatpak 和 AppImage | Linux 中国...

幸运的是&#xff0c;我偶然发现了一个支持这几种通用包格式的应用程序。-- John PaulSnap、Flatpak 和 AppImage 等通用软件包的最大问题之一就是管理它们。大多数内置的软件包管理器都不能全部支持这些新格式。幸运的是&#xff0c;我偶然发现了一个支持这几种通用包格式的应…

字符编码笔记:ASCII,Unicode 和 UTF-8

作者&#xff1a; 阮一峰 日期&#xff1a; 2007年10月28日 今天中午&#xff0c;我突然想搞清楚 Unicode 和 UTF-8 之间的关系&#xff0c;就开始查资料。 这个问题比我想象的复杂&#xff0c;午饭后一直看到晚上9点&#xff0c;才算初步搞清楚。 下面就是我的笔记&#x…