Unity优化——脚本优化策略1

    Hello,大家好,这里是七七,今天来给大家介绍的是Unity脚本中的一些优化策略。

目录

一、最快方法获取组件

二、移除空的回调定义

三、缓存组件引用

四、共享计算输出

五、Update、Coroutines和InvokeRepeating


一、最快方法获取组件

GetComponent()方法有一些变体,它们的性能消耗不同,因此要谨慎地调用该方法的最高版本。

3个可用的重载版本是GetComponent(string),GetComponent<T>()和GetComponent(typeof(T))。由于这些方法每年都做一些优化,因此最高效的版本取决于所使用的Unity版本。在Unity5的所有后续版本中,最好使用GetComponent<T>()变体。

通过测试最终发现,GetComponent<T>()比GetComponent(typeof(T))快一点点,而GetComponent(string)明显比另外两个方法慢得多。因此,我们应该确保永远都不用GetComponent(string)方法。除非万不得已。

二、移除空的回调定义

Unity中编写脚本的主要意义是在从MonoBehaviour继承的类中编写回调函数,Unity会在必要时调用它们。最常用的4个回调是Awake()、Start()、Update()和FixedUpdate()。

在第一次创建MonoBehaviour时调用Awake()。Start()在Awake()之后不久,但在第一个Update()之前调用。在场景初始化期间,每个MonoBehaviour组件的Awake()回调在Start()回调之前调用。

之后,每次渲染管线呈现一个新图像时,都会重复调用Update()。

最后,在物理引擎更新之前,调用FixedUpdate(),以固定时间间隔调用。

MonoBehaviour在场景中第一次实例化时,Unity会将任何定义好的回调添加到一个函数指针列表中,在关键时刻调用这个列表。然而,即使函数体是空的,Unity也会将其添加入列表。核心Unity引擎没有意识到这些函数体是空的,它只知道方法已经定义,而它必须获取方法,然后在必要时调用它。如果将这些空定义分散在整个代码库中,那么将浪费少量CPU。

解决方法很简单;删除空的回调定义。但在可扩展的代码库中查找这样的空定义可能比较困难,但如果使用一些基本的正则表达式(简称regex),应该能够相对容易地找到空的回调定义

下面的regex表达式应搜索出代码中的空Update()定义:

void\s*Update\s*?\(\s*?\)\s*?\n*?{\n*?\s*?\}

这个regex检查Update()回调的标准方法定义,同时包含可能分布在整个方法定义中的多于空白和换行符。

当然,上面的做法也可以查找非样板的Unity回调,例如OnGUI()、OnEndable()、OnDestroy()和LateUpdate()。唯一的区别是在新样本中自定义了Start()和Update()。

在Unity脚本中,性能问题最常见来源时执行以下操作,而误用Update()回调

  • 反复计算很少或从不改变的值
  • 太多的组件计算一个可以共享的结果
  • 执行工作的频率远超必要值

下面来谈谈直接解决这些问题的一些提示 

三、缓存组件引用

在Unity中编写脚本时,反复计算一个值是常见的错误,特别是在使用GetComponent()方法时。如果实在Update()里面,那么这个问题将会更加严重。更好的方法时在初始化过程中获取所需数据的引用,并将其保存,直到需要它们为止。

以这种方式缓存组件引用,就不必在每次需要它们时重新获取,每次都会节省一些CPU开销。代价是少量的额外内存消耗。

同样的技巧也适用于在运行时决定计算的任何数据块。不需要要求CCPU在每次执行Update()时都重新计算相同的值,因为可以将它存储在内存中,供将来参考。

四、共享计算输出

让多个对象共享某些计算结果,可节省性能开销;当然,只有这些计算都生成相同的结果,才有效。这种情形通常很容易发现,但是重构起来很困难,因此利用这种情况将非常依赖于实现方案。

示例包括在场景中找到对象,从文件中读取数据,解析数据(如XML或JSON),在大列表或深层的信息字典中找到内容,为一组AI对象计算路径,复杂的数学轨迹,光线追踪等。

每次执行一个昂贵的操作时,考虑是否从多个位置调用它,但总是得到相同的输出。如果是这样,重构就是明智的。最大的成本通常只是牺牲了一点代码的简洁性,尽管传递值可能会造成一些额外的开销。

请注意,通常很容易养成在基类中隐藏大型复杂函数的习惯,然后定义使用该函数的派生类,完全忘记了该函数的开销,因为我们很少再次查看改代码。最好使用Unity Profiler来指出,这个昂贵的函数可能调用了多少次,像往常一样,不要预先优化那些函数,除非已经证明这是一个性能问题。无论他有多昂贵,只要不超出性能限制,它就不是真正的性能问题。

