Warning: include_once(/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php): failed to open stream: No such file or directory in /var/www/html/fields/dbp13/local/config.php on line 4

Warning: include_once(): Failed opening '/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php' for inclusion (include_path='.:/opt/php/lib/php') in /var/www/html/fields/dbp13/local/config.php on line 4

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fields/dbp13/local/config.php:4) in /var/www/html/pmwiki-2.2.86/pmwiki.php on line 1250
Datenbankpraktikum SS 2013 - MMOG - Planeten Und Gebaeude Spielwelt

Malte Gegner

Die Spielwelt

Die Spielweltregeln

Die Welt von Cypis ist durch eine begrenzte Zahl von Galaxien gegeben, die sich jeweils durch ihre X-Koordinate eindeutig kennzeichnen lassen. In jeder Galaxie gibt es zwischen vier und acht Sonnensystemen mit einer eindeutigen Y-Koordinate. In jedem Sonnensystem wiederum befinden sich zwischen vier und acht Planeten mit einer Z-Koordinate. Damit ist jeder Planet eindeutig durch ein Tripel (X,Y,Z) festgelegt. Die Anzahl von vier bis acht Elementen pro Klasse ist nicht zufällig. Unter der Annahme, dass dieses Spiel mehrere Personen spielen würden, wurde festgelegt, dass jeder Spieler im Durchschnitt ungefähr eine handvoll Planeten verwalten solle. Durch die Wahl von vier bis acht Planeten pro Sonnensystem unterstützen wir damit diesen Teil des Spiels. Zur Übersichtlichkeit wurde diese Verteilung auch für die Sonnensystemanzahl pro Galaxie gewählt.

In der Frontend-Ansicht ist es den Usern erlaubt, eine Teilansicht auf die Galaxien auf einem zweidimensionalen Feld zu sehen. Die Größe der Teilansicht, sowie die gesamte Maximalgröße des Feldes kann in den GameSettings vor Spielbeginn festgelegt werden. Standardmäßig haben wir die maximale Größe auf 12 und die Ansicht auf 7 gestellt. Das bedeutet, dass man ein 7x7-Spielfeld sieht, das Teil einer 12x12-Welt ist. Diese Welt bietet potentiell Platz für ca. 1000 Spieler.

Man kann die erforderliche Spielfeldgröße anhand der zu erwartenden Spielerzahl bestimmen:

  • maxSize = (player_count * 1,3 / 7,5) ^ (1/2)
  • Es ist zu erwarten, dass man nicht eine völlig überfüllte Welt generieren möchte und es kann passieren, dass User freie Startplaneten in ihren Besitz bringen und damit die Zahl der freien Startpositionen verringern können. Daher sollte die Welt ca. 30% (Faktor 1,3) größer sein, als nötig.
  • Die 7,5 stellt die durchschnittliche Zahl der Startplaneten pro Galaxie dar. Diese ist der Erwartungswert aus der zufälligen Generierung.

Für erwartete 100000 Spieler bedeutet das, dass eine Weltgröße von 132 ausreichen sollte. Damit erzeugt man maximal 17424 Galaxien mit insgesamt ca. 627000 Planeten. Damit erfüllt man die Erwartung von 4-5 Planeten pro User und hat noch etwas freien Platz übrig.

Koordinaten und Ansicht

Die Koordinaten des Spielfeldes belaufen sich auf drei Integer X,Y und Z. Dabei wird die Position der Galaxien X aus zwei Ansichtskoordinaten x und y mithilfe der Cantorschen Paarungsfunktion errechnet. Dadurch kann man die Galaxien auf einem zweidimensionalen Feld direkt anordnen, obwohl sie nur durch einen Integer beschrieben werden.

Innerhalb der Galaxie stehen diese Methoden dazu zur Verfügung:

def self.calcX(x, y)
  if x.integer? && y.integer? then
    x = x % (GameSettings.get("WORLD_LENGTH").to_i)
    y = y % (GameSettings.get("WORLD_LENGTH").to_i)
    (x + y) * (x + y + 1) / 2 + y + 1
  else
    -1
  end
end
 
def getCoords()
  if self.x > 0 then
    i = self.x - 1
    j = (Math.sqrt(0.25 + 2 * i) - 0.5).floor
    [j - (i - j*(j+1)/2), i - j*(j+1)/2]
  else
    [-1, -1]
  end
end

Hier sind die X-Koordinaten an der Position (0,0) und Umgebung dargestellt. Die Galaxie links oberhalb der Mitte ist Position (11,11):

