Ich experimentiere mit dem Erstellen einer Game Engine von Grund auf in Java und habe ein paar Fragen. Meine Hauptspielschleife sieht folgendermaßen aus:
int FPS = 60;
while(isRunning){
/* Current time, before frame update */
long time = System.currentTimeMillis();
update();
draw();
/* How long each frame should last - time it took for one frame */
long delay = (1000 / FPS) - (System.currentTimeMillis() - time);
if(delay > 0){
try{
Thread.sleep(delay);
}catch(Exception e){};
}
}
Wie Sie sehen können, habe ich die Framerate auf 60 fps eingestellt, die für die delay
Berechnung verwendet wird. Die Verzögerung stellt sicher, dass jeder Frame dieselbe Zeit benötigt, bevor der nächste gerendert wird. In meiner update()
Funktion, die ich mache x++
, erhöht sich der horizontale Wert eines Grafikobjekts, das ich zeichne, mit folgendem:
bbg.drawOval(x,40,20,20);
Was mich verwirrt, ist die Geschwindigkeit. Wenn ich FPS
auf 150 setze , geht der gerenderte Kreis sehr schnell über die Geschwindigkeit, während die Einstellung FPS
auf 30 mit der halben Geschwindigkeit über den Bildschirm läuft. Beeinflusst die Framerate nicht nur die "Glätte" des Renderings und nicht die Geschwindigkeit der gerenderten Objekte? Ich denke, ich vermisse einen großen Teil, ich würde eine Klarstellung lieben.
quelle
1000 / FPS
könnte Ihre Division durchgeführt und das Ergebnis einer Variablen vor Ihrerwhile(isRunning)
Schleife zugewiesen werden . Dies hilft, ein paar CPU-Anweisungen zu sparen, um etwas mehr als einmal nutzlos zu machen.Antworten:
Sie verschieben den Kreis um ein Pixel pro Bild. Es sollte keine große Überraschung sein, dass sich Ihr Kreis bei einer Rendering-Schleife von 30 FPS um 30 Pixel pro Sekunde bewegt.
Grundsätzlich haben Sie drei Möglichkeiten, um mit diesem Problem umzugehen:
Wählen Sie einfach eine Bildrate und bleiben Sie dabei. Das war es, was viele Spiele der alten Schule taten - sie liefen mit einer festen Rate von 50 oder 60 FPS, die normalerweise mit der Bildschirmaktualisierungsrate synchronisiert waren, und entwarfen einfach ihre Spielelogik, um alles Notwendige innerhalb dieses festgelegten Zeitintervalls zu tun. Wenn dies aus irgendeinem Grund nicht geschehen wäre, müsste das Spiel nur einen Frame überspringen (oder möglicherweise abstürzen), was sowohl das Zeichnen als auch die Spielphysik effektiv auf die halbe Geschwindigkeit verlangsamt.
Insbesondere Spiele, die Funktionen wie die Erkennung von Hardware-Sprite-Kollisionen verwendeten , mussten so funktionieren, da ihre Spielelogik untrennbar mit dem Rendering verbunden war, das in Hardware mit einer festen Rate ausgeführt wurde.
Verwenden Sie einen variablen Zeitschritt für Ihre Spielphysik. Grundsätzlich bedeutet dies, dass Sie Ihre Spieleschleife so umschreiben, dass sie ungefähr so aussieht:
und im Inneren
update()
die physikalischen Formeln anpassen, um den variablen Zeitschritt zu berücksichtigen, z. B. wie folgt:Ein Problem bei dieser Methode ist, dass es schwierig sein kann , die Physik (meistens) unabhängig vom Zeitschritt zu halten . Sie möchten wirklich nicht, dass die Entfernung, über die Spieler springen können, von ihrer Bildrate abhängt. Die Formel, die ich oben gezeigt habe, funktioniert gut für konstante Beschleunigung, z. B. unter Schwerkraft (und die im verknüpften Beitrag ist ziemlich gut, selbst wenn die Beschleunigung im Laufe der Zeit variiert), aber selbst mit den perfektesten physikalischen Formeln ist es wahrscheinlich, dass mit Schwimmern gearbeitet wird erzeugen ein bisschen "numerisches Rauschen", das insbesondere exakte Wiederholungen unmöglich machen kann. Wenn Sie glauben, dass Sie dies möchten, möchten Sie möglicherweise die anderen Methoden bevorzugen.
Entkoppeln Sie das Update und zeichnen Sie die Schritte. Hier besteht die Idee darin, dass Sie Ihren Spielstatus mit einem festen Zeitschritt aktualisieren, aber zwischen den einzelnen Frames eine unterschiedliche Anzahl von Aktualisierungen ausführen. Das heißt, Ihre Spieleschleife könnte ungefähr so aussehen:
Um die empfundene Bewegung glatte, mögen Sie vielleicht auch Ihre haben
draw()
Methode interpolieren Dinge wie Objektpositionen glatt zwischen den vorherigen und den nächsten Spiel Staaten. Dies bedeutet, dass Sie den korrekten Interpolationsversatz an diedraw()
Methode übergeben müssen, z. B.:Außerdem müsste Ihre
update()
Methode den Spielstatus tatsächlich einen Schritt voraus berechnen (oder möglicherweise mehrere, wenn Sie eine Spline-Interpolation höherer Ordnung durchführen möchten) und vorherige Objektpositionen speichern, bevor Sie sie aktualisieren, damit diedraw()
Methode interpolieren kann zwischen ihnen. (Es ist auch möglich, vorhergesagte Positionen basierend auf Objektgeschwindigkeiten und -beschleunigungen zu extrapolieren. Dies kann jedoch ruckartig aussehen, insbesondere wenn sich Objekte auf komplizierte Weise bewegen und die Vorhersagen häufig fehlschlagen.)Ein Vorteil der Interpolation besteht darin, dass Sie bei einigen Arten von Spielen die Aktualisierungsrate der Spielelogik erheblich reduzieren können, während die Illusion einer reibungslosen Bewegung erhalten bleibt. Beispielsweise können Sie Ihren Spielstatus möglicherweise nur 5 Mal pro Sekunde aktualisieren, während Sie immer noch 30 bis 60 interpolierte Bilder pro Sekunde zeichnen. Wenn Sie dies tun, möchten Sie möglicherweise auch in Betracht ziehen, Ihre Spiellogik mit der Zeichnung zu verschachteln (dh einen Parameter für Ihre
update()
Methode zu haben, der besagt, dass nur x % eines vollständigen Updates ausgeführt werden sollen, bevor Sie zurückkehren) und / oder die Spielphysik auszuführen. Logik und Rendering-Code in separaten Threads (Vorsicht vor Synchronisationsfehlern!).Natürlich ist es auch möglich, diese Methoden auf verschiedene Arten zu kombinieren. In einem Client-Server-Multiplayer-Spiel kann es beispielsweise vorkommen, dass der Server (der nichts zeichnen muss) seine Aktualisierungen zu einem festgelegten Zeitpunkt ausführt (für konsistente Physik und genaue Wiederspielbarkeit), während der Client vorausschauende Aktualisierungen durchführt (bis im Falle einer Meinungsverschiedenheit vom Server überschrieben werden) zu einem variablen Zeitschritt für eine bessere Leistung. Es ist auch möglich, Interpolation und Aktualisierungen mit variablen Zeitschritten sinnvoll zu mischen. In dem gerade beschriebenen Client-Server-Szenario macht es beispielsweise nicht viel Sinn, wenn der Client kürzere Aktualisierungszeitschritte als der Server verwendet. Sie können also eine Untergrenze für den Client-Zeitschritt festlegen und in der Zeichenphase interpolieren, um höhere zu ermöglichen FPS.
(Bearbeiten: Code hinzugefügt, um absurde Aktualisierungsintervalle / -zählungen zu vermeiden, falls der Computer beispielsweise für mehr als eine Sekunde vorübergehend angehalten oder auf andere Weise eingefroren wird, während die Spieleschleife läuft. Vielen Dank an Mooing Duck, der mich daran erinnert hat, dass dies erforderlich ist .)
quelle
updateInterval
ist nur die Anzahl der Millisekunden, die Sie zwischen den Aktualisierungen des Spielstatus benötigen . Für beispielsweise 10 Updates pro Sekunde würden Sie festlegenupdateInterval = (1000 / 10) = 100
.currentTimeMillis
ist keine monotone Uhr. Verwenden SienanoTime
stattdessen, es sei denn, Sie möchten, dass die Synchronisierung der Netzwerkzeit mit der Geschwindigkeit der Dinge in Ihrem Spiel in Konflikt gerät.while(lastTime+=updateInterval <= time)
. Das ist aber nur ein Gedanke, keine Korrektur.Ihr Code wird derzeit jedes Mal ausgeführt, wenn ein Frame gerendert wird. Wenn die Bildrate höher oder niedriger als die angegebene Bildrate ist, ändern sich Ihre Ergebnisse, da die Aktualisierungen nicht das gleiche Timing haben.
Um dies zu lösen, sollten Sie sich auf Delta Timing beziehen .
Um dies zu tun:
Sie müssten dann die Deltazeit mit dem Wert multiplizieren, den Sie mit der Zeit ändern möchten. Zum Beispiel:
quelle
time()
dasselbe zweimal zurückgibt, möchten Sie keine Div / 0-Fehler und keine Verschwendung von Verarbeitung.Das liegt daran, dass Sie Ihre Framerate begrenzen, aber nur ein Update pro Frame durchführen. Nehmen wir also an, das Spiel läuft mit dem Ziel von 60 fps. Sie erhalten 60 logische Updates pro Sekunde. Wenn die Bildrate auf 15 fps sinkt, haben Sie nur 15 logische Aktualisierungen pro Sekunde.
Versuchen Sie stattdessen, die bisher verstrichene Frame-Zeit zu akkumulieren und aktualisieren Sie dann Ihre Spiellogik einmal für jede verstrichene Zeitspanne, z. B. um Ihre Logik mit 100 fps auszuführen, führen Sie das Update einmal für alle 10 akkumulierten ms aus (und subtrahieren Sie diese von der Zähler).
Fügen Sie eine Alternative hinzu (besser für die visuelle Darstellung), und aktualisieren Sie Ihre Logik basierend auf der verstrichenen Zeit.
quelle