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.
Antworten:
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.
quelle
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).
quelle
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.
quelle
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).
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.
quelle