Durch Extrapolation wird die Kollisionserkennung unterbrochen

10

Bevor ich die Bewegung meines Sprites extrapolierte, funktionierte meine Kollision perfekt. Nach der Extrapolation auf die Bewegung meines Sprites (um die Dinge zu glätten) funktioniert die Kollision jedoch nicht mehr.

So funktionierten die Dinge vor der Extrapolation:

Geben Sie hier die Bildbeschreibung ein

Nachdem ich meine Extrapolation implementiert habe, wird die Kollisionsroutine jedoch unterbrochen. Ich gehe davon aus, dass dies darauf zurückzuführen ist, dass es auf die neue Koordinate einwirkt, die von der Extrapolationsroutine (die sich in meinem Renderaufruf befindet) erzeugt wurde.

Nachdem ich meine Extrapolation angewendet habe

Geben Sie hier die Bildbeschreibung ein

Wie kann ich dieses Verhalten korrigieren?

Ich habe versucht, unmittelbar nach der Extrapolation eine zusätzliche Kollisionsprüfung durchzuführen. Dies scheint viele Probleme zu lösen, aber ich habe dies ausgeschlossen, da es nicht in Frage kommt, Logik in mein Rendering zu integrieren.

Ich habe auch versucht, eine Kopie der spritesX-Position zu erstellen, diese zu extrapolieren und mit dieser anstelle des Originals zu zeichnen, sodass das Original intakt bleibt, damit die Logik es aufnehmen kann - dies scheint eine bessere Option zu sein, aber es erzeugt immer noch einige seltsame Effekte bei Kollisionen mit Wänden. Ich bin mir ziemlich sicher, dass dies auch nicht der richtige Weg ist, um damit umzugehen.

Ich habe hier ein paar ähnliche Fragen gefunden, aber die Antworten haben mir nicht geholfen.

Dies ist mein Extrapolationscode:

public void onDrawFrame(GL10 gl) {


        //Set/Re-set loop back to 0 to start counting again
        loops=0;

        while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){

        SceneManager.getInstance().getCurrentScene().updateLogic();
        nextGameTick+=skipTicks;
        timeCorrection += (1000d/ticksPerSecond) % 1;
        nextGameTick+=timeCorrection;
        timeCorrection %=1;
        loops++;
        tics++;

     }

        extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks; 

        render(extrapolation);
}

Extrapolation anwenden

            render(float extrapolation){

            //This example shows extrapolation for X axis only.  Y position (spriteScreenY is assumed to be valid)
            extrapolatedPosX = spriteGridX+(SpriteXVelocity*dt)*extrapolation;
            spriteScreenPosX = extrapolationPosX * screenWidth;

            drawSprite(spriteScreenX, spriteScreenY);           


        }

Bearbeiten

Wie oben erwähnt, habe ich versucht, eine Kopie der Sprite-Koordinaten speziell zum Zeichnen zu erstellen. Dies hat seine eigenen Probleme.

Erstens, unabhängig vom Kopieren, wenn sich das Sprite bewegt, ist es super glatt, wenn es stoppt, wackelt es leicht nach links / rechts - da es immer noch seine Position basierend auf der Zeit extrapoliert. Ist das normal und können wir es ausschalten, wenn das Sprite stoppt?

Ich habe versucht, Flags für links / rechts zu haben und nur dann zu extrapolieren, wenn eines davon aktiviert ist. Ich habe auch versucht, die letzte und die aktuelle Position zu kopieren, um festzustellen, ob es einen Unterschied gibt. Bei Kollisionen helfen diese jedoch nicht weiter.

Wenn der Benutzer beispielsweise die rechte Taste drückt und sich das Sprite nach rechts bewegt, wenn es gegen eine Wand stößt und der Benutzer die rechte Taste weiterhin gedrückt hält, wird das Sprite weiterhin nach rechts animiert, während es von der Wand gestoppt wird ( daher nicht wirklich bewegend), aber da die richtige Flagge immer noch gesetzt ist und auch weil die Kollisionsroutine das Sprite ständig aus der Wand bewegt, scheint es dem Code (nicht dem Spieler) immer noch, dass sich das Sprite noch bewegt, und daher Die Extrapolation wird fortgesetzt. Was der Spieler also sehen würde, ist das "statische" Sprite (ja, es ist animierend, aber es bewegt sich nicht wirklich über den Bildschirm), und ab und zu zittert es heftig, wenn die Extrapolation versucht, es zu tun ..... .. Ich hoffe das hilft

