Wie man den Spielstatus angesichts der EDT verwaltet?

8

Ich entwickle einen Echtzeit-Strategiespiel-Klon auf der Java-Plattform und ich habe einige konzeptionelle Fragen darüber, wo ich den Spielstatus einstellen und verwalten soll. Das Spiel verwendet Swing / Java2D als Rendering. In der aktuellen Entwicklungsphase ist keine Simulation und keine KI vorhanden und nur der Benutzer kann den Zustand des Spiels ändern (z. B. Gebäude bauen / abreißen, Produktionslinien hinzufügen, entfernen, Flotten und Ausrüstung zusammenstellen). Daher kann die Spielzustandsmanipulation in dem Ereignis-Dispatch-Thread ohne irgendwelche Wiedergabesuche durchgeführt werden. Der Spielstatus wird auch verwendet, um dem Benutzer verschiedene zusammengefasste Informationen anzuzeigen.

Da ich jedoch eine Simulation einführen muss (zum Beispiel Baufortschritt, Bevölkerungswechsel, Flottenbewegungen, Herstellungsprozess usw.), wird das Ändern des Spielstatus in einem Timer und EDT das Rendering sicherlich verlangsamen.

Nehmen wir an, die Simulation / AI-Operation wird alle 500ms durchgeführt und ich verwende SwingWorker für die Berechnung von etwa 250ms Länge. Wie kann ich sicherstellen, dass es keine Wettlaufsituation zwischen der Simulation und der möglichen Benutzerinteraktion gibt?

Ich weiß, dass das Ergebnis der Simulation (das ist eine kleine Menge an Daten) effizient über den Aufruf SwingUtilities.invokeLater () zurück in den EDT verschoben werden kann.

Das Spielzustandsmodell scheint zu komplex zu sein, um unmöglich zu sein, nur unveränderliche Werteklassen überall zu verwenden.

Gibt es einen relativ korrekten Ansatz, um diese Lese-Race-Bedingung zu eliminieren? Vielleicht ein vollständiges / teilweises Klonen des Spiels bei jedem Tick des Timers machen oder den Lebensraum des Spielzustandes von EDT in einen anderen Thread ändern?

Update: (aus den Kommentaren, die ich gab) Das Spiel arbeitet mit 13 KI kontrollierten Spielern, 1 menschlichen Spieler und hat ungefähr 10000 Spielobjekte (Planeten, Gebäude, Ausrüstung, Forschung, etc.). Ein Spielobjekt zum Beispiel hat die folgenden Attribute:

%Vor%

In einem Szenario baut der Benutzer ein neues Gebäude auf diesem Planeten. Dies wird in EDT durchgeführt, da die Karten- und Gebäudesammlung geändert werden muss. Parallel dazu wird alle 500ms eine Simulation durchgeführt, um die Energiezuordnung zu den Gebäuden auf allen Spielplaneten zu berechnen, die die Gebäudesammlung für die Statistikerfassung durchqueren müssen. Wenn die Zuweisung berechnet wird, wird sie dem EDT vorgelegt und das Energiefeld jedes Gebäudes zugewiesen.

Nur menschliche Spielerinteraktionen haben diese Eigenschaft, weil die Ergebnisse der AI-Berechnung sowieso auf die Strukturen im EDT angewendet werden.

Im Allgemeinen sind 75% der Objektattribute statisch und werden nur zum Rendern verwendet. Der Rest ist entweder über Benutzerinteraktion oder Simulation / KI-Entscheidung veränderbar. Es wird auch sichergestellt, dass kein neuer Simulations / AI-Schritt gestartet wird, bis der vorherige alle Änderungen zurückgeschrieben hat.

Meine Ziele sind:

  • Vermeide es, die Benutzerinteraktion zu verzögern, z. Benutzer platziert das Gebäude auf den Planeten und erst nach 0.5s bekommt das visuelle Feedback
  • Vermeiden Sie, den EDT mit Berechnungen, Lock Wait usw. zu blockieren.
  • Concurrency-Probleme mit Sammlungsdurchlauf und -änderung, Attributänderungen vermeiden

