Zeitumkehrmechanismus in Spielen

10

Ich frage mich, wie Zeitmanipulationsmechanismen in Spielen normalerweise aufgebaut sind. Ich bin besonders an Zeitumkehr interessiert (ähnlich wie in der neuesten SSX oder Prince of Persia).

Das Spiel ist ein 2D-Top-Down-Shooter.

Der Mechanismus, den ich entwerfen / implementieren möchte, stellt die folgenden Anforderungen:

1) Aktionen von Entitäten außer dem Spielercharakter sind vollständig deterministisch.

  • Die Aktion, die eine Entität ausführt, basiert auf den seit dem Start des Levels fortschreitenden Frames und / oder der Position des Spielers auf dem Bildschirm
  • Entitäten werden zur festgelegten Zeit während des Levels erzeugt.

2) Die Zeitumkehr funktioniert durch Zurückkehren in Echtzeit.

  • Spieleraktionen werden ebenfalls umgekehrt, es wird in umgekehrter Reihenfolge wiederholt, was der Spieler ausgeführt hat. Der Spieler hat während der Rückwärtszeit keine Kontrolle.
  • Die Zeit für das Umkehren ist unbegrenzt. Auf Wunsch können wir bis zum Anfang des Levels umkehren.

Als Beispiel:

Frames 0-50: Der Spieler bewegt sich in dieser Zeit um 20 Einheiten vorwärts. Feind 1 erscheint in Frame 20. Feind 1 bewegt sich in Frame 30-40 um 10 Einheiten nach links. Der Spieler schießt eine Kugel auf Frame 45. Die Kugel bewegt sich 5 vorwärts (45-50) und tötet Feind 1 um Rahmen 50

Wenn Sie dies umkehren, wird dies in Echtzeit wiedergegeben: Der Spieler bewegt sich in dieser Zeit 20 Einheiten rückwärts. Feind 1 wird bei Bild 50 wiederbelebt. Die Kugel erscheint bei Bild 50 wieder. Die Kugel bewegt sich rückwärts 5 und verschwindet (50-45). Der Feind bewegt sich nach links. 10 (40-30) Der Feind wird bei entfernt Rahmen 20.

Als ich nur die Bewegung betrachtete, hatte ich einige Ideen, wie ich dies erreichen könnte. Ich dachte daran, eine Schnittstelle zu haben, die das Verhalten ändert, wenn die Zeit voranschreitet oder sich umkehrt. Anstatt so etwas zu tun:

void update()
{
    movement += new Vector(0,5);
}

Ich würde so etwas machen:

public interface movement()
{
    public void move(Vector v, Entity e);
}

public class advance() implements movement
{
    public void move(Vector v, Entity e)
    {
            e.location += v;
    }
}


public class reverse() implements movement
{
    public void move(Vector v, Entity e)
    { 
        e.location -= v;
    }
}

public void update()
{
    moveLogic.move(new vector(5,0));
}

Ich erkannte jedoch, dass dies in Bezug auf die Leistung nicht optimal wäre und für fortgeschrittenere Aktionen (wie z. B. sanfte Bewegungen entlang gekrümmter Pfade usw.) schnell kompliziert werden würde.

Jkh2
quelle
1
Ich habe das alles nicht gesehen (wackelige YouTube-Kamera, 1,5 Stunden) , aber vielleicht gibt es einige Ideen, dass Jonathan Blow dies in seinem Spiel Braid getan hat.
MichaelHouse
Mögliches Duplikat von gamedev.stackexchange.com/questions/15251/…
Hackworth

Antworten:

9

Vielleicht haben Sie einen Blick auf die nehmen wollen Befehlsmuster .

Grundsätzlich wird jede umkehrbare Aktion, die Ihre Entitäten ausführen, als Befehlsobjekt implementiert. Alle diese Objekte implementieren mindestens zwei Methoden: Execute () und Undo () sowie alles, was Sie sonst noch benötigen, z. B. eine Zeitstempeleigenschaft für das korrekte Timing.