五、Update、Coroutines和InvokeRepeating

另一个很容易养成的习惯是在Update()回调中以超出需要的频率重复调用某段代码。例如,开始情况如下:

void Update(){processAI();
}

processAI()可能是个复杂的任务,在每一帧中都调用了ProcessAI()。如果这个活动占用了太多的帧率预算,且任务完成的频率低于没有明显缺陷的每一帧,那么提高性能的一个好方法就是简单地减少ProcessAI()的调用频率。

 void Update(){_timer += _timer.deltaTime;if(_timer >= _aiProcessDelay){ProcessAI();_timer-=_aiProcessDelay;}}

这样改进减少了Update()的回调总成本,需要一些额外的内存来存储浮点数据。但最终Unity仍要调用一个空的回调函数。

这个函数是一个完美的示例,可以将它转换成协程,利用其延迟的调用属性。协程通常用于编写短事件序列的脚本,可以是一次性的,也可以是重复的操作。它们不应该与线程混淆,线程以并发方式在完全不同的CPU内核上运行,而且多个线程可以同时运行。相反,协程以顺序的方式在主线程上运行,这样在任何给定时刻都只处理一个协程,每个协程通过yield语句决定何时暂停和继续。下面的代码说明可以协程形式重写以上的Update()回调。

 void Update(){StartCorountine(ProcessAICoroutine);}IEnumerator ProcessAICoroutine(){while (true){ProcessAI();yeld return new WaitForSeconds(_aiProcessDelay);}}

这种方法的好处是,这个函数只调用_aiProcessDelay值指示的次数,在此之前它一直处于空闲状态,从而减少对大多数帧的性能影响。然而,这种方法有其缺点。

首先,与标准函数调用相比,启动协程会带来额外的开销成本(大约是标准函数调用的三倍),还会分配一些内存,将当前状态存储在内存中,直到下一次调用它。这种额外的开销也不是一次性的成本,因为协程经常不断地调用yield,这会一次又一次地造成相同的开销成本,所以需要确保降低频率的好处大于此成本。

其次,一旦初始化,协程的运行独立于MonoBehaviour组件中Uptdate()回调的触发,不管组件是否禁用,都将继续调用协程。如果执行大量的GameObject构建和析构操作,写成可能会显得很笨拙。

再次,协程会在包含它的GameObject变成不活动的那一刻自动停止,不管出于什么原因(无论它被设置为不活动的还是它的一个父对象被设置为不活动的)。如果GameObject再次设置为活动的,协程不会自动重新启动。

最后,将方法转换为协程,可减少大部分帧中的性能损失,但如果方法体的单次调用突破了帧率预算,则无论该方法的调用次数怎么少,都将超出预算。因此,这种方法最适用于如下情况:即由于在给定的帧中调用该方法的次数太多而导致帧率超出预算,而不是因为该方法本身太昂贵。这种情况下,我们别无选择,只能深入研究并改进方法本身的性能,或者减少其他任务的成本,将时间让给该方法,来完成其工作。

在生成协程时,有几种可用的yield类型。WaitForSeconds容易理解;协程在yield语句上暂停指定的秒数。但是,它并不是一个精确的计时器,所以当这个yield类型恢复执行时,可能会有少量的变化。

WaitForSecondsRealTime是另一个选项,与WaitForSeconds的唯一区别是,它使用未缩放的时间。WaitForSeconds与缩放的时间进行比较,后者收到全局Time.timeScale属性的影响。而WaitForSecondsRealTime则不是,因此,如果要调整时间缩放值,请注意使用哪种yield类型。

还有WaitForEndOfFrame选项,它在下一个Update()结束时继续,还有WaitForFixedUpdate,它在下一个FixedUpdate()结束时继续。最后,Unity5.3引入了WaitUntil和WaitWhile,在这两个函数中,提供了一个委托函数,协程根据给定的委托返回true或false分别暂停或继续。请注意,为这些yield类型提供的委托将对每个Update()执行一次,直到它们返回停止它们所需的布尔值,因此它们非常类似于在while循环过程中使用WaitForEndOfFrame的协程。当然,同样重要的是,所提供的委托函数执行起来不会太昂贵。

委托函数是C#中非常有用的结构,允许将本地方法作为参数传递给其他方法,通常用于回调

 某些Update()回调的编写方式可以简化为简单的协程,这些协程总是在其中一种类型上调用yield,但应该注意前面提供的缺点。协程很难调试,因此它们不遵循正常的执行流程;在调用栈上没有调用者。可以直接指责为什么协程在给定的时间触发,如果协程执行复杂的任务,与其他子系统交互,就会导致一些很难察觉的缺陷,因为他们在其他代码不希望的时刻触发,这些缺陷也往往是及其难重现的类型。如果希望使用协程,最好使它们尽可能简单,且独立于其他复杂的子系统。

