2013-02-26 18 views
6

Ich habe vor kurzem versucht, in die Programmierung von Spielen zu kommen. Ich bin ziemlich erfahren mit Java, aber nicht mit Spieleprogrammierung. Ich las http://www.koonsolo.com/news/dewitters-gameloop/ und implementiert, um die Spielschleife mit dem folgenden Code vorgeschlagen dort:Geht "deWiTTERS Game Loop" von einer konstanten UPS aus?

private static int UPDATES_PER_SECOND = 25; 
private static int UPDATE_INTERVAL = 1000/UPDATES_PER_SECOND * 1000000; 
private static int MAX_FRAMESKIP = 5; 

public void run() { 
    while (true) { 
     int skippedFrames = 0; 
     while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) { 
      this.updateGame(); 
      this.nextUpdate += UPDATE_INTERVAL; 
      skippedFrames++; 
     } 

     long currentNanoTime = System.nanoTime(); 
     double interpolation = (currentNanoTime + UPDATE_INTERVAL - this.nextUpdate)/UPDATE_INTERVAL; 
     this.repaintGame(interpolation); 
    } 
} 

Die Schleife viel versprechend und leicht aussah, aber jetzt, dass ich versuche tatsächlich etwas zu tun, damit mir nicht mehr so ​​sicher. Wenn ich mich nicht komplett irre updateGame() kümmert sich um Dinge wie das Berechnen von Positionen, Bewegen von Feinden, Berechnen von Kollisionen, ...? Da die Interpolation nicht an updateGame() übergeben wird, bedeutet das, dass wir davon ausgehen, dass updateGame() genau und stetig UPDATES_PER_SECOND mal pro Sekunde aufgerufen wird? Bedeutet das, dass alle unsere Berechnungen auf dieser Annahme basieren? Und würde uns das nicht viel Ärger bereiten, wenn - aus welchen Gründen auch immer - sich der Aufruf von updateGame() verzögert?
Zum Beispiel, wenn mein Charakter Sprite soll nach rechts gehen und wir bewegen es entsprechend seiner Geschwindigkeit auf jedem updateGame() - wenn die Methode verzögert werden würde, würde dies bedeuten, dass unsere Berechnungen einfach aus sind und der Charakter würde nacheilen?

Auf der Website wird das folgende Beispiel für die Interpolation angegeben:

Wenn im 10. gametick die Position 500 ist, und die Geschwindigkeit ist 100, dann in der 11. die Position 600 sein gametick wird. Also, wo werden Sie Ihr Auto platzieren, wenn Sie es rendern? Du könntest einfach die Position des letzten Spielticks einnehmen (in diesem Fall 500). Aber ein besserer Weg ist, vorherzusagen, wo das Auto auf exakt 10,3 wäre, und dies geschieht wie folgt aus:
view_position = position + (speed * interpolation)
Das Auto wird dann an der Position wiedergegeben werden 530.

Ich weiß, es ist nur ein Beispiel, aber würde das die auto geschwindigkeit nicht von der UPDATES_PER_SECOND abhängig machen? Also würde mehr UPS ein schnelleres Auto bedeuten? Das kann nicht stimmen ...?

Jede Hilfe, Tutorial, was auch immer geschätzt wird. (Danke!)

UPDATE/SOLUTION

Schließlich Hilfe, hier ist das, was ich bin derzeit mit - funktioniert ziemlich gut so weit (für ein Charakter-Sprites in Bewegung), aber lassen Sie sich für das wirklich komplizierte Spiel Zeug warten um das zu entscheiden. Trotzdem wollte ich das teilen.
Mein Spiel Schleife sieht nun wie folgt aus:

private static int UPDATES_PER_SECOND = 25; 
private static int UPDATE_INTERVAL = 1000/UPDATES_PER_SECOND * 1000000; 
private static int MAX_FRAMESKIP = 5; 

private long nextUpdate = System.nanoTime(); 

