Spielzustände bei mehreren interagierenden Spielobjekten

FunMaker

Mitglied
Leider ist mir kein wirklich aussagekräftiger Titel eingefallen. Da es hier vielmehr um Meinung als um eine tatsächliche Lösung geht. Aber ich hoffe trotzdem auf Anregungen von euch!

Ich stelle mal kurz die Situation vor:

Gehen wir einmal von einem hypotethischen Strategiespiel aus (Aber auch in anderen Genres kann das Problem auftreten). In einem Gefecht sind 2 Gegner beteiligt mit jeweils einer gewissen Anzahl an Einheiten.
Wir gehen nun der Einfachheit halber einmal von einer Single-Threaded Verarbeitung der Spiellogik aus - im Fall von Multithreading verschärft sich das Problem nur noch mehr:

Java:
public void tickGame()
{
//in units sind ALLE Einheiten des Spiels in beliebiger Reihenfolge enthalten
for (Unit unit : units)
{
//Unit.tick() führt alle Aktionen aus, die für diese Einheit relevant sind (Angreifen falls Waffe nachgeladen, Sterben falls HPs <= 0, Bewegung)
   unit.tick();
}

Jetzt die große Frage: Es gibt 2 Möglichkeiten die Spielwelt darzustellen:
  1. Man setzt für alle Einheiten einen fest definierten Zustand der für alle Einheiten solange gilt, bis alle Einheiten ihre Aktionen durchgeführt haben.
  2. Eine Einheit hat bereits ihren Zustand gewechselt wenn ihre tick() Methode aufgerufen wurde.

Was meine ich hiermit nun genau?
  1. Wenn eine Einheit sich bewegt, setzt sie in ihrer tick() Methode nur ihre "zukünftige Position" verändert aber nicht direkt ihre Position. Nachdem die Spielwelt ihre tick() Methode durchlaufen hat werden alle "zukünftigen Positionen" zu "aktuellen Positionen".
  2. Wenn eine Einheit sich bewegt, setzt sie in ihrer tick() Methode direkt ihre Position neu. Es existiert also immer nur genau eine Position für diese Einheit, die direkt verändert wird wenn für die Einheit die tick() Methode aufgerufen wird.
Die oben beschriebene Änderung an der Position lässt sich genauso auf Lebenspunkte oder andere Eigenschaften einer Einheit anwenden.

Welche Auswirkungen haben die unterschiedlichen Ansätze?
  1. Die Reihenfolge der Einheiten in units ist in diesem Fall egal. Da z.B. eine Einheit welche überprüft, ob die anvisierte gegnerische Einheit noch in Reichweite der Waffen ist diese Prüfung anhand der "aktuellen Position der gegnerischen Einheit" durchführt - egal ob die gegnerische Einheit bereits ihren tick() durchgeführt hat oder nicht.
  2. Die Reihenfolge der Einheiten in units ist in diesem Fall relevant. Gehen wir einmal von 2 Einheiten A und B aus. A ist kurz davor B zu vernichten und B versucht zu entkommen und hat auch eine höhere Bewegungsgeschwindigkeit als A. A ist in diesem Spieltick Feuerbereit.
    • Fall units Reihenfolge (A,B): A.tick() prüft ob B in Reichweite und feuert seine Waffen ab und vernichtet B. B.tick() stellt den Tot von B fest und entfernt die Einheit aus dem Spiel
    • Fall units Reihenfolge (B,A): B.tick() stellt fest, dass B noch lebt und bewegt sich von A weg. A.tick() stellt fest, dass B außerhalb der Waffenreichweite ist und B entkommt A.
Noch ein Beispiel bei dem mehrere Einheiten gleichzeitig auf eine fast tote gegnerische Einheit feuern. Es sind aber noch andere mögliche gegnerische Ziele in Reichweite:
  1. Die feuernden Einheiten stellen fest, dass die gegnerische Einheit noch lebt da die Lebenspunkte der Einheit erst am Ende des Spielticks von "zukünftig" auf "aktuell" aktualisiert werden. Die gegnerische Einheit ist nach diesem Gametick mit Sicherheit tot da komplett zerbombt.
  2. Die ersten 2 feuernden Einheiten stellen fest, dass die gegnerische Einheit noch lebt. Die Lebenspunkt der getroffenen Einheit werden sofort aktualisiert. Die nachfolgenden feuernden Einheiten stellen fest, dass die Einheit bereits keine Lebenspunkte mehr hat und können bereits eine andere Einheit angreifen ohne das sie "quasi sinnlos" auf ein bereits tote Einheit feuern.
Man sieht, beide Seiten haben Vor- und Nachteile. Ich bin aber persönlicher der Meinung, dass 1. generell die fairere Variante und die besser zu parallelisierende Variante ist, da alle Objekte sich in einem definierten Zustand befinden. Auch für eine Beurteilung einer KI der Spielsituation ist dies bei weitem besser. Die 1. Variante hat aber auch einige Nachteile:
  1. Man kann die meisten Einheitenvariablen nicht über primitive Datentypen darstellen. Man benötigt für so einfache Sachen wie Hitpoints, Position und andere Variablen Datentypen die zumindest eine flip() Methode anbieten um zukünftige Werte zu aktuellen Werten zu machen.
  2. Man muss alle Einheiten 2 mal durchlaufen. Einmal mit der tick() Methode und danach, wenn alle Einheiten durchlaufen wurden noch einmal mit einer flip() Methode.
  3. Mal abgesehen vom höheren Programmieraufwand da man nicht mal eben einfach Arithmetik mit den Variablen machen kann geht das ganze vermutlich auf die Performance.
Das ganze betrifft natürlich nicht nur Einheiten. Auch Projektile, Ressourcen, Baufortschritte, Forschungsfortschritte können hiervon (je nach Spiel) betroffen sein.

Ich hoffe ich habe die beiden Varianten ausreichend und verständlich genug dargestellt. Falls nicht - einfach noch einmal nachfragen.

Nun, was meint ihr? Lohnt sich der Aufwand und die Performanceinbußen (die sich durch höhere Parallisierung unter Umständen wieder ausgleichen lassen) um eine fairere Verarbeitung aller Spielobjekte zu erreichen? Oder sagt ihr: Ist doch eigentlich schon fair. Bei einer einfachen LinkedList sind die Einheiten halt zu erst dran die auch zu erst produziert und in die Liste eingefügt wurden.

Sorry für Wall of Text aber das Problem musste einfach mit Text umschrieben werden.
 

Bananabert

Bekanntes Mitglied
Es kommt ganz darauf an wie dein Spiel am Ende funktionieren soll


"Welche Auswirkungen haben die unterschiedlichen Ansätze?"
Die beiden Punkte kommen mehr oder weniger auf das Gleiche hinaus. first come first serve
Aus Punkt 1 :
... egal ob die gegnerische Einheit bereits ihren tick() durchgeführt hat oder nicht.



Noch ein Beispiel bei dem mehrere Einheiten gleichzeitig auf eine fast tote gegnerische Einheit feuern. ...
Die nachfolgenden feuernden Einheiten stellen fest, dass die Einheit bereits keine Lebenspunkte mehr hat

Hier hast du dir selbst widersprochen. Entweder sie feuern gleichzeitig, oder sie Feuern nacheinander.
Worauf du hinaus möchtest ist aber klar.
Hier könntest du Variante 1 und 2 verbinden. Die Truppen aus Einheiten errechnen vorher wie viele Feuerkraft/Einheiten notwendig sind eine Einheit zu töten.



Man kann die meisten Einheitenvariablen nicht über primitive Datentypen darstellen. ... wie Hitpoints, Position und andere Variablen Datentypen
Bei der Position kann ich dir zustimmen. Aber warum gerade für die HitPoints?



Bei Variante eins ein Problem anhand eines Beispiels. Fair ist es wohl eher weniger.

Bei Variante zwei als Beispiel zwei Fahrer, A und B.
A ist als erster in der Liste und Fahrer B als zweiter. A fährt B in die Seite
B braucht drei Ticks um in der Mitte der Kreuzung zu sein. A fährt B bei Tick drei in die Seite.
Würdest du nach der Liste gehen, wäre Fahrer A bei Tick drei zuerst auf der Kreuzung und B fährt A in die Seite.



Am Ende kommt es, wie schon gesagt, ganz darauf an wie du den Ablauf deines Spiels haben möchtest.
Beiß dich nicht umbedingt an einer Variante fest. Du kannst das für dich beste aus beiden Varianten nehmen und eine eigene entwerfen.
 

Oben