Optionen:

  • Feinkörnige Objektsperre
  • Unveränderbare Sammlungen
  • Flüchtige Felder
  • Teilweise Momentaufnahme

Alle diese haben Vorteile, Nachteile und Ursachen für das Modell und das Spiel.

Update 2: Ich spreche von diesem Spiel. Mein Klon ist hier . Die Screenshots können helfen, sich die Rendering- und Datenmodell-Interaktionen vorzustellen.

Update 3:

Ich werde versuchen, ein kleines Codebeispiel zu geben, um mein Problem zu klären, wie es aus den Kommentaren zu verstehen ist, die es missverstanden wird:

%Vor%

Die Überlappung liegt also zwischen onAddBuildingClicked () und distributePower (). Stellen Sie sich jetzt den Fall vor, in dem Sie 50 solcher Überlappungen zwischen verschiedenen Teilen des Spielmodells haben.

    
akarnokd 11.06.2009, 15:46
quelle

8 Antworten

3

Das klingt nach einem Client / Server-Ansatz:

Der Player ist ein Client - Interaktivität und Rendering passieren an diesem Ende. So drückt der Spieler eine Taste, die Anfrage geht an den Server. Die Antwort vom Server kommt zurück und der Status des Spielers wird aktualisiert. Zu jedem Zeitpunkt zwischen diesen Ereignissen kann der Bildschirm neu gezeichnet werden und spiegelt den Zustand des Spiels wider, so wie der Client es derzeit kennt.

Die KI ist ebenfalls ein Client - sie entspricht einem Bot.

Die Simulation ist der Server. Er erhält von seinen Clients zu verschiedenen Zeiten Updates und aktualisiert den Zustand der Welt, und sendet diese Aktualisierungen dann gegebenenfalls an alle weiter. Hier passt es sich Ihrer Situation an: Die Simulation / KI erfordert eine statische Welt und viele Dinge geschehen gleichzeitig. Der Server kann Änderungsanforderungen einfach in die Warteschlange stellen und sie anwenden, bevor er die Aktualisierungen an die Clients zurücksendet. Soweit der Server betroffen ist, verändert sich die Spielwelt nicht wirklich in Echtzeit, sondern ändert sich immer dann, wenn der Server sich gut entscheidet.

Schließlich können Sie auf der Clientseite die Verzögerung zwischen dem Drücken der Schaltfläche und dem Anzeigen eines Ergebnisses verhindern, indem Sie einige schnelle Näherungsberechnungen durchführen und ein Ergebnis anzeigen (damit die unmittelbare Notwendigkeit erfüllt ist) und dann das korrektere Ergebnis anzeigen Der Server kommt zu Ihnen.

Beachten Sie, dass dies nicht in einer Art TCP / IP over-the-Internet implementiert werden muss, nur dass es so aussieht.

Alternativ können Sie die Verantwortung dafür übernehmen, dass die Daten während der Simulation in einer Datenbank kohärent bleiben, da sie bereits im Hinblick auf Sperren und Kohärenz erstellt wurden. So etwas wie sqlite könnte als Teil einer nicht vernetzten Lösung funktionieren.

    
Atiaxi 30.06.2009, 19:15
quelle
0

Ich bin mir nicht sicher, ob ich das von Ihnen gesuchte Verhalten vollständig verstehe, aber es klingt, als ob Sie etwas wie einen Statuswechsel Thread / Warteschlange benötigen, so dass alle Statusänderungen von einem einzigen Thread behandelt werden.

Erstellen Sie eine API, vielleicht wie SwingUtilities.invokeLater () und / oder SwingUtilities.invokeAndWait () für Ihre Statusänderungswarteschlange, um Ihre Statusänderungsanforderungen zu bearbeiten.

Wie sich das in der GUI widerspiegelt, hängt von dem Verhalten ab, nach dem Sie suchen. d. h. Geld kann nicht abgehoben werden, da der aktuelle Status 0 € ist, oder dem Benutzer angezeigt wird, dass das Konto leer war, als die Auszahlungsanforderung verarbeitet wurde. (wahrscheinlich nicht mit dieser Terminologie ;-))

    
user101884 11.06.2009 17:18
quelle
0