public void run() { 
    while (true) { 
     int skippedFrames = 0; 
     while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) { 
      long delta = UPDATE_INTERVAL; 
      this.currentState = this.createGameState(delta); 
      this.newPredictedNextState = this.createGameState(delta + UPDATE_INTERVAL, true); 

      this.nextUpdate += UPDATE_INTERVAL; 
      skippedFrames++; 
     } 

     double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate)/(double) UPDATE_INTERVAL; 
     this.repaintGame(interpolation); 
    } 
} 

Wie Sie ein paar Änderungen sehen können:

  • delta = UPDATE_INTERVAL? Ja. Das ist bisher experimentell, aber ich denke, es wird funktionieren. Das Problem ist, sobald Sie ein Delta aus zwei Zeitstempeln berechnen, führen Sie Float-Berechnungsfehler ein. Diese sind klein, aber wenn man bedenkt, dass Ihr Update millionenfach aufgerufen wird, summieren sich diese. Und da die zweite while-Schleife sicherstellt, dass wir verpasste Updates nachholen (falls das Rendering lange dauert), können wir ziemlich sicher sein, dass wir unsere 25 Updates pro Sekunde bekommen. Worst Case: Wir vermissen mehr als MAX_FRAMESKIP Updates - in diesem Fall werden Updates verloren gehen und das Spiel wird nacheilen. Trotzdem, wie ich schon sagte, experimentell. Ich könnte dies wieder in ein tatsächliches Delta ändern.
  • VorhersagenNächster Spielstatus? Ja. Der GameState ist das Objekt, das alle relevanten Spielinformationen enthält, der Renderer erhält dieses Objekt, um das Spiel auf den Bildschirm zu rendern.In meinem Fall habe ich beschlossen, dem Renderer zwei Zustände zu geben: Den, den wir normalerweise mit dem aktuellen Spielstatus passieren würden, und einen vorhergesagten zukünftigen Zustand, UPDATE_INTERVAL, in der Zukunft. Auf diese Weise kann der Renderer den Interpolationswert verwenden, um einfach zwischen beiden zu interpolieren. Den zukünftigen Spielstatus zu berechnen ist eigentlich ganz einfach - da Ihre Update-Methode (createGameState()) sowieso einen Delta-Wert annimmt, erhöhen Sie einfach das Delta um UPDATE_INTERVAL - so wird ein zukünftiger Zustand vorhergesagt. Der zukünftige Zustand setzt natürlich voraus, dass Benutzereingaben usw. gleich bleiben. Wenn dies nicht der Fall ist, wird die nächste Spielstatusaktualisierung die Änderungen übernehmen.
  • Der Rest bleibt ziemlich gleich und stammt aus deWiTTERS Spielschleife. MAX_FRAMESKIP ist so ziemlich ausfallsicher, falls die Hardware WIRKLICH langsam ist, um sicherzustellen, dass wir von Zeit zu Zeit etwas rendern. Aber wenn das einsetzt, werden wir sowieso extreme Verzögerungen haben. Die Interpolation ist die gleiche wie zuvor - aber jetzt kann der Renderer einfach zwischen zwei Gamestates interpolieren, er muss keine Logik haben außer interpolierende Zahlen. Das ist schön!

Vielleicht zur Verdeutlichung, ein Beispiel. Hier ist, wie ich die Zeichenposition (vereinfacht ein wenig) zu berechnen:

public GameState createGameState(long delta, boolean ignoreNewInput) { 
    //Handle User Input and stuff if ignoreNewInput=false 

    GameState newState = this.currentState.copy(); 
    Sprite charSprite = newState.getCharacterSprite(); 
    charSprite.moveByX(charSprite.getMaxSpeed() * delta * charSprite.getMoveDirection().getX()); 
    //getMoveDirection().getX() is 1.0 when the right arrow key is pressed, otherwise 0.0 
} 

... dann in der Farbe Methode des Fensters ...