BungleBonce
quelle
1
Dies wird einige Aufschluß zu ergreifen , um vollständig zu verstehen ( ‚Interpolation‘ hat scheinbar ein Dutzend verschiedene Bedeutungen und es ist nicht ganz klar , auf den ersten Blick genau das, was Sie damit meinen hier), aber mein erster Instinkt ist , ‚sollten Sie nicht tun etwas Ihre beeinflussen Objektposition in Ihrer Rendering-Routine '. Ihr Renderer sollte Ihr Objekt an der angegebenen Position des Objekts zeichnen. Wenn Sie das Objekt manipulieren, gibt es ein Rezept für Probleme, da der Renderer von Natur aus an die Physik des Spiels gekoppelt ist. In einer idealen Welt sollte Ihr Renderer in der Lage sein, konstante Zeiger auf Spielobjekte zu setzen.
Steven Stadnicki
Hallo @StevenStadnicki, vielen Dank für Ihren Kommentar. Es gibt eine Vielzahl von Beispielen, die zeigen, wie ein Interpolationswert an den Renderer übergeben wird. Weitere Informationen finden Sie hier: mysecretroom.com/www/programming-and-software/… , von wo aus ich meine angepasst habe Code. Mein begrenztes Verständnis ist, dass dies die Position zwischen dem letzten und dem nächsten Update basierend auf der Zeit interpoliert, die seit dem letzten Update benötigt wurde - ich stimme zu, dass es ein Albtraum ist! Ich wäre Ihnen dankbar, wenn Sie mir eine Alternative vorschlagen könnten, mit der ich leichter arbeiten kann
Prost
6
Nun, ich sage: "Nur weil Sie Code für etwas finden können, ist dies keine bewährte Methode." :-) In diesem Fall vermute ich jedoch, dass die Seite, auf die Sie verlinkt haben, den Interpolationswert verwendet, um herauszufinden, wo ihre Objekte angezeigt werden sollen, aber die Objektpositionen mit ihnen werden nicht aktualisiert. Sie können dies auch tun, indem Sie eine zeichnungsspezifische Position haben, die für jeden Frame berechnet wird, diese Position jedoch von der tatsächlichen "physischen" Position des Objekts getrennt halten.
Steven Stadnicki
Hallo @StevenStadnicki, wie in meiner Frage beschrieben (der Absatz beginnt mit "Ich habe auch versucht, eine Kopie zu erstellen"), habe ich tatsächlich bereits versucht, eine "Nur zeichnen" -Position zu verwenden :-) Können Sie eine Interpolationsmethode vorschlagen, bei der ich Müssen Sie die Position des Sprites innerhalb der Renderroutine nicht anpassen? Vielen Dank!
BungleBonce
Wenn Sie sich Ihren Code ansehen, sieht es so aus, als würden Sie extrapolieren anstatt interpolieren.
Durza007

Antworten:

1

Ich kann noch keinen Kommentar posten, daher werde ich diesen als Antwort posten.

Wenn ich das Problem richtig verstehe, geht es ungefähr so:

  1. Zuerst hast du eine Kollision
  2. dann wird die Position des Objekts korrigiert (vermutlich durch die Kollisionserkennungsroutine)
  3. Die aktualisierte Position des Objekts wird an die Renderfunktion gesendet
  4. Die Renderfunktion aktualisiert dann die Position des Objekts mithilfe von Extrapolation
  5. Die Position des extrapolierten Objekts unterbricht nun die Kollisionserkennungsroutine