Am einfachsten ist es, die Simulation so schnell zu machen, dass sie im EDT läuft. Bevorzuge Programme, die funktionieren!

Für das Zwei-Thread-Modell empfehle ich, das Domänenmodell mit einem Rendering-Modell zu synchronisieren. Das Rendermodell sollte Daten darüber speichern, was aus dem Domänenmodell stammt.

Für ein Update: Im Simulations-Thread das Render-Modell sperren. Überqueren Sie die Rendermodellaktualisierung, wenn sich die Dinge von den erwarteten Änderungen unterscheiden, aktualisieren Sie das Rendermodell. Wenn Sie mit dem Traversieren fertig sind, entsperren Sie das Render-Modell und planen Sie ein Repaint. Beachten Sie, dass Sie bei diesem Ansatz keine Bazillion-Listener benötigen.

Das Rendermodell kann unterschiedliche Tiefen haben. In einem Extremfall könnte es ein Bild sein, und die Aktualisierungsoperation dient lediglich dazu, eine einzelne Referenz durch das neue Bildobjekt zu ersetzen (dies wird beispielsweise die Größenänderung oder andere Oberflächeninteraktionen sehr gut handhaben). Sie sollten nicht überprüfen, ob ein Element geändert wurde und einfach alles aktualisieren.

    
Tom Hawtin - tackline 11.06.2009 17:35
quelle
0

Wenn der Zustand des Spiels schnell geändert wird (sobald Sie wissen, was Sie ändern sollen), können Sie den Spielstatus wie andere Swing-Modelle behandeln und nur den Status im EDT ändern oder anzeigen. Wenn das Ändern des Spielstatus nicht schnell ist, dann können Sie entweder die Zustandsänderung synchronisieren und dies in Swing Worker / Timer (aber nicht in der EDT) tun oder Sie können es in einem separaten Thread tun, den Sie ähnlich behandeln wie die EDT Betrachten Sie die Verwendung von BlockingQueue , um Änderungsanforderungen zu bearbeiten. Letzteres ist nützlicher, wenn die Benutzeroberfläche niemals Informationen aus dem Spielstatus abrufen muss, sondern die Rendering-Änderungen über Listener oder Beobachter gesendet werden.

    
Kathy Van Stone 11.06.2009 19:35
quelle
0

Ist es möglich, den Spielstatus schrittweise zu aktualisieren und trotzdem ein konsistentes Modell zu haben? Zum Beispiel berechnen Sie für eine Teilmenge von Planeten / Spieler / Flotte Objekte zwischen Rendern / Benutzer Updates.

Wenn ja, könnten Sie inkrementelle Updates im EDT ausführen, die nur einen kleinen Teil des Status berechnen, bevor der EDT Benutzereingaben verarbeiten und rendern kann.

Nach jeder inkrementellen Aktualisierung im EDT müssen Sie sich merken, wie viel vom Modell noch aktualisiert werden muss, und einen neuen SwingWorker auf dem EDT planen, um diese Verarbeitung fortzusetzen, nachdem ausstehende Benutzereingaben und Rendering ausgeführt wurden.

>

Dies sollte es Ihnen ermöglichen, das Kopieren oder Sperren des Spielmodells zu vermeiden, während die Benutzerinteraktionen immer noch ansprechend bleiben.

    
Aaron 14.06.2009 10:42
quelle
0

Ich denke, Sie sollten World nicht irgendwelche Daten speichern oder Änderungen an irgendwelchen Objekten selbst vornehmen, es sollte nur verwendet werden, um einen Verweis auf ein Objekt beizubehalten, und wenn dieses Objekt geändert werden muss, muss der Player die Änderung ändern es direkt. In diesem Fall müssen Sie nur jedes Objekt in der Spielwelt synchronisieren, damit kein anderer Spieler Änderungen vornehmen kann, wenn ein Spieler Änderungen vornimmt. Hier ist ein Beispiel für das, was ich denke:

Spieler A muss etwas über einen Planeten wissen, also fragt er die Welt nach diesem Planeten (wie hängt es von Ihrer Implementierung ab). World gibt einen Verweis auf das Planet-Objekt zurück, um das Spieler A gebeten hat. Spieler A beschließt, eine Änderung vorzunehmen, also tut er dies. Nehmen wir an, es fügt ein Gebäude hinzu. Die Methode, dem Planeten ein Gebäude hinzuzufügen, wird synchronisiert, so dass nur ein Spieler dies gleichzeitig tun kann. Das Gebäude wird seine eigene Bauzeit (falls vorhanden) verfolgen, so dass die Add-Building-Methode des Planeten fast sofort frei wird. Auf diese Weise können mehrere Spieler gleichzeitig Informationen zum selben Planeten anfordern, ohne dass sie sich gegenseitig beeinflussen, und die Spieler können Gebäude fast gleichzeitig hinzufügen, ohne dass es zu einer Verzögerung kommt. Wenn zwei Spieler nach einem Platz suchen, um das Gebäude zu platzieren (wenn das ein Teil Ihres Spiels ist), dann wird die Überprüfung der Eignung eines Ortes eine Frage sein, keine Änderung.

Es tut mir leid, wenn das nicht beantwortet, Sie sind Frage, ich bin mir nicht sicher, ob ich es richtig verstanden habe.

    
mnuzzo 19.06.2009 03:58
quelle
0

Wie wäre es mit der Implementierung einer Architektur für Rohrleitungen und Filter? Pipes verbinden Filter miteinander und stellen Anfragen in Warteschlangen, wenn der Filter nicht schnell genug ist. Die Verarbeitung erfolgt innerhalb von Filtern. Der erste Filter ist die AI-Engine, während die Rendering-Engine durch eine Folge von Filtern implementiert wird.

Bei jedem Timer-Tick wird der neue dynamische Weltzustand basierend auf allen Eingaben berechnet (Zeit ist ebenfalls eine Eingabe) und eine Kopie wird in die erste Pipe eingefügt.

Im einfachsten Fall ist Ihre Rendering-Engine als einzelner Filter implementiert. Es nimmt nur die Zustands-Schnappschüsse von der Eingangsleitung und rendert sie zusammen mit dem statischen Zustand. In einem Live-Spiel kann es vorkommen, dass die Rendering-Engine Zustände überspringt, wenn sich mehrere in der Pipe befinden. Wenn Sie einen Benchmark erstellen oder ein Video ausgeben, möchten Sie jedes Video rendern.

Je mehr Filter Sie in Ihre Rendering-Engine einfügen können, desto besser ist die Parallelität. Vielleicht ist es sogar möglich, die AI-Engine zu zerlegen, z. Vielleicht möchten Sie den dynamischen Zustand in einen sich schnell ändernden und sich langsam ändernden Zustand aufteilen.

Diese Architektur bietet Ihnen eine gute Parallelität ohne viel Synchronisation.

Ein Problem mit dieser Architektur besteht darin, dass die Garbage-Collection häufig ausgeführt wird und alle Threads jedes Mal eingefroren wird, wodurch der durch Multi-Threading gewonnene Vorteil zunichte gemacht wird.

    
Hans Malherbe 21.06.2009 16:49
quelle
0

Es sieht so aus, als ob Sie eine Prioritätswarteschlange benötigen, um die Aktualisierungen in das Modell zu übernehmen, in denen Aktualisierungen für den Benutzer Vorrang vor den Aktualisierungen von der Simulation und anderen Eingaben haben. Was ich höre, ist, dass der Benutzer immer ein sofortiges Feedback über seine Aktionen benötigt, während die anderen Eingaben (Simulation, sonst) Arbeiter haben könnten, die länger als einen Simulationsschritt dauern können. Synchronisieren Sie dann auf der Prioritätswarteschlange.

    
nojevive 27.06.2009 12:52
quelle