Frage

Ich entwickle einen Echtzeit-Strategie-Spiel Klon auf der Java-Plattform und ich habe einige konzeptionellen Fragen darüber, wo zu setzen und wie das Spiel Zustand zu verwalten. Das Spiel nutzt Schwingen / Java2D als Rendering. In der aktuellen Entwicklungsphase keine Simulation und kein AI vorhanden ist und nur der Benutzer in der Lage, den Zustand des Spiels zu ändern (zum Beispiel Bau / ein Gebäude abzureißen, Produktionslinien Add-entfernen, montieren Flotten und Ausrüstung). Daher kann der Spielzustand Manipulation in dem ohne Rendering-Lookup-Event Dispatch Thread ausgeführt werden. Das Spiel Zustand wird auch verwendet, um verschiedene aggregierte Informationen für den Benutzer angezeigt werden soll.

Allerdings, wie ich brauche Simulation einzuführen (zB Aufbau Fortschritt, demografische Veränderungen, Flottenbewegungen, Herstellungsverfahren, etc.), wird das Spiel Zustand in einem Timer und EDT Wechsel sicherlich die Wiedergabe verlangsamen.

Nehmen wir die Simulation / AI Betrieb in jedem 500ms durchgeführt wird, und ich verwende Swingworker für die Berechnung von etwa 250 ms lang. Wie kann ich sicherstellen, dass es keine Race-Bedingung ist, das Spiel Zustand bezüglich liest zwischen der Simulation und der möglichen Interaktion mit dem Benutzer?

Ich weiß, dass das Ergebnis der Simulation kann (die kleine Menge von Daten ist) effizient zu dem EDT bewegt über die SwingUtilities.invokeLater zurück () -Aufruf.

Das Spiel Zustandsmodell scheint zu komplex zu sein, nicht machbar zu sein für nur überall unveränderliche Wertklassen verwenden.

Gibt es einen relativ richtigen Ansatz, um dieses Lese Race-Bedingung zu beseitigen? Vielleicht eine Voll- / Teilspielzustand zu tun auf jeden Zeitgeberablaufstatusbit Klonen oder den Lebensraum des Spiels Zustand von EDT in einem anderen Thread ändern?

Update: (aus den Kommentaren Ich gab) Das Spiel arbeitet mit 13 KI-Spieler, 1 menschlichen Spielern und hat etwa 10.000 Spielobjekte (Planeten, Gebäude, Ausrüstung, Forschung, etc.). Ein Spielobjekt zum Beispiel hat folgende Attribute:

World (Planets, Players, Fleets, ...)
Planet (location, owner, population, type, 
    map, buildings, taxation, allocation, ...)
Building (location, enabled, energy, worker, health, ...)

In einem Szenario erstellt der Benutzer ein neues Gebäude auf diesen Planeten. Dies wird in EDT durchgeführt, wie die Karte und Gebäuden Sammlung geändert werden muss. Parallel dazu wird eine Simulation auf alle 500ms ausführen, um die Energiezuweisung zu den Gebäuden auf allen Spiel Planeten zu berechnen, die die Gebäude Sammlung für Statistikerfassung zu durchqueren muss. Wenn die Zuweisung berechnet wird, um es in den EDT vorgelegt wird und jedes Energiefeld des Gebäudes wird zugewiesen.

Nur menschliche Spieler Interaktionen haben diese Eigenschaft, weil die Ergebnisse der AI Berechnung ohnehin auf die Strukturen in EDT angewandt werden.

In der Regel 75% der Objektattribute sind statisch und nur für Rendering verwendet. Der Rest ist veränderbar entweder über eine Benutzerinteraktion oder Simulation / AI Entscheidung. Es ist auch gewährleistet, dass keine neue Simulation / AI Schritt gestartet wird, bis die vorherige zurück alle Änderungen geschrieben hat.

Meine Ziele sind:

  • Vermeiden Sie die Interaktion mit dem Benutzer zu verzögern, z.B. Benutzer legt das Gebäude auf den Planeten und erst nach 0,5 Sekunden wird die visuelle Rückmeldung
  • Vermeiden Sie den EDT mit Berechnung, Sperre warten blockiert, etc.
  • Vermeiden Sie Parallelitätsprobleme mit Sammlung Traversal und Modifikation, Attributänderungen