Ich kann mir 3 mögliche Lösungen vorstellen. Ich werde sie in der Reihenfolge auflisten, die meiner Meinung nach am wenigsten wünschenswert ist.

  1. Verschieben Sie die Extrapolation aus der Renderfunktion. Extrapolieren Sie die Position des Objekts und testen Sie dann auf eine Kollision.
  2. Wenn Sie die Extrapolation in der Renderfunktion beibehalten möchten, setzen Sie ein Flag, um anzuzeigen, dass eine Kollision aufgetreten ist. Lassen Sie die Kollisionserkennung die Position des Objekts wie bereits zuvor korrigieren. Bevor Sie jedoch den Extrapolationswert berechnen, überprüfen Sie zuerst das Kollisionsflag. Da sich das Objekt bereits dort befindet, wo es sein muss, muss es nicht verschoben werden.
  3. Die letzte Möglichkeit, die für mich eher eine Umgehung als eine Lösung darstellt, besteht darin, die Kollisionserkennung zu überkompensieren. Bewegen Sie das Objekt nach einer Kollision von der Wand weg, sodass sich das Objekt nach der Extrapolation wieder an der Wand befindet.

Beispielcode für # 2.

if (collisionHasOccured)
    extrpolation = 0.0f;
else
    extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks;

Ich denke, # 2 wäre wahrscheinlich der schnellste und einfachste Weg, um zum Laufen zu kommen, obwohl # 1 eine logisch genauere Lösung zu sein scheint. Abhängig davon, wie Sie mit Ihrer Delta-Zeit-Lösung Nr. 1 umgehen, kann sie durch ein großes Delta auf die gleiche Weise unterbrochen werden. In diesem Fall müssen Sie möglicherweise sowohl Nr. 1 als auch Nr. 2 zusammen verwenden.

EDIT: Ich habe Ihren Code früher falsch verstanden. Die Schleifen sollen so schnell wie möglich gerendert und in einem festgelegten Intervall aktualisiert werden. Aus diesem Grund würden Sie die Sprite-Position interpolieren, um den Fall zu behandeln, in dem Sie mehr zeichnen als aktualisieren. Wenn die Schleife jedoch zurückfällt, fragen Sie beim Aktualisieren ab, bis Sie eingeholt werden oder die maximale Anzahl von Frame-Draws übersprungen haben.

Das einzige Problem ist jedoch, dass sich das Objekt nach einer Kollision bewegt. Bei einer Kollision sollte sich das Objekt nicht mehr in diese Richtung bewegen. Wenn es also zu einer Kollision kommt, setzen Sie die Geschwindigkeit auf 0. Dies sollte verhindern, dass die Renderfunktion das Objekt weiter bewegt.

Aholio
quelle
Hallo @Aholio, ich habe Option 2 ausprobiert und es funktioniert, verursacht aber ein paar Pannen. Vielleicht habe ich es falsch gemacht. Ich werde das noch einmal überprüfen. Ich bin jedoch sehr an Option 1 interessiert, obwohl ich keine Informationen dazu finden kann. Wie kann ich sagen in meiner Logikroutine extrapolieren? Übrigens, mein Delta ist fest, also 1/60 oder 0,01667 (eher ist dies die Zahl, die ich zum Integrieren verwende, obwohl die Zeit, die jede Iteration meines Spiels benötigt, offensichtlich je nach dem, was gerade passiert, variieren kann, aber niemals wirklich mehr als 0,01667 dauern sollte ) also jede hilfe dazu wäre super danke.
BungleBonce
Vielleicht verstehe ich nicht ganz, was Sie tun. Etwas seltsam erscheint mir, dass Sie nicht nur Positionsbewegungen in Ihrer Zeichenfunktion ausführen. Sie führen auch Zeitberechnungen durch. Wird das für jedes Objekt gemacht? Die richtige Reihenfolge sollte lauten: Zeitberechnung im Spielschleifencode. Die Spielschleife übergibt das Delta an eine Update-Funktion (dt). Update (dt) aktualisiert alle Spielobjekte. Es sollte jede Bewegung, Extrapolation und Kollisionserkennung handhaben. Nachdem update () zur Spielschleife zurückgekehrt ist, ruft es eine render () -Funktion auf, die alle frisch aktualisierten Spielobjekte zeichnet.
Aholio
Hmmmmm zur Zeit macht meine Update-Funktion alles. Tut die Bewegung (mit Bewegung meine ich die Berechnung der neuen Position von Sprites - dies wird aus meiner Deltazeit berechnet). Das einzige, was ich in meiner Renderfunktion mache (abgesehen vom eigentlichen Zeichnen), ist die Extrapolation der 'neuen' Position, aber dies verwendet natürlich Zeit, da die Zeit von der letzten Logikaktualisierung bis zum Renderaufruf berücksichtigt werden muss. Das verstehe ich sowieso :-) Wie würdest du das machen? Ich verstehe nicht, wie man Extrapolation nur innerhalb des Logik-Updates verwendet. Vielen Dank!
BungleBonce
Meine Antwort wurde aktualisiert. Hoffe das hilft
Aholio
Danke, siehe in meinem ursprünglichen Q "Wenn es stoppt, wackelt es leicht nach links / rechts" - selbst wenn ich mein Sprite stoppe, hält die Extrapolation das Sprite immer noch in Bewegung (Wackeln) - dies geschieht, weil ich das nicht benutze Die alten und aktuellen Positionen des Sprites, um meine Extrapolation zu berechnen. Selbst wenn das Sprite statisch ist, hat es diesen Wackeleffekt basierend auf dem Extrapolationswert, der bei jeder Frame-Iteration berechnet wird. Ich habe sogar versucht zu sagen: "Wenn alte und aktuelle Position gleich sind, dann nicht extrapolieren", aber das scheint immer noch nicht zu funktionieren. Ich gehe zurück und schaue es mir noch einmal an!
BungleBonce
0