事实上,如果在上面的实例中协程很简单,可以归结为一个while循环,总是在WaitForSeconds或WaitForSecondsRealTime上调用yield,则通常可以替换成InvokeRepeating()调用,它的建立更简单,开销成本略小。下面的代码在功能上与前面使用协程定期调用ProcessAI()方法的实现方案相同:

  void Start(){InvokeRepeating("ProcessAI", Of, _aiProcessDelay);}

InvokeRepeating()和协程之间的一个重要区别是,InvokeRepeating()完全独立于MonoBehaviour和GameObject的状态。停止InvokeRepeating()调用的两种方法:

  • 调用CancelInvode(),它停止给定的MonoBehaviour发起的所有InvokeRepeating()回调
  • 销毁关联的MonoBehaviour或它的父GameObject。禁用MonoBehaviour或GameObject都不会停止InvokeRepeating()

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

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

相关文章

面试题:工作中做过 JVM 调优吗?怎么做的?

文章目录 前言cpu占用过高死锁内存泄漏上面只是其中一种处理方法 总结 前言 最近很多小伙伴跟我说&#xff0c;自己学了不少JVM的调优知识&#xff0c;但是在实际工作中却不知道何时对JVM进行调优。今天&#xff0c;我就为大家介绍几种JVM调优的场景。 在阅读本文时&#xff…

github使用token认证

向github提交代码时报错&#xff1a;Support for password authentication was removed on August 13, 2021. Please use a personal access token instead。大概意思就是&#xff0c;原先的密码凭证从2021年8月13日开始就不能用了&#xff0c;后续必须使用个人访问令牌&#x…

死磕Nacos系列:Nacos在我的SpringCloud项目中做了什么?

Nacos服务注册 我们一个SpringCloud项目中集成了Nacos&#xff0c;当项目启动成功后&#xff0c;就可以在Nacos管理界面上看到我们项目的注册信息&#xff0c;还可以看到项目的健康状态等等信息&#xff1a; 那Nacos是什么时候进行了哪些操作的呢&#xff1f;今天我们来一探究…

【Web安全】sql注入绕过技法

sql注入绕过技法 1. 注释符号绕过 原理&#xff1a;SQL注释符号&#xff08;如--, /* */&#xff09;可以用来忽略查询的一部分&#xff0c;特别是在注入点之后的部分。这对于绕过需要闭合的查询或移除查询余下部分的情况特别有用。 -- 注释内容 # 注释内容 /*注释内容*/ ;2…

redis运维(二十一)redis 的扩展应用 lua(三)

一 redis 的扩展应用 lua redis加载lua脚本文件 ① 调试lua脚本 redis-cli 通过管道 --pipe 快速导入数据到redis中 ② 预加载方式 1、错误方式 2、正确方式 "案例讲解" ③ 一次性加载 执行命令&#xff1a; redis-cli -a 密码 --eval Lua脚本路径 key …

希尔伯特变换-matlab仿真

希尔伯特变换&#xff08;hilbert transform&#xff09;简介 在信号处理中我们常见的有傅里叶变换&#xff0c;用来分析频域信息&#xff0c;还有拉普拉斯变换和z变换&#xff0c;用于系统分析系统响应。短时傅里叶分析和小波分析用于时频分析。希尔伯特变换似乎听到的比较少…

jQuery_04 jQuery选择器应用

jQuery中的选择器 1.基本选择器 1.1 id $("#id值") id名称 1.2 class $(".class值") class名称 1.3 标签选择器 $("标签名字") 标签名称 1.4 所有选择器 $("*") 所有标签 1.5 组合选择器 …

Selenium技巧大揭秘:动态数据、分页和Cookie的获取利器

背景&#xff1a; ​ 昨天我们讲了讲关于seleium的一些基础操作&#xff0c;今天讲讲如何将seleium和爬虫结合起来&#xff0c;可以使用selenium获取网页的动态加载数据&#xff0c;可以使用selenium获得cookie&#xff0c;这两个是比较常用的。我将一一展开。 实战案例&…

基于浣熊算法优化概率神经网络PNN的分类预测 - 附代码

基于浣熊算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于浣熊算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于浣熊优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

网络运维与网络安全 学习笔记2023.11.24

