在实际使用Update和FixedUpdate时,遇到一些操作在FixedUpdate不生效的情况。在网上找了一圈要么是一些还没官方文档通俗易懂的定义,要么没有解释到位没能解惑,于是自己根据官方文档分析并通过自己测试做出一些总结。
一、定义
先放出官方解释:https://docs.unity.cn/cn/2021.2/ScriptReference/MonoBehaviour.FixedUpdate.html
Update:按实际帧率调用,受运行时设备的性能及一帧需要渲染物件数量和质量的影响。 游戏从场景简单的地方移动到场景复杂的地方时会出现卡顿,往往就是一帧中加载的东西突然增加,加载时间变长导致帧数变少。
FixedUpdate:按固定的帧率调用,根据设置步长固定间隔的执行。例如:设置50帧,那么不管实际帧中一帧执行了多少秒,FixedUpdate固定每0.02s调用一次。
二、deltaTime和fixedDeltaTime
Time.deltaTime在官方的定义为:完成上一帧所用的时间(以秒为单位),用通俗易懂的话来描述就是,每一帧所间隔的时间。而Time.fixedDeltaTime也能适用于该解释。
例如:50fps,那么对于fixedDeltaTime就是一个固定值0.02。而deltaTime会受设备性能和渲染情况影响,可能前10帧不稳定足足加载了0.4s,平均每帧0.04s,剩下的40帧平均一下就只剩0.015s了。
在Update中使用位移相关的逻辑时,就会有人建议用速度 Speed * Time.deltaTime 使操作边顺滑,这就类似于权重计算,因为1s的Time.deltaTime的和为1,这就确保了每秒位移的距离是相同的。但这么使用实际上只是解决了单位时间位移不同的问题,在帧率变换明显时这种做法还是会显得卡顿。
想象一下,你的角色正在以匀速10m/s的速度狂奔,突然跑到了新环境帧率猛地降到了10fps,第1帧加载了0.6s,后9帧加载0.4s,虽然因为使用了Speed * Time.deltaTime使得你的角色这一秒和前面一样都跑了10m,但毫无疑问你会看到你的角色闪现了一下。
基于上述情况,把处理物理逻辑的代码放到固定时间执行的FixedUpdate中处理会更加流畅,不会因为实际帧率的变化导致刷新角色行为变得时快时慢(简单说就是一卡一卡的,甚至发生“闪现”)。
三、FixedUpdate中跳跃失效问题
那么一股脑把物理逻辑的代码都放入FixedUpdate就行了吗?有问题!
例如控制角色跳跃(Jump)的KeyDown或ButtonDown放在FixedUpdate中,会出现 按键没有反应 或 按下一次键执行了多次 的情况。根据官方文档对FixedUpdate的解释不难推测,FixedUpdate的执行是基于实际帧的。
实际帧率100fps > 固定帧率 50fps的情况 ,FixedUpdate是较为均匀的在这100帧中每2帧执行一次。如果KeyDown或ButtonDown事件触发在实际帧的1帧,3帧,4帧,7帧...,而FixedUpdate是在实际帧的2帧,4帧,6帧,8帧执行,那么就会出现“丢帧”,漏掉了1帧,3帧,7帧,只执行了第4帧的事件。
实际帧率25fps < 固定帧率50fps的情况,FixedUpdate就在每0.02s较为均匀的多次执行实际帧内容。如果KeyDown或ButtonDown事件触发在实际帧的 1帧(0.05s),2帧(0.03s), 3帧(0.06s)总计0.14s,而FixedUpdate就可能在这0.14s中执行了7次实际帧触发的事件,导致3次按键产生了7次响应。
这里提供一种验证方式,在FixedUpdate中写一个KeyDown或ButtonDown事件,分别调节FixedUpdate的固定帧率大致为实际帧率的0.25倍,0.5倍,1倍,1.5倍,2倍,然后快速或匀速按键。结果会发现按键次数和打印日志次数跟这个倍数相符合。(比如:固定帧率为实际帧率的0.25倍(1/4),那么就能看到大概按键4次打印一次结果)
void FixedUpdate() { if (Input.GetKeyDown(KeyCode.Q)) { print("Q key pressed"); } }
图中为固定帧率和实际帧率近似1:1的情况
四、对Update和FixedUpdate的一些使用建议
基于上述结论,把物理逻辑部分放在FixedUpdate中时,
如果涉及到一些单次按键的情况,考虑到可能会出现“丢帧”,可以把这些单次按键的输入检测放在Update中,确保能捕获到该事件。
void Update() { if(Input.GetKeyDown(KeyCode.Q)) isPressed = true; } void FixedUpdate() { if (isPressed) { //执行触发按键的一些逻辑 isPressed = false; } }