Optionen:

  • Feinkörnige Objektverriegelung
  • Immutable Sammlungen
  • Flüchtige Felder
  • Partial Snapshot

Alle diese haben Vorteile, Nachteile und bewirkt, dass das Modell und das Spiel.

Update 2: Ich spreche über dieses Spiel . Mein Klon ist hier . Die Screenshots können helfen, die Rendering- und Datenmodell Interaktionen vorstellen.

Update 3:

Ich werde versuchen, ein kleines Code-Beispiel geben für mein Problem zu klären, wie es aus den Kommentaren scheint es falsch verstanden wird:

List<GameObject> largeListOfGameObjects = ...
List<Building> preFilteredListOfBuildings = ...
// In EDT
public void onAddBuildingClicked() {
    Building b = new Building(100 /* kW */);
    largeListOfGameObjects.add(b);
    preFilteredListOfBuildings.add(b);
}
// In EDT
public void paint(Graphics g) {
    int y = 0;
    for (Building b : preFilteredListOfBuildings) {
        g.drawString(Integer.toString(b.powerAssigned), 0, y);
        y += 20;
    }
}
// In EDT
public void assignPowerTo(Building b, int amount) {
    b.powerAssigned = amount;
}
// In simulation thread
public void distributePower() {
    int sum = 0;
    for (Building b : preFilteredListOfBuildings) {
        sum += b.powerRequired;
    }
    final int alloc = sum / (preFilteredListOfBuildings.size() + 1);
    for (final Building b : preFilteredListOfBuildings) {
        SwingUtilities.invokeLater(=> assignPowerTo(b, alloc));            
    }
}

So ist die Überlappung zwischen dem onAddBuildingClicked () und distributePower (). Nun den Fall vorstellen, wo Sie 50 dieser Art von Überlappungen zwischen den verschiedenen Teilen des Spiels Modell haben.

War es hilfreich?

Lösung

Das klingt wie es von einem Client / Server-Ansatz profitieren könnte:

Der Spieler ist eine Client - Interaktivität und Rendering auf diesem Zweck passieren. So der Spieler eine Taste drückt, geht die Anfrage an den Server. Die Antwort vom Server zurückkommt, und der Zustand des Spielers aktualisiert wird. An jedem Punkt zwischen diesen Dingen geschehen, kann der Bildschirm neu lackiert werden, und es spiegelt den Zustand des Spiels, wie der Kunde weiß es zur Zeit.

Die KI ist ebenfalls ein Client - es ist das Äquivalent eines Bot

.

Die Simulation ist der Server. Es erhält Updates von seinen Kunden zu verschiedenen Zeiten und aktualisiert den Zustand der Welt, sendet dann diese Updates zu jedem gegebenenfalls aus. Hier ist, wo es mit Ihrer Situation anknüpft: Die Simulation / AI erfordert eine statische Welt, und viele Dinge auf einmal geschehen. Der Server kann einfach Änderungsanforderungen anstehen und sie anwenden, bevor die Updates zurück an den Client (s) zu senden. So bis zum Server des betreffenden wird die Spielwelt nicht wirklich in Echtzeit ändern, es ändert sich, wenn der Server verflixt gut entscheidet es ist.

Schließlich auf der Client-Seite können Sie die Verzögerung zwischen dem Drücken der Taste verhindern und um ein Ergebnis zu sehen, durch ein paar schnellen ungefähre Berechnungen zu tun und ein Ergebnis angezeigt wird (so die unmittelbare Notwendigkeit erfüllt ist) und dann das korrektere Ergebnis anzeigt, wenn die Server wird um mit Ihnen zu sprechen.

Beachten Sie, dass dies tatsächlich nicht in einem TCP implementiert werden müssen / IP-over-the-Internet Art und Weise, nur, dass es davon in diesen Kategorien zu denken hilft.

