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.
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:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
float timestep = 0.001 * (time - lastTime); // in seconds
if (timestep <= 0 || timestep > 1.0) {
timestep = 0.001; // avoid absurd time steps
}
update(timestep);
draw();
// ... sleep until next frame ...
lastTime = time;
}
und im Inneren update()
die physikalischen Formeln anpassen, um den variablen Zeitschritt zu berücksichtigen, z. B. wie folgt:
speed += timestep * acceleration;
position += timestep * (speed - 0.5 * timestep * acceleration);
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:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
if (time - lastTime > 1000) {
lastTime = time; // we're too far behind, catch up
}
int updatesNeeded = (time - lastTime) / updateInterval;
for (int i = 0; i < updatesNeeded; i++) {
update();
lastTime += updateInterval;
}
draw();
// ... sleep until next frame ...
}
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 die draw()
Methode übergeben müssen, z. B.:
int remainder = (time - lastTime) % updateInterval;
draw( (float)remainder / updateInterval ); // scale to 0.0 - 1.0
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 die draw()
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 .)