Die Koordinaten Y der Sonnensysteme und Z der Planeten nehmen Werte zwischen 1 und 8 an. Sie beschreiben die eindeutige Entfernung zum übergeordneten Element.

Die Koordinaten erlauben eine Distanzberechnung zwischen Galaxien, Sonnensystemen und Planeten. Wir berechnen Entfernungen durch den euklidischen Abstand der Ansichtskoordinaten x,y in Verbindung mit den Koordinaten Y und Z. Damit die Unterschiede zwischen Planeten in demselben Sonnensystem und in verschiedenen Sonnensystemen oder Galaxien deutlicher werden, kann man in den GameSettings einen Streckfaktor der Weltebenen festlegen. Dadurch wird der Abstand zwischen zwei benachbarten Galaxien sehr viel größer werden als der Abstand zweier benachbarter Planeten. Die Entfernungen legen indirekt fest, wie lange ein Raumschiff braucht, um von einem Ort zu einem anderen zu reisen.

Zusätzlich ist durch die maximale Spielfeldgröße in den GameSettings gegeben, welche Werte (x,y) in der Galaxienansicht angenommen werden können. Im Standardfall mit Maximalgröße 12 haben die Galaxien Positionen von (0,0) bis (11,11). Andere Positionen, wie negative oder größere Werte haben wir dadurch umgangen, dass die Welt in sich geschlossen ist. Das bedeutet, dass die Position (12,12) im Standardfall wieder die Position (0,0) ist. Die Welt krümmt sich also um ihre x- und y-Achse. Durch die Maximalgröße wird implizit die maximale Entfernung in der Welt festgelegt. In der Distanzberechnung wird beachtet, dass ein Weg über den "Rand" der Welt kürzer als der triviale Weg sein kann.

In der Useransicht benutzen wir einen Performanceboost. Anstatt die Galaxien reihenfolgenrichtig zu laden und anzuzeigen, wird der Controller angewiesen, ein bestimmte Position (x,y) zu laden. Mithilfe der WORLD_VIEW_LENGTH und WORLD_LENGTH kann er alle zugehörigen X-Koordinaten errechnen und alle anzuzeigenden Galaxien übergeben. In der View werden diese Galaxien dann wiederum auch ungeordnet ausgeworfen. Die Ordnung (das zweidimensionale Feld) entsteht nur für den menschlichen User. Die Galaxien bekommen Stylesheetelemente zugewiesen, die sie an eine bestimmte relative Position im Browser bewegen. Das rudimentäre Laden der Weltdaten ohne Sichtbarkeiten ist dadurch so schnell, dass es nicht ins Gewicht fällt. Das gilt natürlich nur, solange die WORLD_VIEW_LENGTH, die im Standard 7 beträgt, nicht massiv erhöht wird. Es ist aber unsinnig, die Zahl auf unheimlich große Werte zu stellen, da im Browser dann alles unübersichtlich würde.

Dynamische Welterzeugung durch den Worldgenerator

Da die Spielwelt theoretisch durch administrative Anweisungen in den GameSettings sehr groß gewählt werden kann, möchten wir nicht die gesamte Welt schon vor Spielbeginn generieren. Stattdessen wird nur die allererste Galaxie an der Position (0,0) in der Galaxieansicht erzeugt. Dabei entsteht mindestens ein Startplanet für den ersten User. Mit jeder folgenden spielerischen Aktion meldet ein User mithilfe der Methode seen_by(User) und mention() an welchem Planeten bzw. Sonnensystem bzw. Galaxie er sich gerade befindet. Dabei wird der Worldgenerator angewiesen, die Umgebung der genannten zugehörigen Galaxie zu generieren.

Dazu besitzt die GalaxiesHelper-Klasse eine Hilfsmethode generateNear(x,y), wobei x und y die Anzeigeposition einer einzelnen Galaxie meinen.

def self.generateNear(x, y)
  3.times do |m|
    3.times do |n|
      generateAt(x - 1 + m, y - 1 + n)
    end
  end
end

Sobald mindestens eine Galaxie erzeugt wird, müssen auch alle zugehörigen Sonnensysteme und Planeten in einer einzigen Transaktion miterzeugt werden. Dadurch wird gewährleistet, dass die Weltdaten in der Datenbank konsistent mit den Spielregeln einhergehen. Konnte eine Galaxie nicht erzeugt werden, ist das kein Problem, da automatisch beim nächsten Aufruf an einer benachbarten Galaxie diese Galaxie erneut erzeugt werden kann. Insgesamt wird durch dieses Vorgehen gewährleistet, dass immer genug der Spielwelt als "neues Gebiet" zur Verfügung steht, aber nicht sehr viel auf einmal in die Datenbank eingetragen werden muss. Die einzelnen Generierungen der umgebenen Galaxien dauert zwischen 10 ms und 900 ms, je nachdem, wie viel nachgeneriert werden muss.

