Time.deltaTimeを使用しているにもかかわらず、動きはフレームレートに依存しているように見える


13

Unityでゲームオブジェクトを移動するために必要な変換を計算する次のコードがありますLateUpdate。これはで呼び出されます。私が理解していることから、私の使用はTime.deltaTime最終的な翻訳フレームレートを独立させるべきCollisionDetection.Move()です(ただレイキャストを実行することに注意してください)。

public IMovementModel Move(IMovementModel model) {    
    this.model = model;

    targetSpeed = (model.HorizontalInput + model.VerticalInput) * model.Speed;

    model.CurrentSpeed = accelerateSpeed(model.CurrentSpeed, targetSpeed,
        model.Accel);

    if (model.IsJumping) {
        model.AmountToMove = new Vector3(model.AmountToMove.x,
            model.AmountToMove.y);
    } else if (CollisionDetection.OnGround) {
        model.AmountToMove = new Vector3(model.AmountToMove.x, 0);
    }

    model.FlipAnim = flipAnimation(targetSpeed);
    // If we're ignoring gravity, then just use the vertical input.
    // if it's 0, then we'll just float.
    gravity = model.IgnoreGravity ? model.VerticalInput : 40f;

    model.AmountToMove = new Vector3(model.CurrentSpeed, model.AmountToMove.y - gravity * Time.deltaTime);

    model.FinalTransform =
        CollisionDetection.Move(model.AmountToMove * Time.deltaTime,
            model.BoxCollider.gameObject, model.IgnorePlayerLayer);
    // Prevent the entity from moving too fast on the y-axis.
    model.FinalTransform = new Vector3(model.FinalTransform.x,
        Mathf.Clamp(model.FinalTransform.y, -1.0f, 1.0f),
        model.FinalTransform.z);

    return model;
}

private float accelerateSpeed(float currSpeed, float target, float accel) {
    if (currSpeed == target) {
        return currSpeed;
    }
    // Must currSpeed be increased or decreased to get closer to target
    float dir = Mathf.Sign(target - currSpeed);
    currSpeed += accel * Time.deltaTime * dir;
    // If currSpeed has now passed Target then return Target, otherwise return currSpeed
    return (dir == Mathf.Sign(target - currSpeed)) ? currSpeed : target;
}

private void OnMovementCalculated(IMovementModel model) {
    transform.Translate(model.FinalTransform);
}

ゲームのフレームレートを60FPSにロックすると、オブジェクトは期待どおりに移動します。ただし、ロックを解除すると(Application.targetFrameRate = -1;)、144hzモニターで〜200FPSを達成すると予想されるオブジェクトの速度がはるかに遅くなります。これはスタンドアロンビルドでのみ発生し、Unityエディターでは発生しないようです。

エディター内のオブジェクト移動のGIF、ロック解除されたFPS

http://gfycat.com/SmugAnnualFugu

スタンドアロンビルド内のオブジェクト移動のGIF、ロック解除されたFPS

http://gfycat.com/OldAmpleJuliabutterfly


2
これを読んでください。タイムバケティングはあなたが望むものであり、固定タイムステップです! gafferongames.com/game-physics/fix-your-timestep
アランウルフ

回答:


30

更新が非線形の変化率を補正できない場合、フレームベースのシミュレーションでエラーが発生します。

たとえば、位置と速度の値がゼロで始まり、一定の加速度が1であるオブジェクトを考えてみましょう。

この更新ロジックを適用する場合:

velocity += acceleration * elapsedTime
position += velocity * elapsedTime

異なるフレームレートでこれらの結果を期待できます。 ここに画像の説明を入力してください

このエラーは、最終速度をフレーム全体に適用したかのように扱うことで発生します。これは右リーマン和に似ており、エラーの量はフレームレートによって異なります(別の関数に示されています)。

