Version: 2021.1
ScriptableObject
重要的类 - Mathf

Important Classes - Time and frame rate management

Time 类

Unity 的 Time 类提供许多重要的基本属性,允许您在项目中使用与时间相关的值。

时间脚本参考页面每个成员的描述是不言自明的,这是了解它们的最佳场所。

这里列出了一小部分示例,以提供常见和典型用途的概念:

Time.time 返回自项目开始播放以来的时间量。

Time.deltaTime 返回自上一帧完成以来经过的时间量。

Time.timeScale 表示时间流逝的速率。您可以读取此值,或将其设置为控制时间流逝的速度,从而创建慢动作效果。

有关时间相关属性的完整列表,请参阅时间脚本参考页面

Frame rate management

The Update function allows you to monitor inputs and other events regularly from a script and take appropriate action. For example, you might move a character when the “forward” key is pressed. An important thing to remember when handling time-based actions like this is that the game’s frame rate is not constant and neither is the length of time between Update function calls.

举例来说,假设在一项任务中需要逐步向前移动某个对象,一次一帧。起初看起来好像可以在每帧将对象移动一个固定距离:

//C# 脚本示例
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public float distancePerFrame;
    
    void Update() {
        transform.Translate(0, 0, distancePerFrame); // 这不正确
    }
}

但是,如果帧时间不是恒定的,那么对象看起来会以不规则的速度移动。如果帧时间为 10 毫秒,那么对象将以 distancePerFrame 的距离每秒前进一百次。但如果帧时间增加到 25 毫秒(比如由于 CPU 负载的原因),那么对象每秒只会前进四十次,因此移动的总距离更短。解决方案是通过可从 Time.deltaTime 属性读取的帧时间来缩放移动距离大小:

//C# 脚本示例
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public float distancePerSecond;
    
    void Update() {
        transform.Translate(0, 0, distancePerSecond * Time.deltaTime);
    }
}

Note that the movement is now given as distancePerSecond rather than distancePerFrame. As the frame rate changes, the size of the movement step will change accordingly and so the object’s speed will be constant.

Depending on your target platform, use either Application.targetFrameRate or QualitySettings.vSyncCount to set the frame rate of your application. For more information, see the Application.targetFrameRate API documentation.

固定时间步长 (fixedDeltaTime)

与主帧更新不同,Unity 的物理系统_会_工作到固定的时间步长,这对于模拟的准确性和一致性很重要。在物理更新的开始,Unity 会根据需要执行尽可能多的物理更新以追上当前时间。

如果您的游戏或应用的帧率运行以比固定时间步长值高,这意味着每一帧的持续时间都小于一个固定时间步长,因此 Unity 每帧将执行一次或零次固定物理更新。例如,如果您的固定时间步长值为 0.02,则每秒应该有 50 次固定更新。如果您的游戏或应用随后以大约 60 帧/秒的速度运行,则偶尔会有一帧没有发生固定更新 - 大约十分之一的帧。

如果您的游戏或应用的帧率运行以比固定时间步长值低,这意味着每一帧的持续时间都大于一个固定时间步长。为了解决这个问题,Unity 每帧将执行一次或多次固定物理更新,以便物理模拟“追上”自上一帧以来经过的时间量。例如,如果您的固定时间步长值为 0.01,则每秒应该有 100 次固定更新。如果您的游戏或应用随后以大约 25 帧/秒的速度运行,Unity 每帧将执行大约四次固定更新,以使物理模拟时间与当前帧时间保持同步。

可从 Time 窗口中更改固定时间步长的值,并可使用 Time.fixedDeltaTime 属性从脚本中读取该值。请注意,较低的时间步长值将产生更频繁的物理更新和更精确的模拟,但代价是更大的 CPU 负载。除非要对物理引擎提出很高的要求,需要非常高的精度,否则可能不需要更改默认的固定时间步长。

Unity 的时间逻辑

以下流程图说明了 Unity 用于计算单个帧中时间的逻辑,以及 timedeltaTimefixedDeltaTimemaximumDeltaTime 属性如何相互关联。

Maximum Allowed Timestep (maximumDeltaTime)

当发生非常慢的帧时,Maximum Allowed Timestep(在 Time 窗口中)会限制下一帧中的 deltaTime 值,以避免 deltaTime 值过大而导致的不良副作用。该值也可以通过 Time.maximumDeltaTime 编写脚本进行访问。

例如,一个以 60Hz 频率运行、maximumDeltaTime 值为 0.333 的应用程序遇到一个非常慢的帧(2 秒)会表现出类似于下表中所示的行为:

Frame unscaledTime time deltaTime smoothDeltaTime
1 0.000 3.000 0.014 0.014
2 0.014 3.015 0.014 0.014
3 0.028 3.028 0.014 0.014
4 (a) 0.042 3.043 0.014 0.014
5 2.062 (b) 3.376 (c) 0.333 (d) 0.078
6 2.082 3.396 0.020 0.066
7 2.096 3.410 0.014 0.056
8 2.110 3.424 0.014 0.048