Immer wenn Ihre Entität eine umkehrbare Aktion ausführt, erstellen Sie zuerst ein geeignetes Befehlsobjekt. Sie speichern es auf einem Rückgängig-Stapel, geben es dann in Ihre Spiel-Engine ein und führen es aus. Wenn Sie die Zeit umkehren möchten, platzieren Sie Aktionen oben im Stapel und rufen ihre Undo () -Methode auf, die das Gegenteil der Execute () -Methode bewirkt. Wenn Sie beispielsweise von Punkt A nach Punkt B springen, führen Sie einen Sprung von B nach A durch.

Nachdem Sie eine Aktion geöffnet haben, speichern Sie sie auf einem Wiederherstellungsstapel, wenn Sie nach Belieben vorwärts und rückwärts gehen möchten, genau wie die Rückgängig- / Wiederherstellungsfunktion in einem Texteditor oder Malprogramm. Natürlich müssen Ihre Animationen auch einen "Rücklauf" -Modus unterstützen, um sie rückwärts abzuspielen.

Lassen Sie für mehr Spieldesign-Spielereien jede Entität ihre Aktionen auf einem eigenen Stapel speichern, damit Sie sie unabhängig voneinander rückgängig machen / wiederholen können.

Ein Befehlsmuster hat andere Vorteile: Zum Beispiel ist es ziemlich trivial, einen Wiederholungsrekorder zu erstellen, da Sie lediglich alle Objekte auf den Stapeln in einer Datei speichern und zur Wiederholungszeit einfach einzeln in die Spiel-Engine einspeisen müssen ein.

Hackworth
quelle
2
Beachten Sie, dass die Reversibilität von Aktionen in einem Spiel sein , kann sehr heikles Ding, wegen Gleitkommagenauigkeit Probleme, variable Zeitschritte, etc; Es ist viel sicherer, diesen Status zu speichern, als ihn in den meisten Situationen neu zu erstellen.
Steven Stadnicki
@StevenStadnicki Vielleicht, aber es ist definitiv möglich. C & C Generals macht das auf diese Weise. Es hat stundenlange Wiederholungen von bis zu 8 Spielern, die im schlimmsten Fall ein paar hundert kB wiegen, und ich denke, dass die meisten, wenn nicht alle RTS-Spiele ihren Multiplayer-Modus ausführen: Sie können einfach nicht den vollständigen Spielstatus mit potenziell Hunderten übertragen Von Einheiten pro Frame müssen Sie die Engine die Aktualisierung durchführen lassen. Also ja, es ist definitiv machbar.
Hackworth
3
Replay ist eine sehr unterschiedliche Sache aus Zurückspulen, weil Operationen , die durchgängig reproduzierbare nach vorn sind (beispielsweise die Position , bei der Suche nach Rahmen n, x_n, indem sie mit x_0 = 0 begann und Hinzufügen der Deltas für jeden Schritt V_n) sind nicht unbedingt reproduzierbar rückwärts ;; (x + v_n) -v_n ist in der Gleitkomma-Mathematik nicht konsistent gleich x. Es ist leicht zu sagen, dass es umgangen werden muss, aber Sie sprechen möglicherweise von einer vollständigen Überarbeitung, einschließlich der Tatsache, dass nicht viele externe Bibliotheken verwendet werden können.
Steven Stadnicki
1
Bei einigen Spielen kann Ihr Ansatz machbar sein, aber AFAIK die meisten Spiele , die Zeitumkehr als Mechaniker verwenden verwenden etwas näher an OriginalDaemon des Memento Ansatz , bei dem der jeweilige Zustand wird für jeden Rahmen gespeichert.
Steven Stadnicki
2
Was ist mit dem Zurückspulen, indem Sie die Schritte neu berechnen, aber alle paar Sekunden einen Keyframe speichern? Gleitkommafehler machen wahrscheinlich nicht in wenigen Sekunden einen signifikanten Unterschied (natürlich abhängig von der Komplexität). Es wird auch gezeigt, dass es in der Videokomprimierung funktioniert: P
Tharwen
1

