Die Bewegung scheint von der Bildrate abhängig zu sein, obwohl Time.deltaTime verwendet wird


13

Ich habe den folgenden Code, um die Übersetzung zu berechnen, die erforderlich ist, um ein Spielobjekt in Unity zu verschieben, das aufgerufen wird LateUpdate. Soweit ich weiß , sollte meine Verwendung von Time.deltaTimedie endgültige CollisionDetection.Move()Framerate der Übersetzung unabhängig machen (bitte beachten Sie, dass nur Raycasts ausgeführt werden).

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);
}

Wenn ich die Bildrate des Spiels auf 60 FPS sperre, bewegen sich meine Objekte wie erwartet. Wenn ich es jedoch entsperre ( Application.targetFrameRate = -1;), bewegen sich einige Objekte viel langsamer als erwartet, wenn auf einem 144-Hz-Monitor ~ 200 FPS erreicht werden. Dies scheint nur in einem Standalone-Build und nicht im Unity-Editor zu geschehen.

GIF der Objektbewegung im Editor, FPS freigeschaltet

http://gfycat.com/SmugAnnualFugu

GIF der Objektbewegung im Standalone-Build, FPS freigeschaltet

http://gfycat.com/OldAmpleJuliabutterfly


2
Sie sollten dies lesen. Time Bucketing ist das, was Sie wollen, und feste Zeitschritte! gafferongames.com/game-physics/fix-your-timestep
Alan Wolfe

Antworten:


30

Bei rahmenbasierten Simulationen treten Fehler auf, wenn Aktualisierungen nicht lineare Änderungsraten nicht ausgleichen können.

Angenommen, ein Objekt beginnt mit Positions- und Geschwindigkeitswerten von Null und erfährt eine konstante Beschleunigung von Eins.

Wenn wir diese Aktualisierungslogik anwenden:

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

Wir können diese Ergebnisse bei unterschiedlichen Frameraten erwarten: Bildbeschreibung hier eingeben

Der Fehler wird dadurch verursacht, dass die Endgeschwindigkeit so behandelt wird, als würde sie für den gesamten Frame gelten. Dies ähnelt einer rechten Riemann-Summe und die Fehlermenge variiert mit der Bildrate (auf einer anderen Funktion dargestellt):

Wie MichaelS hervorhebt , wird dieser Fehler halbiert, wenn die Bilddauer halbiert wird, und kann bei hohen Bildraten belanglos werden. Bei Spielen mit Leistungsspitzen oder langen Frames kann dies zu unvorhersehbarem Verhalten führen.


Glücklicherweise erlaubt uns die Kinematik , die Verschiebung, die durch die lineare Beschleunigung verursacht wird, genau zu berechnen:

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

Wenn wir also diese Aktualisierungslogik anwenden:

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

Wir werden die folgenden Ergebnisse haben:

Bildbeschreibung hier eingeben


2
Dies sind nützliche Informationen, aber wie wird der betreffende Code tatsächlich angesprochen? Erstens nimmt der Fehler mit zunehmender Framerate dramatisch ab, sodass der Unterschied zwischen 60 und 200 fps vernachlässigbar ist (8 fps gegenüber unendlich sind bereits nur 12,5% zu hoch). Zweitens liegt der größte Unterschied, sobald das Sprite seine volle Geschwindigkeit erreicht hat, bei 0,5 Einheiten Vorsprung. Die tatsächliche Schrittgeschwindigkeit sollte nicht beeinträchtigt werden, wie in den beigefügten GIFs angegeben. Wenn sie sich umdrehen, ist die Beschleunigung augenscheinlich augenblicklich (möglicherweise mehrere Frames mit mehr als 60 fps, aber nicht vollen Sekunden).
MichaelS

2
Das ist dann ein Problem mit Unity oder Code, kein mathematisches Problem. Eine kurze Tabelle besagt, wenn wir a = 1, vi = 0, di = 0, vmax = 1 verwenden, sollten wir vmax bei t = 1 mit d = 0,5 treffen. Tun Sie dies über 5 Frames (dt = 0,2), ist d (t = 1) = 0,6. Über 50 Frames (dt = 0,02), d (t = 1) = 0,51. Über 500 Bilder (dt = 0,002), d (t = 1) = 0,501. 5 fps sind also 20% hoch, 50 fps sind 2% hoch und 500 fps sind 0,2% hoch. In der Regel ist der Fehler 100 / fps Prozent zu hoch. 50 fps sind ungefähr 1,8% höher als 500 fps. Und das nur beim Beschleunigen. Sobald die Geschwindigkeit den Maximalwert erreicht, sollte die Differenz Null sein. Mit a = 100 und vmax = 5 sollte es noch weniger Unterschiede geben.
MichaelS

2
Tatsächlich habe ich Ihren Code in einer VB.net-App verwendet (simuliert dt von 1/60 und 1/200) und Bounce: 5 bei Frame 626 (10.433) Sekunden vs. Bounce: 5 bei Frame 2081 ( 10.405) Sekunden . 0,27% mehr Zeit bei 60 fps.
MichaelS

2
Es ist Ihr "kinematischer" Ansatz, der einen Unterschied von 10% ergibt. Der traditionelle Ansatz ist der Unterschied von 0,27%. Sie haben sie nur falsch beschriftet. Ich denke, das liegt daran, dass Sie die Beschleunigung falsch eingeben, wenn die Geschwindigkeit maximal ist. Höhere Frameraten führen zu weniger Fehlern pro Frame, sodass ein genaueres Ergebnis erzielt wird. Du brauchst if(velocity==vmax||velocity==-vmax){acceleration=0}. Dann sinkt der Fehler erheblich, obwohl er nicht perfekt ist, da wir nicht genau herausfinden, welcher Teil der Frame-Beschleunigung beendet wurde.
MichaelS

6

Es kommt darauf an, von wo aus Sie Ihren Schritt anrufen. Wenn Sie es über Update aufrufen, ist Ihre Bewegung zwar unabhängig von der Bildrate, wenn Sie mit Time.deltaTime skalieren. Wenn Sie es jedoch über FixedUpdate aufrufen, müssen Sie mit Time.fixedDeltaTime skalieren. Ich nehme an, Sie rufen Ihren Schritt von FixedUpdate aus auf, skalieren jedoch mit Time.deltaTime, was zu einer verminderten scheinbaren Geschwindigkeit führen würde, wenn der feste Schritt von Unity langsamer als die Hauptschleife ist, was in Ihrem Standalone-Build geschieht. Wenn der feste Schritt langsam ist, ist fixedDeltaTime groß.


1
Es wird von LateUpdate aufgerufen. Ich werde meine Frage aktualisieren, um das klar zu machen. Obwohl ich glaube, Time.deltaTimedass immer noch der richtige Wert verwendet wird, unabhängig davon, wo er aufgerufen wird (wenn er in FixedUpdate verwendet wird, verwendet er fixedDeltaTime).
Cooper
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.