上面的数据说明了最大允许时间步长值 (maximumDeltaTime) 的影响。它显示 8 个帧,编号为 1 到 8。在此示例中的第四帧 (a) 期间,有一个减速,导致该帧需要两秒才能完成,而不是像其他帧那样大约需要 25 毫秒。其效果体现在下一帧的数值上。在 unscaledTime 值 (b) 处可以看到大约增加了 2 秒,但是 Time.time 值仅增加 0.333 (c),因为该值是本示例运行时的最大允许时间步长。此帧的 deltaTime 值也被限制在最大允许时间步长 (d)。

最大允许时间步长值也会影响物理计时。固定时间步长使物理模拟能够实时保持准确,但是在游戏大量使用物理系统并且游戏帧率也变低的情况下(例如,由于游戏中存在大量运动的对象),可能会导致问题。必须在常规物理更新之间“挤压”主帧更新处理;如果要进行大量处理,则可能需要在单个帧期间进行多个物理更新,以使物理模拟追上当前时间。由于帧时间、对象位置和其他属性在帧开始时被冻结,因此图形可能与更频繁更新的物理系统不同步。

有限 CPU 处理能力意味着物理系统的处理能力也是有限的,但 Unity 可以选择让您有效地减慢物理时间,让帧处理能够在落后时赶上来。最大允许时间步长可限制 Unity 在指定的帧更新期间处理物理系统和 FixedUpdate 调用所花费的时间。

如果帧更新花费的时间超过 Maximum Allowed Timestep 设置,则物理引擎将“让时间停止”并让帧处理赶上。一旦帧更新完成,物理引擎将恢复,就好像让时间停止后没有流逝一样。这种情况下的结果是刚体不会像正常情况下那样实时完美移动,而会稍微减慢。然而,物理“时钟”仍将跟踪它们,就像它们正常移动一样。物理时间的减慢通常是不明显的,并且通常是针对游戏性能的合理折衷。

时间尺度

对于特殊效果,例如“子弹时间”,有时减慢游戏时间的流逝会很有用,能够使动画和脚本响应以较低的速率发生。此外,有时可能希望完全冻结游戏时间,就像游戏暂停时一样。Unity 有一个 Time Scale 属性可以控制游戏时间相对于实时时间的进展速度。如果该标度设置为 1.0,则游戏时间与实时时间匹配。值为 2.0 会使 Unity 中的时间流逝速度加倍(即,动作将加速),而值为 0.5 则会将游戏速度减半。值为零将使时间完全“停止”。请注意,时间标度实际上并不会降低执行速度,而只是更改了通过 Time.deltaTimeTime.fixedDeltaTime 报告给 Update 和 FixedUpdate 函数的时间步长。当游戏时间减慢时,调用 Update 函数的频率可能高于平常,但每帧报告的 deltaTime 步长将会缩短。其他脚本函数不受时间标度的影响,因此您可以在游戏暂停时显示具有正常交互的 GUI。

Time 窗口有一个属性可用于全局设置时间标度,但使用 Time.timeScale 属性从脚本设置该值通常更有用:

//C# 脚本示例
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void Pause() {
        Time.timeScale = 0;
    }
    
    void Resume() {
        Time.timeScale = 1;
    }
}

Capture Framerate

A very special case of time management is where you want to record gameplay as a video. Since the task of saving screen images takes considerable time, the usual frame rate of the game will be drastically reduced if you attempt to do this during normal gameplay. This will result in a video that doesn’t reflect the true performance of the game.

Fortunately, Unity provides a Capture Framerate property that lets you get around this problem. When the property’s value is set to anything other than zero, game time will be slowed and the frame updates will be issued at precise regular intervals. The interval between frames is equal to 1 / Time.captureFramerate, so if the value is set to 5.0 then updates occur every fifth of a second. With the demands on frame rate effectively reduced, you have time in the Update function to save screenshots or take other actions:

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    // Capture frames as a screenshot sequence. Images are
    // stored as PNG files in a folder - these can be combined into
    // a movie using image utility software (eg, QuickTime Pro).
    // The folder to contain our screenshots.
    // If the folder exists we will append numbers to create an empty folder.
    string folder = "ScreenshotFolder";
    int frameRate = 25;
        
    void Start () {
        // Set the playback frame rate (real time will not relate to game time after this).
        Time.captureFramerate = frameRate;
        
        // Create the folder
        System.IO.Directory.CreateDirectory(folder);
    }
    
    void Update () {
        // Append filename to folder name (format is '0005 shot.png"')
        string name = string.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );
        
        // Capture the screenshot to the specified file.
        Application.CaptureScreenshot(name);
    }
}

尽管使用这种技术录制的视频通常看起来非常好,但是当速度减慢时,游戏很难运行。您可能需要尝试使用 Time.captureFramerate 的值来确保充足的录制时间,但不会过度复杂化测试播放器的任务。

ScriptableObject
重要的类 - Mathf