マイケルズが指摘、このエラーは、フレーム期間が半分にされたときに半減され、高いフレームレートで取るに足らないなることがあります。一方、パフォーマンスの急上昇や長時間の実行フレームが発生するゲームでは、予測できない動作が発生する場合があります。


幸いなことに、キネマティックスにより、線形加速度によって生じる変位を正確に計算できます。

d =  vᵢ*t + (a*t²)/2

where:
  d  = displacement
  v = initial velocity
  a  = acceleration
  t  = elapsed time

breakdown:
  vᵢ*t     = movement due to the initial velocity
  (a*t²)/2 = change in movement due to acceleration throughout the frame

したがって、この更新ロジックを適用すると:

position += (velocity * elapsedTime) + (acceleration * elapsedTime * elapsedTime / 2)
velocity += acceleration * elapsedTime

次の結果が得られます。

ここに画像の説明を入力してください


2
これは有用な情報ですが、問題のコードに実際にどのように対処していますか?まず、フレームレートが増加するとエラーが劇的に減少するため、60 fpsと200 fpsの差は無視できます(8 fps対無限はすでに12.5%だけ高すぎます)。第二に、スプライトがフルスピードになると、最大の違いは0.5単位先になります。添付されている.gifに示されているように、実際の歩行速度には影響しません。向きを変えると、加速は一見瞬時になります(60秒以上のfpsで複数のフレームが可能ですが、完全な秒ではありません)。
MichaelS

2
それは、数学の問題ではなく、ユニティまたはコードの問題です。簡単なスプレッドシートでは、a = 1、vi = 0、di = 0、vmax = 1を使用すると、t = 1でvmaxに到達し、d = 0.5になるはずです。これを5フレーム(dt = 0.2)、d(t = 1)= 0.6で行います。50フレーム以上(dt = 0.02)、d(t = 1)= 0.51。500フレーム以上(dt = 0.002)、d(t = 1)= 0.501。したがって、5 fpsは20%、50 fpsは2%、500 fpsは0.2%です。一般に、エラーは100 / fpsパーセントが高すぎます。50 fpsは500 fpsよりも約1.8%高いです。そして、それは加速中です。速度が最大に達すると、差はゼロになります。a = 100およびvmax = 5の場合、差はさらに小さくなるはずです。
MichaelS

2
実際、VB.netアプリでコードを使用して(1/60と1/200のdtをシミュレート)、バウンス:フレーム626(10.433)秒で5バウンス:フレーム2081で5を得ました( 10.405)秒。60 fpsで0.27%長い時間。
MichaelS

2
10%の違いを与えるのは、「キネマティック」アプローチです。従来のアプローチは、0.27%の違いです。間違ったラベルを付けただけです。速度が最大になったときに加速度を誤って含めているためだと思います。フレームレートを高くすると、フレームあたりのエラーが少なくなるため、より正確な結果が得られます。あなたが必要if(velocity==vmax||velocity==-vmax){acceleration=0}です。その後、エラーは大幅に低下しますが、フレームアクセラレーションのどの部分が終了したかを正確に把握できないため、完全ではありません。
MichaelS

6

どこからステップを呼び出しているかに依存します。Updateから呼び出す場合、Time.deltaTimeでスケーリングする場合、動きは実際にフレームレートに依存しませんが、FixedUpdateから呼び出す場合は、Time.fixedDeltaTimeでスケーリングする必要があります。FixedUpdateからステップを呼び出していますが、Time.deltaTimeでスケーリングすると、Unityの固定ステップがメインループよりも遅い場合に見かけの速度が低下します。これがスタンドアロンビルドで発生しています。固定ステップが遅い場合、fixedDeltaTimeは大きくなります。


1
LateUpdateから呼び出されています。それを明確にするために質問を更新します。Time.deltaTime呼び出された場所に関係なく正しい値を使用すると信じていますが(FixedUpdateで使用される場合、fixedDeltaTimeを使用します)。
クーパー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.