Bei jedem Erstellen eines neuen Sonnensystems wird darauf geachtet, dass genau ein Planet als Startplanet für neue User erzeugt wird und alle anderen als spezialisierte Planeten mit besonderen Eigenschaften. Da wir festgelegt hatten, dass ein User ca. 4-5 Planeten verwalten würde, haben wir uns dazu entschlossen, bei Sonnensystemen, die 7 oder 8 Planeten erstellen, zusätzlich einen zweiten Planeten als Startplaneten zu deklarieren. Das führt dazu, dass User in großen Sonnensystemen automatisch zu mehr Interaktionen mit anderen Usern gezwungen sind und nicht übervorteilt sind. Im Gegenzug ist es zur Spielregel geworden, dass die Übernahme eines Startplaneten nicht mehr möglich ist, sobald ein User diesen Planeten besitzt. Dadurch werden die Startplaneten zu strategischen Punkten im Spiel und gleichen den Nachteil aus, dass sie keine Besonderheit aufweisen.

Startplaneten an neue User zuweisen

Damit ein neuer User beim Spiel teilnehmen kann, muss er einen Startplaneten zugewiesen bekommen. Dazu haben wir eine Hilfsmethode erstellt, die für einen übergebenen User einen Startplaneten sucht. Auf die Möglichkeit, einfach irgendeinen Startplaneten zufällig zu wählen, wollten wir verzichten, da dies dem Spielprinzip nicht förderlich ist. Eine zufällige Zuweisung würde dazu führen, dass User am Anfang sehr weit voneinander entfernt sein können, und zwar so weit, dass der Multiplayeraspekt für sehr lange Zeit nicht zum Tragen kommen würde. Stattdessen sollten die User zwar relativ zufällig, aber doch ziemlich nah beieinander starten. Aufgrund der dynamischen Generierung der Welt ist dies sehr einfach:

Auszug aus dem Modul PlanetsHelper

@@marker = []
@@marker_pos = 0
 
def self.search_startplanet()
  if @@marker.nil? || @@marker.length < 8 then
    @@marker = Planet.where(special: 0, user_id: nil).limit(8)
  end
  return false if @@marker.empty?
  @@marker_pos = (@@marker_pos+1) % 8
  planet = @@marker[@@marker_pos]
  @@marker[@@marker_pos] = nil
  @@marker.compact!
  return planet
end
 
def self.claim_startplanet_for(user)
  return false if !user.is_a?User || user.nil? || user.planets.count > 0
  p = search_startplanet
  if !p.nil? && p.is_a?(Planet) && p.user.nil?
    p.claim(user)
    p.set_home_planet(user)
    return true
  else
    return false
  end
end

Der erste User startet immer auf einem Startplaneten in der allerersten Galaxie. Alle weiteren User bekommen einen der ersten acht in der Datenbank zu findenden Startplaneten zugewiesen. Damit ist es sehr wahrscheinlich, dass ein neuer User in der gleichen Galaxie startet, wie mindestens ein anderer, manchmal auch in demselben Sonnensystem. Auf jeden Fall werden die Startpositionen nach Interesse der bereits spielenden User verteilt, da die zuerst erkundeten Galaxien zuerst in die Datenbank geschrieben wurden. Somit starten neue Spieler immer an einer Position, die mindestens einen anderen User als Nachbarn besitzt oder durch andere erkundet wurde. Somit werden die nicht bekannten/erkundeten Galaxien auch nicht als Startpositionen ausgewählt. Wenn man davon ausgeht, dass im Spiel viele User im Durchschnitt in alle Richtungen gleich erkunden, würden die neuen User in etwa nahe zur Position (0,0) starten.

Sichtbarkeiten und Nebel des Krieges

Es ist eine der Spielregeln, die besagt, dass man nicht die gesamte Welt kennen und sehen darf, sondern nur das, was auch die eigenen und verbündeten Truppen sehen können. Dies ist allgemein als "Nebel des Krieges" bekannt. Wir haben diesen Aspekt aufgefasst und derart umgesetzt, dass User ihre eigenen Planeten kennen, die der Verbündeten und alle Planeten sichtbar, aber nicht betretbar sind, die in einem Sonnensystem liegen, in dem mindestens ein bekannter Planet liegt. Eine Galaxie ist immer dann sichtbar und auch betretbar, wenn in ihr mindestens ein sichtbares Sonnensystem liegt.

