帧率独立的 FixedUpdate 与 Update

Oli*_*ons 2 unity-game-engine

我已经阅读了这个和官方文档:fixedUpdate()深层解释

所以我试图分离我的代码。首先,在Update(),我没有给出完整的代码,变量是不言自明的:

private void Update()
{
    if (Input.GetButton("Jump")) {
        if (groundsTouched>0) {
            _jumpRequest = true;
        } else {
            _keepOnJumping = true;
        }
    } else {
        _keepOnJumping = false;
    }
    /* Handle release button: */
    _fallRequest = true;
}
Run Code Online (Sandbox Code Playgroud)

现在我正在做这样的所有计算FixedUpdate()

private void FixedUpdate()
{
    if (_jumpRequest) {
        if (!_jumpGravitySent) {
            _jumpGravitySent = true;
            _animator.SetBool("Jump", true);
            _jumpRequest = false;
            jumpTimeCounter = jumpTime;
            /* Cancel all force (couldn't find a better way) */
            _rigidbody.velocity = Vector3.zero;
            _rigidbody.angularVelocity = Vector3.zero;
            _rigidbody.AddForce(
                Vector3.up * jumpVelocity, ForceMode.VelocityChange
            );
        }
    } else if (_keepOnJumping) {
        jumpTimeCounter -= Time.fixedDeltaTime;
        if (jumpTimeCounter >= 0) {
            _rigidbody.AddForce(
                Vector3.up * jumpVelocity * jumpKeepMultiplier, 
                ForceMode.Acceleration
            );
        }
    }
    if (groundsTouched == 0 && 
        _rigidbody.velocity.y > velocityFallMin &&
        _rigidbody.velocity.y < velocityFallMax
    ) {
        _animator.SetBool("Jump", false);
        _animator.SetBool("Fall", true);
    }
    if (_fallRequest) {
        _fallRequest = false;
        _jumpGravitySent = false;
        _keepOnJumping = false;
    }
}
Run Code Online (Sandbox Code Playgroud)

我遇到的问题真的很奇怪:FPS低时,玩家跳不高。

Unity QA 看到了我的问题,他们的回答是:

您正在添加依赖于 fixedDeltaTime 的 Force,这取决于您的可用性能(或本质上是帧速率)。

如果您转到 Edit->Project Settings->Time 并将固定 Timestep 更改为更大的值,您将获得预期的行为。尝试几个不同的固定时间步长值,看看行为如何变化。

另一个建议是重写代码,使其不依赖于帧速率(例如,使用速度而不是强制或在跳跃时添加特定量的力,不依赖于时间步长)。

“另一个建议是重写代码,使其不依赖于帧速率”-> 你会怎么做,我认为我上面的代码正在这样做!

我错过了什么?我做错了什么/可能的解决方案是什么?

Gal*_*dil 5

我想知道给你这个答案的 Unity QA 是谁,这太可怕了。

让我解释一下现实中会发生什么。

1)让我们开始fixedDeltaTime这个值永远不会依赖于帧速率。它可以在编辑器 (in Edit->Project Settings->Time) 中设置,这是在运行时保留的值,除非任何脚本通过赋值更改它。Unity 引擎永远不会自行更改它。

2) 物理循环:在完整的引擎循环中,Unity 将执行多个物理循环(0,1 或更多),然后执行单个渲染循环。每个渲染循环执行的物理循环数基于此数量fixedDeltaTime以及自上次deltaTime循环以来经过的时间(即:渲染循环的时间)。

例如,假设fixedDeltaTime = 0.0166667自上次物理循环以来经过的时间小于该时间,例如10ms。Unity 不会执行物理循环。现在假设甚至下一帧都以10毫秒为单位进行渲染- 这意味着自上次物理循环以来,20毫秒已经过去了。由于这高于fixedDeltaTime,Unity 将执行物理循环。有时可能会发生帧渲染非常缓慢的情况(由于不可预见的原因),例如在40ms 中。为了保持物理模拟的一致性,Unity 必须连续运行两个物理循环,这是因为0.04/0.0166667 = 2.4.

请记住,Unity 会跟踪上一个物理循环开始时间和下一个物理循环之间的差异:如果渲染10每帧持续ms,并且fixedDeltaTime设置为166667ms (60Hz),则只要您启动运行时,Unity 就会执行第一个物理循环,然后在第一个渲染帧之后跳过一个(因为只有10ms 已经传递而不是166667),然后在第二个渲染帧(20针对 传递的 ms )之后执行一个物理循环166667。但是现在我们的循环被3.3333ms去同步了,所以 Unity 会跟踪它。

在第 3 帧之后,又过了10ms,但不会执行物理循环,因为10+3.3333 = 13.3333它仍然低于fixedDeltaTime. 现在,让我们假设第 4 个渲染帧“出错”,它持续25ms 而不仅仅是10。在下一个物理循环开始时,25+13.3333 = 38.3333自上次物理更新以来总共经过了 和38.3333/16.6667 = 2.3,并且 Unity 将连续执行两个物理循环以跟上固定步长模拟,然后继续渲染第 5 帧。


在所有这些介绍之后,让我们回到你的问题,看看会发生什么:

在某个时刻执行Update(), 并设置_jumpRequest = true;_fallRequest = true;

在这个渲染帧之后,FixedUpdate()第一次执行,执行AddForce ForceMode.VelocityChange线,设置_fallRequest = false;_jumpGravitySent = false;_keepOnJumping = false;。在此结束后FixedUpdate(),Unity 执行物理模拟,借助物理引擎调整刚体的位置和速度。

现在问题触发了:由于渲染帧很慢,物理循环至少连续执行两次,但Update()中间没有执行,所以FixedUpdate()跳过所有内容,但物理模拟运行了第二次,拖动刚体相对于预期的最高位置向下的位置。

Update()再次执行,最后你的代码集_keepOnJumping = true;,当它回来给FixedUpdate()它会执行AddForce ForceMode.Acceleration,但紧接着,另一种物理模拟是第2次(因低帧速率)执行,再拖动收到刚体下来可以在屏幕上渲染。

希望这有助于了解您的问题及其发生原因,以便您现在拥有正确的工具来正确修复它。