Update、FixedUpdate、LateUpdate函数,定时器和帧数率

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

1 Update、LateUpdate、FixedUpdate函数

Unity将每帧调用一次 Update()函数。这意味着,如果你以60fps的速度运行,那意味着每秒调用60次Update()函数。如果以50fps的速度运行,则每秒调用50次Update函数。这也就是说。每两次执行update函数之间的时间间隔,其实是变化不定的。正是由于这一点,使用Update()函数的最大问题是它可能与物理引擎不同步。调用行为可以改变,因为它可以结束更快或更慢,这取决于您的图形在渲染的硬件上放置了多少负载,使用物理更新可能会导致完全错误的反应。

Unity确实会尝试以项目所指定的速率调用FixedUpdate()函数。但是,这些调用也需要根据当前实际的帧速率去确定。这意味着虽然任何给定帧上可能有零次,一次甚至超过100次对FixedUpdate()函数的调用,但Unity会动态调整到实际帧速率。通过深入挖掘,我们逐渐意识到,这也意味着对FixedUpdate()的所有调用都不一定在指定的时间间隔内。下面举例说明下这种情况:

默认情况下FixedUpdate()函数的更新频率是50FPS(0.02s),如果当游戏的更新频率为60FPS时,那么FixedUpdate有固定的更新速率是很好理解的,因为这种绝对可以保证到,每执行一次FixedUpdate()函数,就至少执行了一帧,但是如果游戏本身的更新频率就很低,例如为30FPS时,每一帧的时间间隔明显大于0.02秒的,那么这时候在每两次Update()函数的调用之间。FixedUpdate()函数就被调用了多次。

  1. 通常FixedUpdate()函数比Update()函数更频繁地被调用。
  2. FixedUpdate()函数可以在一帧内被调用多次,如果帧速率很低,则根本不能在帧之间调用。
  3. 如果帧速率很高,也可能不会调用FixedUpdate()
  4. FixedUpdate()函数被调用之后立即进行所有物理计算和更新。
  5. 作用力,扭矩或其他与物理相关的函数时,应使用FixedUpdate函数。
  6. FixedUpdate()函数由一个准确可信的计时器调用,而与实际的帧速率无关,因此在FixedUpdate()中应用移动计算时,您不需要将您的值乘以Time.deltaTime。
  1. Update()函数是每一帧中执行更新操作的主要(入口)函数。
  2. Update()函数中用来移动非物理系统驱动的物体。
  3. Update()函数用来接受用户输入。
  4. Update()函数可用来实现简单的计时器。

由于Update()的执行方式,与物理引擎的执行步调不同,即可能在一帧中,FixedUpdate()函数执行了多次,而Update函数则只执行了一次。这取决于渲染器在渲染图形时的负载量,以及所耗费的CPU时间。如果在Update()函数中执行物理运算时,会导致未知的结果。同时也可以看到:函数的执行间隔,未必就是TimeManager界面中Fixed TimeStep项中所指定的值。

  1. LateUpdate()函数每帧会被调用一次,在Update()函数结束后调用
  2. 所有在Update()函数中执行的计算,将会在LateUpdate()函数调用之前完成执行。
  3. 如果实现一个跟随式的第三视角摄像机,则摄像机的状态更新放在LateUpdate()函数中执行是一个通用的做法。

因为你的角色是在Update()函数里面执行移动和转向的操作。所以在LateUpdate()函数执行摄像机的位置和旋转度计算,可以确保在计算时,角色已经全部完成了位置计算,摄像机可以正确地跟踪到角色的正确位置。

2 UnityEngine.Time类

2.1 Time.deltaTime

Time.deltaTime是类Time的一个公有静态的浮点类型成员变量

public static float deltaTime;

这个变量是只读的,返回本帧到上一帧流逝的时间,单位是秒Time.deltaTime的使用例子如下:
如果要沿着Y轴移动某个game object,那么假设移动速度为每秒n个单位,那么每一帧移动的距离distance为:

float n = 10.0f; // 每秒移动的速度
float distance = Time.deltaTime * n;

然后把计算结果distance累加到game object自身的local Position的y值上即可

GameObject o = GameObject.Find("AnObject");
var localPos = o.transform.localPosition
localPos.y += distance;
o.tranform.localPosition = localPos;

这样子,虽然不确定一秒内到底能执行了多少帧,但在一秒内每帧执行的时长是可以得知的。利用每帧之间的时长乘以每秒物体运行的速度,就可以得到该帧要发生的位移的距离值。最终无论帧速率如何,一秒钟内移动的距离是确定的。当然如果帧速率太低的话,物体移动时就会有很明显的跳跃感。比如一秒钟内要移动一百米,一秒钟内执行了30帧,和一秒钟内执行了1帧,其给用户的观感肯定有着非常明显的差异。

MonoBehaviour.FixedUpdate()函数中要使用fixedDeltaTime变量代替deltaTime.在MonoBehaviour.OnGUI()函数中不要依赖于Time.deltaTime进行计算,因为在一帧中OnGUI()中可能会被调用多次,那么每次在OnGUI()函数中拿到的deltaTime值就是一个定值,这显然会导致计算逻辑出错。

2.2 Time.timeScale

Time.timeScale属性可以用来控制Time.deltaTime的大小。如果Time.timeScale为1,Time.timeDelta的值为N。每帧之间流逝的时间为R。这时候:

N = R

当把Time.timeScale设置为0.5的时,每帧之间流逝的实际时间依然为R,但这时候Time.timeDelta则变成了0.5N。这时候每帧的移动距离distance,从原来的:

distance = N×moveSpeed

变成了:

distance = 0.5×N×moveSpeed

实际每帧所花费的时间还是R,但每帧移动的距离变小了。所以画面呈现了“慢动作”的效果。如果把Time.timeScale设置为2的时,每帧移动的距离变了,所以画面呈现了快进的效果。可参照示例工程

参考文档

https://aresthedog.com/unity-concepts-update-vs-fixedupdate-vs-lateupdate
FixedUpdate真的是固定时间片执行的吗?
初学unity Time.deltatime的理解
Unity3D研究院之Time.timeScale、游戏暂停(七十四)