public void paint(Graphics g) { 
    super.paint(g); 

    Graphics2D g2d = (Graphics2D) g; 

    Sprite currentCharSprite = currentGameState.getCharacterSprite(); 
    Sprite nextCharSprite = predictedNextState.getCharacterSprite(); 
    Position currentPos = currentCharSprite.getPosition(); 
    Position nextPos = nextCharSprite.getPosition(); 
    //Interpolate position 
    double x = currentPos.getX() + (nextPos.getX() - currentPos.getX()) * this.currentInterpolation; 
    double y = currentPos.getY() + (nextPos.getY() - currentPos.getY()) * this.currentInterpolation; 
    Position interpolatedCharPos = new Position(x, y); 
    g2d.drawImage(currentCharSprite.getImage(), (int) interpolatedCharPos.getX(), (int) interpolatedCharPos.getY(), null); 
} 

Antwort

2

Sie nicht Ihre Spiellogik stützen unter der Annahme, dass die Aktualisierungsintervalle konstant sind. Fügen Sie eine Spieluhr ein, die genau die Zeit zwischen zwei Aktualisierungen misst. Sie können dann alle Ihre Berechnungen auf die Verzögerung stützen und müssen sich nicht um die tatsächliche Aktualisierungsrate kümmern.

In diesem Fall würde die Autos Geschwindigkeit in Einheiten/Sekunde und das Delta wäre die Gesamt Sekunden seit dem letzten Update gegeben:

car.position += car.velocity * delta; 

Es ist eine gängige Praxis von der Spiellogik Aktualisierung zu trennen der Zeichnung Rahmen. Wie Sie sagten, ermöglicht dies eine konstante Aktualisierungsrate, indem das Rendern eines Frames hin und wieder übersprungen wird.

Aber es geht nicht so sehr darum, die Aktualisierungsintervalle konstant zu halten. Es ist nur sehr wichtig, eine minimale Anzahl von Updates pro Zeiteinheit zu haben. Stellen Sie sich ein Fahrzeug vor, das sich sehr schnell in Richtung eines Hindernisses bewegt. Wenn die Aktualisierungsfrequenz zu niedrig ist, könnte die zurückgelegte Entfernung zwischen zwei Aktualisierungen größer als die Gesamtgröße der Hindernisse sein. Das Fahrzeug würde sich direkt hindurch bewegen.

+0

Danke für die Antwort. Die Sache, die mich verwirrt, ist, dass deWITTER in seiner Spielschleife vorschlägt, das Delta (oder die Interpolation, die im Grunde aus dem Delta berechnet wird) nur an die Rendering-Funktion zu übergeben, aber nicht die Update-Funktion, die sich um die Spiellogik kümmert. Also sagen Sie, dass beide Funktionen den Delta-Wert auf die eine oder andere Weise erhalten sollten? – BlackWolf

+0

Ich würde sagen, dass das Delta nur in der Update-Methode benötigt wird, da hier die Spielwelt verändert wird. Es sollte ausreichen, dass die Rendermethode * nur * das [Spielweltmodell] (http://stackoverflow.com/tags/model/info) liest. Es schafft eine Darstellung der Welt, sollte aber seinen Zustand nicht ändern; Daher scheint das Delta in der Render-Methode unnötig zu sein. – Lucius

+1

nun, ja, ich denke ich werde es dann einfach so umsetzen, da dies auch für mich mehr Sinn macht. Als Nebenbemerkung denke ich immer noch, dass die Weitergabe des Delta an das Rendering immer noch nützlich sein kann - es ermöglicht es, Bewegungen zwischen zwei Spielupdates zu interpolieren. Ansonsten, wenn es 25 Spielupdates pro Sekunde aber 200 Renderupupdates pro Sekunde gäbe, würden 175 Renderings grundsätzlich verschwendet werden. Wie Sie gesagt haben, kann dieses _could_ bedeuten, dass ein Auto "durch" ein Hindernis fährt, aber es ist wahrscheinlich, dass die Update-Methode oft genug aufgerufen wird, so dass dies nicht geschieht/nicht bemerkt wird. – BlackWolf