Es sieht so aus, als müssten Sie das Rendern und Aktualisieren der Physik vollständig trennen. Normalerweise wird die zugrunde liegende Simulation in diskreten Zeitschritten ausgeführt, und die Frequenz ändert sich nie. Zum Beispiel könnten Sie die Bewegung Ihres Balls alle 1/60 Sekunde simulieren, und das war's.

Um eine variable Bildrate zu ermöglichen, sollte der Rendering-Code mit einer variablen Frequenz arbeiten, aber jede Simulation sollte sich immer noch in einem festen Zeitschritt befinden. Auf diese Weise können die Grafiken den Speicher aus der Simulation als schreibgeschützt lesen und die Interpolation anstelle der Extrapolation einrichten.

Da die Extrapolation versucht, vorherzusagen, wo zukünftige Werte liegen werden, können plötzliche Änderungen der Bewegung zu großen Extrapolationsfehlern führen. Es ist besser, stattdessen Ihre Szene über einen Frame hinter der Simulation zu rendern und zwischen diskreten bekannten Positionen zu interpolieren.

Wenn Sie einige Details zur Implementierung sehen möchten, habe ich hier bereits einen kurzen Abschnitt zu diesem Thema in einem Artikel geschrieben . Bitte lesen Sie den Abschnitt "Timestepping".

Hier ist der wichtige Pseudocode aus dem Artikel:

const float fps = 100
const float dt = 1 / fps
float accumulator = 0

// In units seconds
float frameStart = GetCurrentTime( )

// main loop
while(true)
  const float currentTime = GetCurrentTime( )

  // Store the time elapsed since the last frame began
  accumulator += currentTime - frameStart( )

  // Record the starting of this frame
  frameStart = currentTime

  // Avoid spiral of death and clamp dt, thus clamping
  // how many times the UpdatePhysics can be called in
  // a single game loop.
  if(accumulator > 0.2f)
    accumulator = 0.2f

  while(accumulator > dt)
    UpdatePhysics( dt )
    accumulator -= dt

  const float alpha = accumulator / dt;

  RenderGame( alpha )

void RenderGame( float alpha )
  for shape in game do
    // calculate an interpolated transform for rendering
    Transform i = shape.previous * alpha + shape.current * (1.0f - alpha)
    shape.previous = shape.current
    shape.Render( i )

Die RenderGameFunktion ist von größtem Interesse. Die Idee ist, Interpolation zwischen diskreten Simulationspositionen zu verwenden. Der Rendering-Code kann eigene Kopien der schreibgeschützten Simulationsdaten erstellen und zum Rendern einen temporären interpolierten Wert verwenden. Dies gibt Ihnen eine sehr reibungslose Bewegung ohne dumme Probleme wie das, was Sie zu haben scheinen!

RandyGaul
quelle