Ich habe in den letzten 8 Monaten ein paar ähnliche Fragen gestellt, ohne wirklich Freude zu haben, also werde ich die Frage allgemeiner stellen.
Ich habe ein Android-Spiel, das OpenGL ES 2.0 ist. Darin habe ich die folgende Spielschleife:
Meine Schleife arbeitet nach einem festen Zeitschrittprinzip (dt = 1 / ticksPerSecond )
loops=0;
while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){
updateLogic(dt);
nextGameTick+=skipTicks;
timeCorrection += (1000d/ticksPerSecond) % 1;
nextGameTick+=timeCorrection;
timeCorrection %=1;
loops++;
}
render();
Meine Integration funktioniert folgendermaßen:
sprite.posX+=sprite.xVel*dt;
sprite.posXDrawAt=sprite.posX*width;
Jetzt funktioniert alles so, wie ich es gerne hätte. Ich kann angeben, dass sich ein Objekt in 2,5 Sekunden über eine bestimmte Entfernung (z. B. Bildschirmbreite) bewegen soll, und genau das wird es tun. Auch aufgrund des Frame-Überspringens, das ich in meiner Spieleschleife zulasse, kann ich dies auf so ziemlich jedem Gerät tun, und es dauert immer 2,5 Sekunden.
Problem
Das Problem ist jedoch, dass beim Überspringen eines Renderrahmens die Grafik stottert. Es ist extrem nervig. Wenn ich die Möglichkeit zum Überspringen von Frames entferne, ist alles reibungslos, wie Sie möchten, läuft jedoch auf verschiedenen Geräten mit unterschiedlichen Geschwindigkeiten. Es ist also keine Option.
Ich bin mir immer noch nicht sicher, warum der Frame überspringt, aber ich möchte darauf hinweisen, dass dies nichts mit schlechter Leistung zu tun hat. Ich habe den Code direkt auf 1 winziges Sprite zurückgeführt und keine Logik (abgesehen von der erforderlichen Logik) Bewegen Sie das Sprite) und ich bekomme immer noch übersprungene Frames. Und dies ist auf einem Google Nexus 10-Tablet (und wie oben erwähnt, muss ich Frames überspringen, um die Geschwindigkeit auf allen Geräten trotzdem konstant zu halten).
Die einzige andere Möglichkeit, die ich habe, ist die Verwendung von Interpolation (oder Extrapolation). Ich habe jeden Artikel gelesen, aber keiner hat mir wirklich geholfen zu verstehen, wie es funktioniert, und alle meine versuchten Implementierungen sind fehlgeschlagen.
Mit einer Methode konnte ich die Dinge reibungslos in Gang bringen, aber es war nicht praktikabel, weil es meine Kollision durcheinander brachte. Ich kann das gleiche Problem bei jeder ähnlichen Methode vorhersehen, da die Interpolation zur Renderzeit an die Rendermethode übergeben wird (und innerhalb dieser angewendet wird). Wenn Collision die Position korrigiert (Zeichen steht jetzt direkt neben der Wand), kann der Renderer seine Position ändern und in die Wand zeichnen .
Ich bin also wirklich verwirrt. Die Leute haben gesagt, dass Sie die Position eines Objekts niemals innerhalb der Rendering-Methode ändern sollten , aber alle Online-Beispiele zeigen dies.
Ich bitte um einen Push in die richtige Richtung. Bitte verlinken Sie nicht auf die beliebten Game-Loop-Artikel (DeWitters, Fix your timestep usw.), da ich diese mehrmals gelesen habe . Ich bin nicht zu fragen jemand meinen Code für mich zu schreiben. Erklären Sie bitte in einfachen Worten, wie Interpolation mit einigen Beispielen tatsächlich funktioniert. Ich werde dann versuchen, irgendwelche Ideen in meinen Code zu integrieren, und bei Bedarf spezifischere Fragen stellen. (Ich bin sicher, dass dies ein Problem ist, mit dem viele Menschen zu kämpfen haben).
bearbeiten
Einige zusätzliche Informationen - Variablen, die in der Spielschleife verwendet werden.
private long nextGameTick = System.currentTimeMillis();
//loop counter
private int loops;
//Amount of frames that we will allow app to skip before logic is affected
private final int maxFrameskip = 5;
//Game updates per second
final int ticksPerSecond = 60;
//Amount of time each update should take
private final int skipTicks = (1000 / ticksPerSecond);
float dt = 1f/ticksPerSecond;
private double timeCorrection;
quelle
Antworten:
Es gibt zwei Dinge, die entscheidend dafür sind, dass die Bewegung reibungslos erscheint. Das erste ist offensichtlich, dass das, was Sie rendern, dem erwarteten Status zum Zeitpunkt der Präsentation des Rahmens für den Benutzer entsprechen muss. Das zweite ist, dass Sie dem Benutzer Bilder präsentieren müssen in einem relativ festen Intervall. Das Präsentieren eines Rahmens bei T + 10 ms, dann eines weiteren bei T + 30 ms und eines weiteren bei T + 40 ms erscheint dem Benutzer als ruckelnd, selbst wenn das, was für diese Zeiten tatsächlich angezeigt wird, gemäß der Simulation korrekt ist.
In Ihrer Hauptschleife scheint es keinen Gating-Mechanismus zu geben, um sicherzustellen, dass Sie nur in regelmäßigen Abständen rendern. Manchmal führen Sie also 3 Aktualisierungen zwischen den Renderings durch, manchmal 4. Grundsätzlich wird Ihre Schleife so oft wie möglich gerendert, sobald Sie genug Zeit simuliert haben, um den Simulationsstatus vor die aktuelle Zeit zu verschieben dann rendern Sie diesen Zustand. Aber jede Variabilität, wie lange das Aktualisieren oder Rendern dauert, und das Intervall zwischen den Frames variieren ebenfalls. Sie haben einen festen Zeitschritt für Ihre Simulation, aber einen variablen Zeitschritt für Ihr Rendering.
Was Sie wahrscheinlich brauchen, ist eine Wartezeit kurz vor dem Rendern, um sicherzustellen, dass Sie das Rendern immer erst zu Beginn eines Renderintervalls starten. Im Idealfall sollte dies anpassungsfähig sein: Wenn das Aktualisieren / Rendern zu lange gedauert hat und der Beginn des Intervalls bereits verstrichen ist, sollten Sie sofort rendern, aber auch die Intervalllänge erhöhen, bis Sie konsistent rendern und aktualisieren können und trotzdem darauf zugreifen können das nächste Rendern vor Ablauf des Intervalls. Wenn Sie genügend Zeit haben, können Sie das Intervall langsam verkürzen (dh die Bildrate erhöhen), um wieder schneller zu rendern.
Aber, und hier ist der Kicker: Wenn Sie den Frame nicht sofort rendern, nachdem Sie festgestellt haben, dass der Simulationsstatus auf "jetzt" aktualisiert wurde, führen Sie zeitliches Aliasing ein. Der Rahmen, der dem Benutzer präsentiert wird, wird etwas zur falschen Zeit präsentiert, und das an sich wird sich wie ein Stottern anfühlen.
Dies ist der Grund für den "Teilzeitschritt", der in den von Ihnen gelesenen Artikeln erwähnt wird. Es gibt es aus einem guten Grund, und das liegt daran, dass Sie die Frames einfach nicht zum richtigen Zeitpunkt präsentieren können, wenn Sie Ihren Physik-Zeitschritt nicht auf ein festes ganzzahliges Vielfaches Ihres festen Rendering-Zeitschritts festlegen. Sie präsentieren sie entweder zu früh oder zu spät. Die einzige Möglichkeit, eine feste Renderrate zu erhalten und dennoch etwas physikalisch Korrektes zu präsentieren, besteht darin, zu akzeptieren, dass Sie sich zum Zeitpunkt des Rendering-Intervalls höchstwahrscheinlich auf halbem Weg zwischen zwei Ihrer festen Physik-Zeitschritte befinden. Dies bedeutet jedoch nicht, dass die Objekte während des Renderns geändert werden. Nur, dass das Rendering vorübergehend festlegen muss, wo sich die Objekte befinden, damit sie irgendwo zwischen dem Ort, an dem sie sich vor und dem Ort nach dem Update befanden, gerendert werden können. Das ist wichtig - ändern Sie niemals den Weltzustand für das Rendern, nur Aktualisierungen sollten den Weltzustand ändern.
Um es in eine Pseudocode-Schleife zu bringen, brauche ich etwas mehr wie:
Damit dies funktioniert, müssen alle zu aktualisierenden Objekte wissen, wo sie sich zuvor befanden und wo sie sich jetzt befinden, damit das Rendering das Wissen darüber verwenden kann, wo sich das Objekt befindet.
Lassen Sie uns eine Zeitleiste in Millisekunden erstellen, die besagt, dass das Rendern 3 ms dauert, das Aktualisieren 1 ms dauert, Ihr Aktualisierungszeitschritt auf 5 ms festgelegt ist und Ihr Renderzeitschritt bei 16 ms [60 Hz] beginnt (und bleibt).
Es gibt noch eine weitere Nuance, zu weit im Voraus zu simulieren, was bedeutet, dass die Eingaben des Benutzers möglicherweise ignoriert werden, obwohl sie vor dem tatsächlichen Rendern des Frames aufgetreten sind. Machen Sie sich darüber jedoch keine Sorgen, bis Sie sicher sind, dass die Schleife reibungslos simuliert.
quelle
Was Ihnen alle erzählt haben, ist richtig. Aktualisieren Sie niemals die Simulationsposition Ihres Sprites in Ihrer Renderlogik.
Stellen Sie sich das so vor: Ihr Sprite hat zwei Positionen. wo die Simulation sagt, dass er sich seit dem letzten Simulationsupdate befindet und wo das Sprite gerendert wird. Sie sind zwei völlig unterschiedliche Koordinaten.
Das Sprite wird an seiner extrapolierten Position gerendert. Die extrapolierte Position wird für jeden Render-Frame berechnet, zum Rendern des Sprites verwendet und dann weggeworfen. Das ist alles dazu.
Davon abgesehen scheinen Sie ein gutes Verständnis zu haben. Hoffe das hilft.
quelle