Sie können sich das Memento-Muster ansehen. Die Hauptabsicht besteht darin, Undo / Redo-Operationen durch Zurücksetzen des Objektstatus zu implementieren. Für bestimmte Arten von Spielen sollte dies jedoch ausreichen.

Für ein Spiel in einer Echtzeitschleife können Sie jeden Frame Ihrer Operationen als Statusänderung betrachten und speichern. Dies ist ein einfacher Ansatz zur Implementierung. Die Alternative besteht darin, abzufangen, wenn der Status eines Objekts geändert wird. Erkennen beispielsweise, wann sich die auf einen starren Körper einwirkenden Kräfte ändern. Wenn Sie Eigenschaften zum Abrufen und Festlegen von Variablen verwenden, kann dies auch eine relativ einfache Implementierung sein. Der schwierige Teil besteht darin, zu ermitteln, wann der Status zurückgesetzt werden muss, da dies nicht für jedes Objekt dieselbe Zeit ist (Sie können die speichern Rollback-Zeit als Frame-Anzahl ab dem Start des Systems).

OriginalDaemon
quelle
0

In Ihrem speziellen Fall sollte die Handhabung des Rollbacks durch Zurückspulen der Bewegung gut funktionieren. Wenn Sie mit den KI-Einheiten irgendeine Form der Pfadfindung verwenden, müssen Sie diese nach dem Rollback neu berechnen, um überlappende Einheiten zu vermeiden.

Das Problem ist die Art und Weise, wie Sie mit der Bewegung selbst umgehen: Eine anständige Physik-Engine (ein 2D-Top-Down-Shooter ist mit einer sehr einfachen in Ordnung), die Informationen über vergangene Schritte (einschließlich Position, Nettokraft usw.) verfolgt, liefert eine feste Basis. Wenn Sie dann ein maximales Rollback und eine Granularität für die Rollback-Schritte festlegen, sollten Sie das gewünschte Ergebnis erzielen.

Dunkle Flügel
quelle
0

Dies ist zwar eine interessante Idee. Ich würde davon abraten.

Das Vorwärtsspielen des Spiels funktioniert einwandfrei, da eine Operation immer den gleichen Effekt auf den Spielstatus hat. Dies bedeutet nicht, dass Sie durch den umgekehrten Vorgang den ursprünglichen Zustand erhalten. Bewerten Sie beispielsweise den folgenden Ausdruck in einer beliebigen Programmiersprache (deaktivieren Sie die Optimierung).

(1.1 + 3 - 3) == 1.1

Zumindest in C und C ++ wird false zurückgegeben. Obwohl der Unterschied gering sein mag, stellen Sie sich vor, wie viele Fehler sich bei 60 fps über 10 Sekunden von Minuten ansammeln können. Es wird Fälle geben, in denen ein Spieler nur etwas verpasst, es aber trifft, während das Spiel rückwärts wiederholt wird.

Ich würde empfehlen, Keyframes jede halbe Sekunde zu speichern. Dies nimmt nicht zu viel Speicherplatz in Anspruch. Sie können dann entweder zwischen Keyframes interpolieren oder noch besser die Zeit zwischen zwei Keyframes simulieren und dann rückwärts wiedergeben.

Wenn Ihr Spiel nicht zu kompliziert ist, speichern Sie einfach 30 Mal pro Sekunde Keyframes des Spielstatus und spielen Sie diese rückwärts. Wenn Sie 15 Objekte mit jeweils einer 2D-Position hätten, würde es gut 1,5 Minuten dauern, bis ein MB ohne Komprimierung erreicht ist. Computer haben Gigabyte Speicher.

Überkomplizieren Sie es also nicht, es wird nicht einfach sein, ein Spiel rückwärts zu spielen, und es wird viele Fehler verursachen.

Hannesh
quelle