Es hat sich herausgestellt, dass die Abfragen, ob etwas sichtbar ist oder nicht, zu lange dauern, als dass man sie einem Element stellen könnte. Die Ladezeit der Frontendansicht belief sich mit dem Verfahren auf mehrere Sekunden für einfachste Ansichten. Stattdessen haben wir uns entschieden, alle Sichtbarkeiten beim ersten Fragen direkt gebündelt aus den Daten in der Datenbank zu erhalten und dem User als Sichtbarkeitenarrays als Cache anzuhängen, da der User immer derselbe für eine Ansicht sein wird. Damit konnte die Ladezeit von Sekunden auf Zentelsekunden optimiert werden.

Auszug aus der Sichtbarkeitsberechnung mit Caching für Sonnensysteme, die sich innerhalb des Usermodels befindet:

def visible_sunsystems
  if !(GameSettings.get("caching?")) || @cache_visible_sunsystems.nil? || @cache_visible_sunsystems.empty? then
    @cache_visible_sunsystems = []
    a = self.alliance
    ps = self.planets
    if a.nil? then # Ist der User nicht Teil einer Alliance?
      @cache_visible_sunsystems = self.sunsystems
      ps.each do |p|
        @cache_visible_sunsystems << p.sunsystem
      end
    else # Der User ist Teil einer Alliance!
      ps.each do |p|
        @cache_visible_sunsystems << p.sunsystem
      end
      a_users = a.users # Umkopieren des Arrays erspart in der Schleife mehrfache Abfragen
      a_users.each do |a_user|
      @cache_visible_sunsystems.concat a_user.sunsystems
      end
    end
  end
  return @cache_visible_sunsystems
end

Wie festzustellen ist, kann es passieren, dass im Cache Sonnensysteme doppelt bzw. mehrfach eingetragen werden. Das haben wir zur Kenntnis genommen, aber aus Performancegründen erhalten, denn für die späteren Anfragen ist es unerheblich, wie oft ein Element im Cache-Array enthalten ist. Daher macht es fast keinen Unterschied vorher zu testen, ob ein Element schon enthalten ist.
Beispiel für Planetensichtbarkeiten, nicht betretbare Planeten sind halbtransparent:

Viewspezifische Features

Uns stehen sechs verschiedene Galaxienbilder zur Verfügung. Um mehr Abwechslung in der Ansicht zu erhalten, nutzen wir diese Bilder mehrfach und verdreht. Welches Bild angezeigt werden soll, hängt von der X-Koordinate der Galaxie ab. Die Darstellung errechnet sich wie folgt:

  • nach links gedreht, wenn X%24 > 17
  • auf dem Kopf, wenn X%24 > 11
  • nach rechts gedreht, wenn X%24 > 5
  • normal, sonst

Die Nummer des Bildes bestimmt sich aus X%24/6+1.

Darstellung der Sonnensysteme in einer Galaxie:

Die Position der Sonnensysteme in einer Galaxie und die Position der Planeten in einem Sonnensystem ist festgelegt. Allein der Zufall, wie viele und wo Planeten/Sonnensysteme in einem/einer Sonnensystem/Galaxie generiert wurden, bestimmt, wo sie angezeigt werden. Planeten werden farblich und größentechnisch nach ihrer Spezialisierung in einem Sonnensystem angezeigt. So sind beispielsweise die Startplaneten bläulich und die Kristallplaneten kleiner und mit einem Ring versehen direkt als solche zu erkennen, ohne dass man sie zuerst betreten muss.

Der Aufwand der verschiedenen Darstellungen ist minimal, da alle Bilder per Stylesheets über diverse Klassen eingebunden werden, sodass die Bilder erst beim Enduser nach Fertigstellen der Seite eingebettet werden.
In jeder View befindet sich ein div-Container, der Informationen zur aktuellen Ansicht enthält. Diese sind standardmäßig ausgeblendet und werden durch Hovern mit der Maus auf entsprechenden Elementen angezeigt. So werden Informationen zu einer speziellen Galaxie sichtbar, wenn man über eine mit der Maus fährt, oder man erhält Informationen zu einem Planeten, wenn man mit der Maus auf ihn zeigt.


Page last modified on August 24, 2013, at 12:59 PM