Alternativ können Sie auch die Verantwortung Platz für die Daten kohärent während der Simulation in einer Datenbank zu halten, da sie bereits gebaut sind mit Sperren und Kohärenz im Auge behalten. So etwas wie SQLite könnte als Teil einer nicht-vernetzten Lösung arbeiten.

Andere Tipps

Nicht sicher, ob ich völlig das Verhalten verstehen Sie suchen, aber es klingt wie Sie so etwas wie eine Zustandsänderung Thread / Warteschlange, so dass alle Zustandsänderungen behandelt werden von einem einzigen Thread benötigen.

Erstellen Sie eine api, vielleicht wie SwingUtilities.invokeLater () und / oder SwingUtilities.invokeAndWait () für Ihre Zustandsänderung Warteschlange Ihrer Zustand Änderungsanforderungen zu behandeln.

Wie das in der gui widerspiegeln Ich denke, hängt von dem Verhalten, das Sie suchen. das heißt kann nicht Geld abheben, weil aktuelle Zustand $ 0 oder Pop zurück an den Benutzer ist, dass das Konto leer war, als die zurücktreten Anfrage bearbeitet wurde. (Wahrscheinlich nicht mit dieser Terminologie ;-))

Der einfachste Ansatz ist die Simulation schnell genug, um in der EDT laufen. Bevorzugen Sie Programme, die arbeiten!

Für das Zwei-Thread-Modell, was ich vorschlagen, ist das Domain-Modell mit einem Rendering-Modell synchronisieren. Das Render-Modell sollte die Daten behalten, was kam aus dem Domänenmodell.

Für ein Update: In der Simulation Thread sperren das Modell machen. Traverse der Modellaktualisierung machen, wo die Dinge anders sind, was das Update machen Modell erwartet wird. Beim Verfahren beendet, schließen Sie das Modell machen und ein Neuzeichnen planen. Beachten Sie, dass Sie in diesem Ansatz nicht über eine Unmenge von Hörern benötigen.

Das Modell machen können unterschiedliche Tiefen aufweisen. Bei einer Extrem kann es ein Bild sein, und die Aktualisierungsoperation ist nur eine einzige Referenz mit dem neuen Bildobjekt zu ersetzen (dies wird nicht handhaben, zum Beispiel, Ändern der Größe oder andere oberflächliche Interaktion sehr gut). Sie werden vielleicht nicht die Mühe zu prüfen, ob ein Element ändern hat und nur eveything aktualisieren.

Wenn Sie das Spiel Zustand ändert sich schnell ist (wenn Sie wissen, was es zu ändern) können Sie den Spielzustand wie andere Swing-Modelle und einzige Änderung behandeln oder den Zustand, in dem EDT anzuzeigen. Wenn die Änderung der Spielzustand nicht schnell ist, dann können Sie entweder Zustandsänderung synchronisieren und tun es in swing Arbeiter / Timer (aber nicht die EDT) oder Sie können es in separaten Thread tun, die Sie ähnlich wie bei der EDT behandeln (bei dem Sie zeigen Blick auf eine BlockingQueue mit Änderungsanforderungen zu handhaben). Die letzte ist nützlicher, wenn die UI nie Informationen aus dem Spiel Zustand abrufen muss, sondern hat die Rendering-Änderungen über Hörer oder Beobachter geschickt.

Ist es möglich, das Spiel Zustand inkrementell zu aktualisieren und noch ein Modell hat, die konsistent ist? Zum Beispiel für eine Teilmenge von Planeten / Spielern neu berechnen / Flotte Objekte zwischen rendert / Benutzer Updates.

Wenn ja, könnten Sie inkrementelle Aktualisierungen im EDT ausgeführt, dass nur einen kleinen Teil des Staates berechnen, bevor der EDT so dass Benutzereingaben verarbeiten und übertragen.

Nach jeder Zwischenaktualisierung im EDT würden müssen Sie daran denken, wie viel des Modells aktualisiert werden bleibt und eine neue Swingworker am EDT planen diese Verarbeitung nach ausstehenden Benutzereingaben fortzusetzen und Rendering durchgeführt wurde.

Dies sollte Ihnen ermöglichen, das Kopieren oder Sperren des Spielmodell zu vermeiden, während immer noch die Benutzer-Interaktionen als Reaktion zu halten.