网络运维与网络安全 学习笔记 第二十五天 今日目标 DHCP中继代理、三层交换机DHCP、子网划分的原理、子网划分的应用 项目需求分析、技术方案选型、网络拓扑绘制 基础交换网络设计、内网优化、连接外网服务器 DHCP中继代理 DHCP中继概述 场景&#xff1a; DHCP客户端与DH…

Java LCR 089 打家劫舍

题目链接&#xff1a;打家劫舍 定义一个数组 dp&#xff0c;其中 dp[i] 表示从第 0 间房子到第 i 间房子&#xff08;包括第 i 间&#xff09;能够偷窃到的最高金额。 对于第 i 间房子有两种选择&#xff0c;偷或不偷&#xff1a; 偷就不能偷第 i - 1 间房子&#xff1a; dp[i]…

中职网安-Linux操作系统渗透测-Server2130(环境加qq)

B-9:Linux操作系统渗透测 任务环境说明:  服务器场景:Server2130  服务器场景操作系统:Linux(关闭链接) 1.通过本地PC中渗透测试平台Kali对靶机场景进行系统服务及版本扫描渗透测试,并将该操作显示结果中Apache服务对应的版本信息字符串作为Flag值提交; 2.…

中间件渗透测试-Server2131(解析+环境)

B-10&#xff1a;中间件渗透测试 需要环境的加qq 任务环境说明&#xff1a; 服务器场景&#xff1a;Server2131&#xff08;关闭链接&#xff09; 服务器场景操作系统&#xff1a;Linux Flag值格式&#xff1a;Flag&#xff5b;Xxxx123&#xff5d;&#xff0c;括…

【Netty专题】Netty调优及网络编程中一些问题补充(面向面试学习)

目录 前言阅读对象阅读导航笔记正文一、如何选择序列化框架1.1 基本介绍1.2 在网络编程中如何选择序列化框架1.3 常用Java序列化框架比较 二、Netty调优2.1 CONNECT_TIMEOUT_MILLIS&#xff1a;客户端连接时间2.2 SO_BACKLOG&#xff1a;最大同时连接数2.3 TCP_NODELAY&#xf…

rfc4301- IP 安全架构

1. 引言 1.1. 文档内容摘要 本文档规定了符合IPsec标准的系统的基本架构。它描述了如何为IP层的流量提供一组安全服务&#xff0c;同时适用于IPv4 [Pos81a] 和 IPv6 [DH98] 环境。本文档描述了实现IPsec的系统的要求&#xff0c;这些系统的基本元素以及如何将这些元素结合起来…

【数据结构】树与二叉树(廿四):树搜索给定结点的父亲(算法FindFather)

文章目录 5.3.1 树的存储结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法1. 获取大儿子、大兄弟结点2. 搜索给定结点的父亲a. 算法FindFatherb. 算法解析c. 代码实现 3. 代码整合 5.3.1 树的存储结构 5. 左儿子右兄弟链接结构 【数据结构】树与二叉树&#xff08;十九&…

没搞懂二维差分是什么怎么办???

摸鱼的时候画的&#xff0c;根据公式反推 一维差分倒是懂了 a[10]{1,2,6,9,11,12,17,21,32,67}; c[10]{1,1,4,3,2,1,5,4,11,35}; 现要把[3,7]的值都增加3 c[10]{1,1,7,3,2,1,5,1,11,35}; 要查询的时候再用for循环相加 结论&#xff1a;成立且适用于多次修改 不知道为什么这个…

抖音生态融合:开发与抖音平台对接的票务小程序

为了更好地服务用户需求&#xff0c;将票务服务与抖音平台结合&#xff0c;成为了一个创新的方向。通过开发票务小程序&#xff0c;用户可以在抖音平台上直接获取相关活动的票务信息&#xff0c;完成购票、预订等操作&#xff0c;实现了线上线下的有机连接。 一、开发过程 1…

h5小游戏-盖楼游戏

盖楼游戏 一个基于JavaScrtipt、Html5 的盖楼游戏 效果预览 点我下载源代码 Game Rule 游戏规则 以下为默认游戏规则&#xff0c;也可参照下节自定义游戏参数 每局游戏生命值为3&#xff0c;掉落一块楼层生命值减1&#xff0c;掉落3块后游戏结束&#xff0c;单局游戏无时间限…

springboot+vue基本微信小程序的旅游社系统

项目介绍 现今市面上有关于旅游信息管理的微信小程序还是比较少的&#xff0c;所以本课题想对如今这么多的旅游景区做一个收集和分类。这样可以给身边喜欢旅游的朋友更好地推荐分享适合去旅行的地方。 前端采用HTML架构&#xff0c;遵循HTMLss JavaScript的开发方式&#xff0…