The Last Avanger
Letztes Wochenende hat bei InnoGames ein sogenannter Game Jam stattgefunden. Diesmal hatte ich glücklicherweise auch Zeit daran teil zu nehmen. Für alle die zu faul sind den verlinkten Wikipedia Artikel zu lesen: Bei einem Game Jam setzt man sich 48 Stunden lang hin und versucht in dieser Zeit ein Spiel zu entwickeln. Dabei gibt es kaum Regeln, lediglich ein Thema an dem man sich orientieren sollte und genannte Zeitbeschränkung. Das Thema des aktuellen Game Jam war Space. Zusammen mit einem guten Freund der momentan Informatik studiert und zwei Kollegen, welche von Programmierung keinerlei Ahnung hatten, habe ich mich an diesem Event beteiligt. Unser Ergebnis ist The Last Avenger. Da ich finde, dass sich das Ergebnis wirklich sehen lassen kann möchte ich in diesem Blog-Eintrag ein wenig über die Entstehung schreiben.
Bevor ich allerdings zur technischen Umsetzung komme, noch ein paar allgemeine Dinge zum Game Jam. Wer glaubt nur Programmierer können daran teilnehmen, der irrt gewaltig. Ein gutes Spiel braucht nicht nur Programmcode sondern auch Grafiken und Sound. Eventuell sind ein wenig Story, Gamedesign und Balancing auch nicht verkehrt. Die Programmierung ist also nur ein Teilbereich und wenn man sich in Teams organisiert, kann jeder, der eine der genannten Fähigkeiten auch nur ansatzweise besitzt, an einem Game Jam teilnehmen. So haben auch meine beiden Nicht-Programmierer-Kollegen einen wertvollen Beitrag geleistet. Natürlich geht es nicht ohne Programmierer, aber im Gegenzug ist es auch schwer sich mit mehr als einem Programmierer im Team in einer so kurzen Zeit zu koordinieren.
Nun kommen wir zur technischen Umsetzung. Sobald wir das Grundkonzept für unser Spiel ausgetüftelt hatten, stellte sich die Frage: Wie wollen wir das technisch umsetzen? Es schien mir unangebracht, sich in der kurzen uns zur Verfügung stehenden Zeit auch noch in neue Programmiersprachen und Technologien einzuarbeiten. Also wollte ich mich auf das konzentrieren, was ich am besten kann: Web-Technologien. Da PHP für das Spiel ungeeignet erschien, da das Spiel clientseitig läuft, blieb nur JavaScript. Hierbei stellte sich dann noch eine letzte Frage: Arbeite ich mit der Manipulation von DOM-Elementen oder verwende ich ein Canvas? Im Endeffekt habe ich mich für die erste Variante entschieden. Hierbei versprach ich mir, schneller und einfacher Ergebnisse zu erzielen, da der Browser die meiste Arbeit übernimmt. So muss ich mich nicht mit dem Zeichnen herum ärgern, sondern kann mich auf andere Dinge konzentrieren. Im Nachhinein habe ich noch ein wenig mit Canvas experimentiert, was meine Entscheidung vom Game Jam nur bestärkt hat. Ich hatte bei den ersten Versuchen starke Probleme mit der Performance von Canvas, die ich allerdings mit einigen Tricks bereits verbessern konnte. Aber eben diese Tricks hätten Zeit gekostet, weshalb die DOM-Version eine gute Wahl war. Sobald die Canvas-Version fertig ist, will ich sie ebenfalls hier veröffentlichen.
Wer noch nie auf die Idee gekommen ist, aus DOM-Elementen ein Spiel zu stricken, für den will ich kurz auf die allgemeine Funktionsweise eingehen. Jedes Objekt im Spiel erstellt bei seiner Erzeugung auch mit Hilfe von document.createElement ein div. Dieses bekommt eine Klasse, welche alle zum Anzeigen nötigen Informationen, wie Höhe, Breite oder Hintergrundbild enthält. Wird nun ein Element sichtbar oder unsichtbar wird es mit appendChild oder removeChild in ein div mit der id gameframe eingefügt oder entfernt. Der Gameframe stellt den sichtbaren Bereich dar. In seiner Mitte befindet sich das Raumschiff. Womit wir beim Thema Futurama sind, denn unser Raumschiff verfügt über einen besonderen Antrieb. Der Antrieb bewegt nicht das Raumschiff, sondern er bewegt das Universum um das Schiff herum. So hat das Schiff eine feste Position und wird auch das gesamte Spiel nicht bewegt. Was bewegt wird, sind alle anderen Objekte und der Hintergrund der Gameframes.
Etwas komplizierter wird es bei den Bewegungen. Hier hat man in der Regel eine Richtung, in die sich etwas bewegt und eine Geschwindigkeit. Dabei handelt es sich bei der Geschwindigkeit um eine Strecke und bei der Richtung um einen Winkel. Ja nun werden alte Erinnerungen an den Mathematikunterricht in der Schule wach. Wenn die Geschwindigkeit die Hypotenuse eines rechtwinkligen Dreiecks ist, dann konnte man da doch irgendwas mit Sinus und Kosinus berechnen. Und dann gab es auch noch Bogen- und Winkelmaß. Da meine Erinnerung an den Mathematikunterricht auch schon etwas verstaubt waren, bin ich froh, dass ich Finn und Wikipedia hatte. Zusammen konnten wir ein paar Funktionen innerhalb der vector.js verfassen, die diese Berechnungen für uns übernommen haben. Kleine anmerkung noch, den Y-Wert muss man mit -1 multiplizieren, da das Koordinatensystem auf dem Bildschirm ja etwas anders gepolt ist als herkömmliche. Wer Details zur Berechnung wissen möchte, dem empfehle ich sich die JavaScript-Datei einfach mal selbst anzusehen. Wen man mit dem ganzen Sinus und Kosinus Geraffel endlich fertig ist, hat man einen zweidimensionalen Vektor als Ergebnis. Mit diesem kann man nun schon ein wenig besser Rechnen. Als erstes berechnet man den Bewegungsvektor des Schiffes. Der Hintergrund des Gameframes wird dann um den negativen Wert dieses Vektors (also genau entgegengesetzt der Flugrichtung) verschoben. Ebenso funktioniert es bei unbewegten Objekten wie den Asteroiden. Bei bewegten Objekten wird ein eigener Bewegungsvektor errechnet und mit dem des Schiffes zusammengerechnet. Nach der Berechnung der neuen Positionen wird das Dom entsprechend manipuliert, dass diese auch Sichtbar werden.
Und als hätten wir von Mathematik noch nicht genug, kommt jetzt auch noch der Satz des Pythagoras ins Spiel. Diesen brauchen wir zur Abstandsberechnung, die für die Sichtbarkeit von Objekten und die Kollisionsberechnung eine große Rolle spielt. Der Einfachheit halber hat in diesem Spiel jedes Objekt einen Kreis als Hitbox. Dies vereinfacht die Kollisionsberechnung enorm, da man nur noch den Abstand der Mittelpunkte zweier Objekte berechnen muss und anschließend Prüfen muss ob dieser kleiner als die Summe der Radien ist. Ist dies der Fall, hat eine Kollision stattgefunden. Aus Gründen der Performance habe ich mich entschlossen nur zu prüfen ob Gegner (Asteroiden eingeschlossen) mit dem Schiff oder seinen Schüssen kollidieren. Zwischen den Gegnern gibt es keine Kollisionsabfragen.
Der letzte Punkt, dem ich mich widmen möchte, ist das Universum. Vom Universum werden alle Objekte erzeugt und alles was nicht sichtbar ist wird im Universum gespeichert. Dabei ist das Universum in Sektoren unterteilt. Es werden immer lediglich vier dieser Sektoren betrachtet und geprüft, ob sich in ihnen ein Objekt befinden welches in Reichweite des Schiffes kommt. Ist ein Objekt in Reichweite wird es vom Universum ins aktive Spiel übertragen. Ist ein Objekt außer Reichweite wird es aus dem Spiel entfernt und im Universum gespeichert. Bei den Sektoren, welche untersucht werden, handelt es sich um den Sektor in dem sich das Schiff befinden und die drei am nächsten zum Schiff gelegenen angrenzenden Sektoren. Diese Vorgehensweise soll sicherstellen, dass immer nur eine begrenzte Menge an Objekten untersucht werden muss und somit die Performance in etwa konstant bleibt. Am Anfang ist das Universum leer, ein Sektor wird erst generiert, wenn er auf Objekte untersucht werden soll. Somit wächst das Universum erst mit der Zeit an und braucht nur so viel Speicher wie nötig. Wie die Generierung der Sektoren im Detail funktioniert will ich hier nun nicht mehr erläutern. Ich sage nur soviel: die Art und Anzahl der Gegner hängt von der Entfernung zum Ursprung ab. Daher wird das Spiel immer schwerer, je weiter man sich vom seiner Basis entfernt.
PS: Mein Gott ist das ein langer Artikel geworden, den liest bestimmt keiner...
