Sie möchten Aktualisierungsraten (logisches Häkchen) und Ziehungsraten (Renderhäkchen) trennen.
Ihre Aktualisierungen erzeugen die Position aller zu zeichnenden Objekte in der Welt.
Ich werde hier zwei verschiedene Möglichkeiten behandeln, die von Ihnen gewünschte, die Extrapolation und auch eine andere Methode, die Interpolation.
1.
Bei der Extrapolation wird die (vorhergesagte) Position des Objekts im nächsten Frame berechnet und dann zwischen der aktuellen Objektposition und der Position des Objekts im nächsten Frame interpoliert.
Dazu muss jedem zu zeichnenden Objekt ein velocity
und zugeordnet sein position
. Um die Position zu ermitteln, an der sich das Objekt im nächsten Frame befindet, addieren wir einfach velocity * draw_timestep
die aktuelle Position des Objekts, um die vorhergesagte Position des nächsten Frames zu ermitteln. draw_timestep
ist die Zeit, die seit dem vorherigen Render-Tick (auch bekannt als Draw-Call) vergangen ist.
Wenn Sie es dabei belassen, werden Sie feststellen, dass Objekte "flackern", wenn ihre vorhergesagte Position nicht mit der tatsächlichen Position im nächsten Frame übereinstimmt. Um das Flackern zu beseitigen, können Sie die vorhergesagte Position speichern und bei jedem Zeichenschritt zwischen der zuvor vorhergesagten Position und der neuen vorhergesagten Position hin- und herschalten , wobei die seit der vorherigen Aktualisierung verstrichene Zeit als Lerp-Faktor verwendet wird. Dies führt immer noch zu einem schlechten Verhalten, wenn sich schnell bewegende Objekte plötzlich ändern und Sie diesen Sonderfall möglicherweise behandeln möchten. Alles, was in diesem Absatz gesagt wird, ist der Grund, warum Sie keine Extrapolation verwenden möchten.
2.
Bei der Interpolation wird der Status der letzten beiden Aktualisierungen gespeichert und zwischen diesen basierend auf der aktuellen Zeitspanne interpoliert, die seit der letzten Aktualisierung vergangen ist. In diesem Setup muss jedem Objekt ein position
und zugeordnet sein previous_position
. In diesem Fall stellt unsere Zeichnung im schlimmsten Fall einen Update-Tick hinter dem aktuellen Gamestate dar und im besten Fall genau denselben Status wie der aktuelle Update-Tick.
Meiner Meinung nach möchten Sie wahrscheinlich eine Interpolation, wie ich sie beschrieben habe, da sie einfacher zu implementieren ist und es in Ordnung ist, einen winzigen Sekundenbruchteil (z. B. 1/60 Sekunde) hinter Ihrem aktuellen aktualisierten Status zu zeichnen.
Bearbeiten:
Falls das oben Genannte nicht ausreicht, um eine Implementierung durchzuführen, finden Sie hier ein Beispiel für die von mir beschriebene Interpolationsmethode. Ich werde nicht auf die Hochrechnung eingehen, da mir kein reales Szenario einfällt, in dem Sie es vorziehen sollten.
Wenn Sie ein ziehbar Objekt erstellen, wird es um die Eigenschaften speichern erforderlich gezogen werden (dh der Zustand benötigten Informationen zu ziehen).
In diesem Beispiel werden Position und Drehung gespeichert. Möglicherweise möchten Sie auch andere Eigenschaften wie Farbe oder Texturkoordinatenposition speichern (z. B. wenn eine Textur gescrollt wird).
Um zu verhindern, dass Daten geändert werden, während der Render-Thread sie zeichnet (dh die Position eines Objekts wird geändert, während der Render-Thread zeichnet, aber alle anderen wurden noch nicht aktualisiert), müssen wir eine Art Doppelpuffer implementieren.
Ein Objekt speichert zwei Kopien davon previous_state
. Ich werde sie in ein Array stellen und sie als previous_state[0]
und bezeichnen previous_state[1]
. Es werden ebenfalls zwei Kopien benötigt current_state
.
Um zu verfolgen, welche Kopie des Doppelpuffers verwendet wird, speichern wir eine Variable state_index
, die sowohl dem Update- als auch dem Draw-Thread zur Verfügung steht.
Der Update-Thread berechnet zunächst alle Eigenschaften eines Objekts unter Verwendung seiner eigenen Daten (beliebige Datenstrukturen). Dann kopiert er current_state[state_index]
zu previous_state[state_index]
und kopiert die neuen Daten relevant für das Zeichnen, position
und rotation
in current_state[state_index]
. Dann wird state_index = 1 - state_index
die aktuell verwendete Kopie des Doppelpuffers umgedreht.
Alles im obigen Absatz muss mit herausgenommenem Schloss gemacht werden current_state
. Das Update und Draw Threads entfernen beide diese Sperre. Die Sperre wird nur für die Dauer des schnellen Kopierens von Statusinformationen aufgehoben.
Im Render-Thread führen Sie dann eine lineare Interpolation von Position und Drehung wie folgt durch:
current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)
Wo elapsed
ist die Zeit, die im Render-Thread seit dem letzten Update-Tick vergangen ist, und update_tick_length
wie lange dauert Ihre feste Update-Rate pro Tick (z. B. bei 20FPS-Updates update_tick_length = 0.05
) ?
Wenn Sie die Lerp
obige Funktion nicht kennen , lesen Sie den Artikel von Wikipedia zum Thema: Lineare Interpolation . Wenn Sie jedoch nicht wissen, was Lerping ist, sind Sie wahrscheinlich nicht bereit, entkoppelte Aktualisierungen / Zeichnungen mit interpolierten Zeichnungen zu implementieren.