Ich glaube, Sie sollten jedoch keine Welt keine Daten speichern oder Änderungen an irgendwelchen Objekten selbst, sollte es nur einen Verweis auf ein Objekt zu halten, verwendet werden, und wenn das Objekt geändert werden muss, müssen die Spieler die Änderung Änderung vornehmen es direkt. In diesem Fall Sie das einzige, was tun müssen, ist jedes Objekt in der Spielwelt synchronisieren, so dass, wenn ein Spieler eine Änderung macht, kann kein anderer Spieler dies tun. Hier ist ein Beispiel dafür, was ich denke:

Spieler A muss über einen Planeten wissen, so dass es die Welt für diesen Planeten fragt (wie bei der Implementierung abhängig ist). Welt gibt einen Verweis auf den Planeten Objekt Spieler gefragt. Spieler A entscheidet, eine Änderung vorzunehmen, so tut sie dies. Lassen Sie uns sagen, dass es ein Gebäude erstellt. Das Verfahren, ein Gebäude zu dem Planeten hinzuzufügen synchronisiert, so dass nur ein Spieler in einer Zeit, so tun. Das Gebäude wird der Überblick über seine eigene Bauzeit (falls vorhanden), so dass der Zusatz Bauverfahren Planeten würde fast sofort frei. Auf diese Weise können mehrere Spieler gleichzeitig die Informationen auf dem gleichen Planeten stellen, ohne sich gegenseitig zu beeinflussen und die Spieler können Gebäude hinzufügen fast gleichzeitig ohne viel Aussehen Verzögerung. Wenn zwei Spieler einen Platz suchen, um das Gebäude zu setzen (wenn das Teil des Spiels ist), dann wird die Eignung eines Standortes Überprüfung wird eine Abfrage nicht eine Änderung sein.

Es tut mir leid, wenn dies nicht, dass du Frage nicht beantworten, ich bin nicht sicher, ob ich es richtig verstanden.

Wie wäre es Leitungen und Filter-Architektur zu implementieren. Pipes verbindet Filter zusammen und Warteschlange Anforderungen, wenn die Filter nicht schnell genug ist. Die Verarbeitung geschieht im Inneren Filter. Der erste Filter ist der AI-Motor, während die Wiedergabemaschine durch eine Reihe von nachfolgenden Filter implementiert ist.

Bei jedem Timer-Tick wird der neue dynamischer Weltstaat berechnet auf der Grundlage aller Eingänge (Zeit ist auch ein Eingang) und ein Kopie in das erste Rohr eingesetzt.

Im einfachsten Fall Ihre Rendering-Engine wird als einzelner Filter implementiert. Es dauert nur den Zustands Schnappschüsse aus dem Eingangsrohr und macht es zusammen mit dem statischen Zustand. In einem Live-Spiel, die Rendering-Engine möchten Zustände überspringen, wenn es mehr als eine in der Leitung sind, während, wenn Sie eine Benchmark oder Ausgabe eines Video tun werden Sie jeden machen möchten.

Je mehr Filter können Sie Ihre Rendering-Engine in, desto besser ist die Parallelität wird zersetzen. Vielleicht ist es sogar möglich, die KI-Engine zu zersetzen, z.B. Sie können dynamische Zustand in sich schnell ändernden und sich langsam ändernden Zustand trennen.

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

Ein Problem bei dieser Architektur besteht darin, dass die Garbage Collection häufig jedes Mal alle Fäden Einfrieren laufen wird, möglich, einen Vorteil gewonnen von Multi-Threading zu töten.

Es sieht aus wie Sie benötigen auf Priorityqueue die Updates zum Modell zu setzen, in denen frmo den Benutzer aktualisiert haben Vorrang vor dem Updates von der Simulation und anderen Eingängen. Was ich höre Sie sagen, dass der Benutzer immer ein unmittelbares Feedback über seine Handlungen wheras die anderen Eingänge benötigt (Simulation, sonst) könnten Arbeitnehmer, die länger als ein Simulationsschritt in Anspruch nehmen. Dann synchronisieren auf der Priorityqueue.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top