对于给定的帧率(以帧/秒 (FPS) 为单位),每一帧的持续时间往往会有所变化。这些变化可能很小。例如,在以 60__ FPS__见“第一人称射击游戏”和“每秒帧数”。
See in Glossary 运行的游戏中,实际的每秒帧数可能会有轻微波动,因此每帧的持续时间在 0.016 秒到 0.018 秒之间。当应用程序执行大量计算或进行垃圾收集时,或者当其他应用程序在竞争资源时,就可能出现较大的波动。
Time.time 表示自应用程序启动以来已流逝的时间,因此通常会持续且稳定地增加。Time.deltaTime 表示自上一帧以来已流逝的时间,因此理想情况下保持相当恒定。这两个值都基于游戏内而非实时,这意味着它们会考虑所应用的任何时间缩放。例如,如果将 Time.timeScale 设置为 0.1 以获得慢动作效果,那么 Time.time 值将以实时速率的 10% 增加。实时 10 秒后,Time.time 的值会增加 1。
除了在游戏中放慢或加快时间,还可以将 Time.timeScale 设置为零以暂停游戏。在这种情况下,Unity 仍会调用 Update 方法,但 Time.time 完全不会增加,且 Time.deltaTime 为零。
这些值还会受到 Time.maximumDeltaTime 的值限制。这些属性所报告的任何暂停时长或帧率变化,都不会超过 Time.maximumDeltaTime。例如,如果发生一秒的延迟,但 maximumDeltaTime 设置为默认值 0.333,那么 Time.time 会仅仅增加 0.333,且 Time.deltaTime 等于 0.333,尽管实际时间已经过去了一秒。
每个属性的未缩放版本(Time.unscaledTime 和 Time.unscaledDeltaTime)会忽略这些限制,并报告这两种情况下的实际已流逝时间。对于即使游戏处于慢动作状态,也需要以固定速度响应的情况非常有用。比如__ UI__(即用户界面,User Interface)让用户能够与您的应用程序进行交互。Unity 目前支持三种 UI 系统。更多信息
See in Glossary 交互动画。
下表展示了一个示例,16 帧逐次播放,播放过程中,中间有一帧出现了明显的长时间延迟。下图说明了各种 Time 类属性如何报告和响应这种大幅的帧率变化。
| 帧 | unscaledTime | time | unscaledDeltaTime | deltaTime | smoothDeltaTime |
|---|---|---|---|---|---|
| 1 | 0.000 | 0.000 | 0.018 | 0.018 | 0.018 |
| 2 | 0.018 | 0.018 | 0.018 | 0.018 | 0.018 |
| 3 | 0.036 | 0.036 | 0.018 | 0.018 | 0.018 |
| 4 | 0.054 | 0.054 | 0.018 | 0.018 | 0.018 |
| 5 | 0.071 | 0.071 | 0.017 | 0.017 | 0.018 |
| 6 | 0.089 | 0.089 | 0.018 | 0.018 | 0.018 |
| 7 | 0.107 | 0.107 | 0.018 | 0.018 | 0.018 |
| 8 (a) | 1.123 (b) | 0.440 (c) | 1.016 (d) | 0.333 (e) | 0.081 (f) |
| 9 | 1.140 | 0.457 | 0.017 | 0.017 | 0.066 |
| 10 | 1.157 | 0.474 | 0.017 | 0.017 | 0.056 |
| 11 | 1.175 | 0.492 | 0.018 | 0.018 | 0.049 |
| 12 | 1.193 | 0.510 | 0.018 | 0.018 | 0.042 |
| 13 | 1.211 | 0.528 | 0.018 | 0.018 | 0.038 |
| 14 | 1.229 | 0.546 | 0.018 | 0.018 | 0.034 |
| 15 | 1.247 | 0.564 | 0.018 | 0.018 | 0.031 |
| 16 | 1.265 | 0.582 | 0.018 | 0.018 | 0.028 |
第 1 到 7 帧以每秒大约 60 帧的稳定速率运行。您可以看到 Time.time 和 Time.unscaledTime 一起稳步增加,表示 Time.timeScale 设置为 1。
第 8 (a) 帧上发生超过一秒的大延迟。发生资源竞争时,可能会发生这种情况。例如,某个操作在从磁盘加载大量数据时阻塞了主进程。
当帧延迟超过 Time.maximumDeltaTime 时,Unity 会限制 Time.deltaTime 报告的值以及添加到 Time.time 上的时间量。这样可以避免在时间步长超过该值时可能发生的不良副作用。如果没有这个限制,其移动由 Time.deltaTime 缩放的物体,理论上在两帧之间可以移动无限远的距离。这可能会导致画面故障,比如角色会毫无阻碍地穿过像墙壁这样的障碍物。
您可以在编辑器中通过更改时间 (Time) 窗口中的最大允许时间步长 (Maximum allowed timestep) 设置来调整 Time.maximumDeltaTime,也可以通过设置 Time.maximumDeltaTime 属性的值在代码中进行调整。
默认的 Time.maximumDeltaTime 值为三分之一秒 (0.3333333)。这意味着,在由 Time.deltaTime 控制移动的游戏中,一个物体从上一帧到下一帧的移动距离,会被限制在三分之一秒内该物体所能移动的距离,而不管自上一帧以来实际已流逝了多少时间。
以图表形式查看上表中的数据,有助于直观了解这些时间属性之间的关系:
在第 8 帧上,Time.unscaledDeltaTime (d) 和 Time.deltaTime (e) 在报告已流逝的时间上有所不同。尽管第 7 帧和第 8 帧之间经过了整整一秒的实时时间,但 Time.deltaTime 仅报告 0.333 秒。这是因为 Time.deltaTime 被限制为 Time.maximumDeltaTime 值。
同样,Time.unscaledTime (b) 大约增加了整整 1 秒,因为添加了真实的(无上限)值,而 Time.time (c) 仅增加了较小的上限值。Time.time 不会追赶上实际已流逝的时间,而是表现得好像延迟持续时间仅为 Time.maximumDeltaTime。
Time.smoothDeltaTime 属性根据一种算法,平滑掉所有变化后,给出近期 Time.deltaTime 值的近似值。这是另一种避免在物体移动或其他基于时间的计算中出现不良波动的方法。特别是那些低于由 Time.maximumDeltaTime 设置的阈值的波动。该平滑算法无法预测未来的变化,但它会逐渐调整其报告的值,以消除近期已流逝时间 Time.deltaTime 中的波动,使得平均报告时间大致等同于实际流逝的时间。
maximumDeltaTime 值也会影响固定更新循环。物理系统使用 Time.fixedDeltaTime 定义的固定时间间隔来确定在每个步骤中模拟的时间。Unity 会尝试使物理模拟与已逝时间保持同步,有时还会每帧执行多次物理更新。
但是,如果物理模拟严重滞后,物理系统可能需要执行大量步骤才能赶上当前时间。这些追赶步骤本身可能会导致额外的卡顿。为了避免出现卡顿的恶性循环,Time.maximumDeltaTime 值还会限制物理系统在任意两帧之间进行模拟的时间量。
如果帧更新的处理时间超过 Time.maximumDeltaTime,物理引擎将不会尝试模拟超出的时间,而是让帧处理赶上进度。旦帧更新完成,物理模拟就会继续进行,就好像从它停止后根本没有时间流逝过一样。
这种情况下的结果是物理对象不会像正常情况下那样实时完美移动,而会稍微减慢。不过,物理系统仍会像它们正常移动那样对其进行追踪。物理时间的变慢通常不太容易被察觉,而且与游戏玩法的性能相比,这往往是一种可以接受的权衡。
以下流程图说明了 Unity 用于计算单帧时间的逻辑,以及 time、deltaTime、fixedDeltaTime 和 maximumDeltaTime 属性之间的相互关系。