你啥时候乘以 Unity 中的 Time.deltaTime?

Posted

技术标签:

【中文标题】你啥时候乘以 Unity 中的 Time.deltaTime?【英文标题】:When do you multiply by Time.deltaTime in Unity?你什么时候乘以 Unity 中的 Time.deltaTime? 【发布时间】:2022-01-17 14:01:35 【问题描述】:

关于何时应该或不应该将数量乘以 Time.deltaTime,似乎有很多令人困惑/相互矛盾的建议。所以我想知道,因为它与 Unity 物理系统有关,你应该什么时候乘以 Time.deltaTime

我了解 Time.deltaTime 用于使某些动作与帧速率无关,但在某些情况下,不清楚某个动作是否与帧速率无关。例如:

在 FixedUpdate 中执行 rigidbody.velocity += acceleration*Time.deltaTime;。由于 FixedUpdate 以恒定的时间步长运行,这是否使乘以 Time.deltaTime 变得不必要? 正在做rigidbody.AddForce(force*Time.deltaTime, forceMode);。各种ForceModes 对此有何影响?

我已经看到了针对个别情况的解决方案,但如果有一种简单的直觉可以解决所有这些情况,那就太好了。

【问题讨论】:

【参考方案1】:

直觉

乘以Time.deltaTime 用于使瞬时操作以连续(或“平滑”)方式进行。

瞬时操作会导致某个量“跳跃”到不同的值。以下操作是即时的

Rigidbody.[position|velocity|rotation|angularVelocity] += x; Rigidbody2D.[position|velocity|rotation|angularVelocity] += x; Rigidbody.Add[Force|Torque](x, ForceMode.[Impulse|VelocityChange]); Rigidbody2D.Add[Force|Torque](x, ForceMode2D.Impulse);

以上所有操作都会导致一个数量立即跳转x,与帧的长度无关。

相比之下,以下操作是连续的

Rigidbody.Add[Force|Torque](x, ForceMode.[Force|Acceleration]); Rigidbody2D.Add[Force|Torque](x, ForceMode.Force); Rigidbody.velocity = x;(从Rigidbody.position的角度来看是连续的。即设置速度不会使Rigidbody.position突然跳到一个新的值)

所有上述操作都应用于整个框架(至少在概念上会发生这种情况;物理系统在内部执行的操作超出了此答案的范围)。为了说明这一点,Rigidbody.AddForce(1, ForceMode.Force) 将在整个框架内将 1 牛顿的力施加到对象上;位置或速度不会突然跳跃。

那么你什么时候乘以Time.deltaTime

如果您使用的是连续操作,您几乎绝对应该乘以Time.deltaTime。但如果您使用的是瞬时操作,答案取决于该操作是否以连续方式使用。

示例 1:爆炸

void Explode() 
    rigidbody.velocity += explosionAcceleration;

在这里你应该乘以Time.deltaTime,因为Explode() 只被调用一次。它并不意味着应用于一系列帧。

示例 2:移动

void Update() 
    rigidbody.position += velocity * Time.deltaTime;

这里你必须乘以Time.deltaTime,因为对象应该随着时间连续移动,而且你还使用了瞬时操作。请注意,这可以替换为连续操作,并且无需乘以Time.deltaTime

void Update() 
    rigidbody.velocity = velocity;

虽然这并不完全等同于原始值,因为它忽略了 rigidbody.velocity 的先前值。

示例 3:加速

void Update() 
    rigidbody.AddForce(acceleration*Time.deltaTime, ForceMode.VelocityChange);

在这里,您应该乘以 Time.deltaTime,因为您希望速度以一致的速率逐帧增加。请注意,这完全等同于:

void Update() 
    rigidbody.AddForce(acceleration, ForceMode.Acceleration);

此代码在整个帧的过程中应用加速度。这段代码也(在我看来)更干净、更容易理解。

FixedUpdate 呢?

加入FixedUpdate 不会影响上述任何建议。是的,理论上你可以避免乘以Time.deltaTime,因为帧之间的时间总是相同的。但是你所有的单元都必须依赖于固定的帧速率。因此,例如,如果您想以1 m/s 的速率以每秒60 帧的速度移动,则必须将1/60=.01666 添加到每帧的位置。然后想象一下如果你改变你的固定时间步长会发生什么。您将不得不更改一堆常量来适应。

FixedUpdate 中执行代码不会突然使该代码帧速率独立。您仍然需要采取与Update 相同的预防措施。

附带说明,您不需要在 FixedUpdate 内将 Time.deltaTime 替换为 Time.fixedDeltaTime,因为 Unity 已经进行了这种替换。

结论

如果您希望瞬时操作在一系列帧上平稳运行,请仅乘以 Time.deltaTime

很多时候,您可以通过使用更直观的连续操作来避免使用Time.deltaTime(参见示例 2 和示例 3)。但这当然取决于上下文。

【讨论】:

【参考方案2】:

考虑这个问题的一个简单方法是确定:

是否直接操作对象的变换?

当直接通过其transform.positiontransform.rotation 组件移动对象时,应在Update() 中进行转换操作,该操作在每个绘制帧运行一次。开发者在构建游戏时应该期望更新/帧速率在零和游戏的最大帧速率之间变化。实现这一点的方法是将现在和上一帧之间的时间考虑到每个更新/帧的移动(步骤)中。

因此,对transform.positiontransform.rotation 的操作应该考虑到Time.deltaTime 并且应该发生在Update() 内。

物体是否被物理操纵?

在这种情况下,应通过在FixedUpdate() 中调用Rigidbody.AddForce()Rigidbody.AddTorque() 来施加力和扭矩。 FixedUpdate() 可以预期以固定/保证的速率发生,Unity 默认为每秒 50 次(可以在游戏项目的物理设置中进行调整)。

为了使事情复杂一点,当使用 ForceMode.Force(默认)或 ForceMode.Acceleration 调用 AddForce()AddTorque() 时,为 forcetorque 参数提供的值将被解释为一整秒,因此这些函数将调整一个固定时间步长的值(力/扭矩或加速度/角加速度)(即,默认 Unity Physics 固定更新速率的 x 0.02 秒)。

如果您对ForceMode.ImpulseForceMode.VelocityChange 感到好奇,它们会将forcetorque 解释为在该固定时间步内应用的数量(即该值不会随时间调整)。

【讨论】:

以上是关于你啥时候乘以 Unity 中的 Time.deltaTime?的主要内容,如果未能解决你的问题,请参考以下文章

你啥时候提升项目中的 package.json 版本?

你啥时候以及为啥要封课?

你啥时候使用“this”关键字? [关闭]

你啥时候在流中使用接口而不是类型别名?

你啥时候在 Go 的 struct 中嵌入互斥锁?

React/Redux - 你啥时候应该从 API 获取新数据来更新你的商店?