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:
Jetzt die große Frage: Es gibt 2 Möglichkeiten die Spielwelt darzustellen:
Was meine ich hiermit nun genau?
Welche Auswirkungen haben die unterschiedlichen Ansätze?
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.
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:
- 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.
- Eine Einheit hat bereits ihren Zustand gewechselt wenn ihre tick() Methode aufgerufen wurde.
Was meine ich hiermit nun genau?
- 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".
- 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.
Welche Auswirkungen haben die unterschiedlichen Ansätze?
- 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.
- 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.
- 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.
- 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 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.
- 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.
- 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.
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.