Technische Grundlagen und Details
Im letzten Post wurde die Absicht, Ziele und der Rahmen gesetzt und nun kommen wir endlich zu einem Thema, das ich hoffentlich etwas flüssiger aus den Fingern in diesen Blog geschrieben kriege. Es geht um die grundlegende Technik die ich benutzten werde und wie in den Zielen beschrieben, werde ich ein fertiges Framework als Basis für das erste Spiel nutzen.
Vorstellung Frameworks
In der Welt der Programmierung ist ein Framework die Grundlage für das zu entwerfende Produkt, das bereits viele technische Aspekte abdeckt. Man nimmt mit einem Framework aus der Entwicklungsphase viel Arbeit und kann sich mehr um das eigentliche Produkt kümmern.
In der Webentwicklung sind Frameworks oft Anstoß einer langen und pausenlosen Diskussion, welche Aspekte ein Framework abdecken muss, in welche Bereiche ein Framework nicht vorstoßen soll und somit die Optionen für den Entwickler und sein Produkt nicht einschränken darf. Bei nativen Anwendungen sind Frameworks auch das Fundament, jedoch sind die Entscheidungen oft limitiert denn das direkte Zusammenspiel zwischen Betriebssystem und Anwendung in Paarung mit der gewählten Programmiersprache (und dem Hersteller) lassen weniger Optionen.
Beim Game Development wird die Basis/Fundament nicht Framework genannt, sondern Engine (GameEngine). Im Vergleich zu einem Framework ist es für den Entwickler aber das gleiche, denn es nimmt einem einen unglaublichen Berg an Arbeit ab und deckt nahezu alle Bereiche ab, die notwendig sind. Bekannte native GameEngines sind Epic UnrealEngine, CryTek CryEngine, Unity UnityEngine und viele proprietäre Engines von GameStudios, die vom Namen bekannt, aber für die Masse nicht zugänglich sind.
Entscheidung für ein Framework
Für das Spiel ist das Ziel definiert die Webplattform/Browser zu nutzen. Damit ergibt sich die Möglichkeit das Spiel auf dem PC, Smartphone und sogar modernen Fernsehern (Smart-TV) laufen zu lassen, denn alles was man benötigt ist ein moderner Browser. Die Entscheidung könnte weiterhin lauten: UnrealEngine, CryEngine oder Unity, denn alle Engines erlauben einen Export des Werkes für das Web. Damit können wir aber noch keine Entscheidung treffen, sondern per Zufall eine Wahl treffen, die später bereut wird. Daher suche ich weitere Kriterien.
Als nächstes ist für mich die Auswahl der Programmiersprache ein entscheidender Faktor ob das Ziel erreicht werden kann oder ob man aufgrund der Programmiersprache und seinen Besonderheiten sich in Feinheiten verliert und nicht vorwärts kommt. So habe ich nur wenig bis keine Erfahrung in Java und C/C++ und der Anfang der Entwicklung wäre geprägt vom Lernen der Sprache.
Meine Wahl fällt auf JavaScript/ECMAScript. Die Erklärung möchte ich kurz halten. Javascript ist in allen Browser bereits vorhanden, die Community ist groß und es existiert in der Community ein großes Gefühl an "Share & Help". Das zeigt sich in Unmengen an Tutorials in Blogs und Youtube, an Communityprojekten die offen für jeden sind und jeder sich beteiligen kann und darf. Somit wird dieser Blog auch ein Teil dieses Spirits beinhalten: Ich teile mein Wissen was ich gelernt habe während der Entwicklung und es besteht die Möglichkeit sich zu beteiligen.
Mit der Auswahl der Sprache habe ich zwar die Auswahl der GameEngines reduziert, aber nicht die Engine bestimmt. Engines wie Unity erlauben einen JavaScript ähnlichen Dialekt in ihrer Engine. UnrealEngine kann theoretisch ohne eine Programmiersprache genutzt werden durch die Nutzung der Blueprints und des "visualisierten Programmierens".
Die Entscheidung
Ich habe mich entschieden für das FrameWork PhaserJS. Phaser ist ein quell-offenes Framework das in JavaScript geschrieben und für die direkte Nutzung im Browser gedacht ist. Es deckt viele Bereiche ab die man für die Entwicklung eines Spiels benötigt: Graphics, Video, Timing, Input, Audio, Assets...
Weiter ist der Support für die Engine grandios und sucht meiner Meinung nach seines Gleichen. Es gibt hunderte Beispiele für fast jeden Aspekt der Engine inklusive Code und kurzen Erklärungen. Die Dokumentation ist vollständig und auf einem aktuellen Stand, via Forum kann man auf die freundliche Unterstützung von vielen anderen Entwicklern bauen oder sogar direkten Kontakt mit den Entwicklern herstellen. Und das ganze für lau und ohne Klauseln, denn die Engine steht unter der MIT Lizenz.
Einstieg in PhaserJS
Die Webseite von Phaser bietet ein Tutorial, wie man die GameEngine herunterlädt und die ersten Schritte hin zu einer Entwicklungsumgebung, die das Programmieren und Experimentieren vereinfachen. Ich erspare mir diese Erklärung zu wiederholen, merke aber direkt an das ich hier zu diesem Zeitpunkt einen Teil unterschlage.
Im Vorfeld zu diesem Blog habe ich mir bereits eine Entwicklungsumgebung und einen Workflow zusammengesetzt, der es mir ermöglicht, nur auf das entwickeln zu konzentrieren. Basis ist dafür NodeJS mit all seinen Helfern und Tools die ich gerne in Zukunft noch weiter beschreiben werden, sowie meinen darauf aufsetzenden Workflow und Tooling.
Jetzt aber möchte ich erst einmal etwas Lust auf Phaser machen und warum es meine Wahl war als Engine.
State Konzept von Phaser
Phaser ist eine GameEngine und ermöglicht mit der unkomplizierten Programmiersprache JavaScript einen schnellen Einstieg und Ergebnisse. Dazu passiert viel im Hintergrund (wofür eine Engine auch da ist) und man beschäftigt sich primär nur mit der Spitze vom Eisberg.
Zunächst hat Phaser das Konzept der "States" die in einer Reihenfolge von Phaser abgespielt werden. In jedem dieser States wird ein Programmteil abgedeckt und triggered eventuell den Aufruf eines weiteren States an. Best Practice sind die States: Boot, Preload, Menu, Play, GameOver, jedoch kann jeder die States nennen wie er lustig ist, es sind einfach nur Namen für einen Spielstatus.
Wenn wir Phaser initiieren, sagen wir zunächst: Rufe den boot
State auf. Im Bootvorgang
werden dann Dinge erledigt wie das Setzen von Parametern für das gesamte Spiel (z.B. Skalierung)
und Assets (Bilder/Ton) für den nächsten State vorladen und sobald abgeschlossen auch aufgerufen.
Denn der nächste State ist preload
State und hier laden wir, wie der Name schon sagt, alle
Assets vor, die später im eigentlichen Spiel genutzt werden. Damit das für den Spieler nicht
das Starren auf einen schwarzen Screen bedeutet, haben wir im boot
state als Asset einen
Ladebalken und gegebenenfalls ein Logo vorgeladen und der User kann nun den Fortschritt beobachten.
Sobald der Fortschritt die 100% erreicht hat, springen wir zum menu
State. Ein Menü ist ein Menü
und es gibt dazu nicht viel zu erklären. Man gibt dem User die Möglichkeit Spieleinstellungen
vorzunehmen, sich Credits und Bonusmaterial anzusehen und das wichtigste, den play
State aufzurufen.
Denn der play
State ist das Herz des ganzen Spiels, denn hier wird das eigentliche Spiel
abgehandelt: Userinput wird erkannt und verarbeitet, Audio zum richtigen Zeitpunkt abgespielt oder
die Physikberechnung einbezogen und final dann Bilder generiert, positioniert und dargestellt.
Zum Schluss dann der gameOver
State, der dem User das Ergebnis seines Spiels aufzeigt und die
Möglichkeit gibt, wieder zum Menü zurückzukehren für eine neue Runde.
Diesen ganzen Zyklus kennen wir aus jedem Spiel und jede Spielengine bietet eine Lösung, die Namen sind nur unterschiedlich. Unity nennt es "Scene", bei UnrealEngine müsste es ein "Level" sein.
Für Phaser vielleicht notwendig zu sagen: Abhängig vom Umfang des Spiels kann es notwendig und
sinnvoll sein den play
State zu unterteilen. Dieser Unterteilung kann zum Beispiel die
Aufteilung jedes Levels in einen eigenen State sein, womit die Abfolge wäre: Boot, Preload, Menu,
Level1, Level2, Level3, ..., Gameover.
Gliederung eines State
Ein State ist aber nicht nur ein großer Block, sondern ein State hat jeweils für sich ebenfalls eine Abfolge von Funktionen die automatisch abgearbeitet werden. Das ist ein Unterschied zum Aufruf eines State, denn ein State wird durch den Programmierer absichtlich getriggered, wobei innerhalb eines States die Funktionen durch die Phaser Engine automatisch aufgerufen werden.
In dieser dargestellten State Transition von boot
nach preload
passiert folgendes:
Der boot
state wird von oben nach unten abgearbeitet bis zu shutddown()
und irgendwo
im boot
State setzt der Programmierer den Trigger, den preload
State zu starten.
Aufgrund des Triggers wird jetzt shutdown()
im boot
durchlaufen und es geht weiter mit
init()
im preload
State und endet zunächst wieder bei render()
, bis ein Trigger
vom Programmierer erreicht wird, das den Aufruf eines weiteren States bedeutete.
Code, endlich Code!
Nun mit diesem kurzen Intro in Phaser States will ich auf das HTML Grundgerüst eingehen. In diesem Beispiel nutze ich nicht meine Entwicklungsumgebung und Workflow, sondern einfaches HTML mit Javascript auf den klassischen Weg.
Zunächst definiere ich für jeden State eine eigene JavaScript Datei und benenne sie wie der State
auch heißt, also boot
State wird zu boot.js
, der preload
State wird preload.js
usw.
Dabei hat jeder State keine wirkliche Funktion, da es sich hier bei nur ein Beispiel handelt und
der Wechsel zwischen den einzelnen States wird durch Timer gelöst.
function Boot() {
}
Boot.prototype = {
/`
* Boot preload
*
* Load the images used in the preload state
*/
preload: function preload() {
// prepare the boot, load images used
// in the preload state
},
/`
* Create the boot state
*
* Setup the game settings for phaser framework
* and kick off the preload state
*/
create: function create() {
this.scale.windowConstraints.bottom = 'visual';
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this.scale.pageAlignHorizontally = true;
this.scale.pageAlignVertically = false;
this.game.state.start('preload'); // trigger transition to preload state
}
};
Im boot
State wird die Skalierung definiert und sofort der preload
State
aufgerufen via this.game.state.start('preload');
. Eine berechtigte Frage wäre:
Woher kommt this.game
und this.scale
? Nun, die Properties werden nachher durch
das Einbinden der States in die jeweiligen States durch Phaser gesetzt. Das sehen
wir aber später im file script.js
function Preload() {
};
Preload.prototype = {
/`
* Preload the Menu state
*/
preload: function preload() {
// here we would load assets (images/sounds used in the game)
},
/`
* After everything has been preloaded,
* `create` the preload state, which means,
* go to the next state.
*/
create: function() {
this.state.start('menu');
}
};
Auch im preload
State passiert aktuell nicht viel, sondern es wird sofort
(und ohne Timer) der menu
State getriggered.
function Menu() {
};
Menu.prototype = {
/`
* Preload the Menu state
*/
preload: function preload() {
// mostly nothing to do here
},
/`
* Create the menu and bind interactions when
* clicking on buttons and stuff.
*
* As example: use a timer that kicks off after 5 seconds
*/
create: function() {
this.game.time.events.add(Phaser.Timer.SECOND * 5, this.onTimerEnd, this);
},
/`
* Callback for timer
*
* Call the play state
*
* @param {Phaser.Timer} timer
*/
onTimerEnd: function onTimerEnd(timer) {
this.state.start('play');
}
};
Der menu
State verhält etwas anders als die vorherigen States, denn es wird ein
Timer gesetzt, der nach 5 Sekunden ein Callback Event auslöst und die Function onTimerEnd()
aufruft. In diesem Callback wird der play
State getriggered.
function Play() {
};
Play.prototype = {
/`
* Preload the play state
*/
preload: function preload() {
// setup some general state things
},
/`
* Create the play state means here, create
* the game: load images, setup inputcontrollers
* and a lot more.
* Basicly everything that needs to be created to
* show the game at time=0
*
* As example again: a Timer that kicks off after
* 5 seconds and jumnps to the gameover state.
*/
create: function() {
this.game.time.events.add(Phaser.Timer.SECOND * 5, this.onTimerEnd, this);
},
/`
* The important update
*
* This function will be called on every frame.
* This is basicly the gameloop and contains
* all calculations and changes of the game
* behavior (get input, process input, change player
* position or change image
*
* For the demo purpose this is empty
*/
update: function update() {
// gameloop here
},
/`
* Callback for timer
* Jump to the gameover state
*
* @param {Phaser.Timer} timer
*/
onTimerEnd: function onTimerEnd(timer) {
this.state.start('gameover');
}
};
Der play
State is das eigentliche Spiel und hier wird bei der Entwicklung eines
Spiels die meisten Aktionen und Änderungen vorgenommen. Die update()
Funktion habe
ich hier mal mit aufgenommen, denn das ist quasi das Herz des ganzen Spiels: der Gameloop.
Die Funktion update()
wird für die Darstellung eines jeden Frames aufgerufen und ist
somit für die Steuerung des gesamten Spiels verantwortlich.
Für das Beispiel wird in dem play
State wird auch nur ein Timer gesetzt, der nach 5
Sekunden in den gameover
State gewechselt.
function Gameover() {
};
Gameover.prototype = {
/`
* Preload the Gameover state
*/
preload: function preload() {
// mostly nothing to do here
},
/`
* Create the GameOver state, show results
* like time player, items collected
* or a rank with highscores
*
* As example again: a Timer that kicks off after
* 5 seconds and jumnps to the gameover state.
*/
create: function() {
this.game.time.events.add(Phaser.Timer.SECOND * 5, this.onTimerEnd, this);
},
/`
* Callback for timer
*
* Jump back to the menu state and
* start all over.
*/
onTimerEnd: function onTimerEnd() {
this.state.start('menu');
}
};
Der gameover
State ist in dem Beispiel wieder nur ein Timer der
weiterleitet auf den bereits definierten menu
State. Es wird also der
gleiche Menu-State aufgerufen, der bereits am Anfang genutzt wurde, jedoch neu initialisiert.
Als nächstes kommt script.js
und hier wird das Spiel zusammengeschnürt, bedeutet alle
States werden der Phaser Game Engine bekannt gemacht und sind somit von Phaser nutzbar.
window.onload = function() {
var game = new Phaser.Game(960, 640, Phaser.AUTO, 'gamecanvas');
game.state.add('boot', Boot);
game.state.add('preload', Preload);
game.state.add('menu', Menu);
game.state.add('play', Play);
game.state.add('gameover', Gameover);
// boot the game
game.state.start('boot');
};
In der ersten Zeile erzeugen wir ein Phaser.Game
, also ein neues Spiel. Die Werte die als
Parameter übergeben werden bedeuten folgendes:
- 960: Breite in Pixel des Spiels
- 480: Höhe in Pixel des Spiels
- Phaser.AUTO: Phaser erkennt selbstständig ob 2D Canvas oder WebGl als Renderer benutzt werden soll
- gamecanvas: Das HTML Element, in das Phaser das Spiel setzen soll
Während der Parameter eins und zwei selbsterklärend sind, ein paar Worte noch zum dritten Parameter.
Ein Renderer ist die Technik die Phaser vom Browser nutzt um dynamische Inhalte (wie ein Spiel) im
Browser darstellen zu können. Alle Browserhersteller unterstützen Canvas (2D Darstellung, Phaser.CANVAS
) und
WebGL (3D Darstellung, Phaser.WEBGL
). Während WebGL in der Lage ist auch vollwertige 3D Spiele
darzustellen, nutzt Phaser die WebGL Technologie nur im gleichen Umfang wie bei den 2D Canvas
Darstellung. Ein 3D EgoShooter im Browser ist mit WebGL machbar, aber PhaserJS ist nicht für ein
3D EgoShooter ausgelegt. Der einzige Vorteil der die WebGL Nutzung sind Lichteffekte und WebGL Shader
auf der 2D Darstellung. Jedoch gibt es Benchmarks die zeigen, das Phaser mit WebGL nicht die gleiche
Performance bringt, wie mit der 2D Canvas RenderEngine. Daran arbeitet das Phaser Team aber, denn
mit Phaser Version 3.x (aktuell 2.4.x) wird der WebGL Renderer komplett neu geschrieben.
Das Bekanntmachen der States an die Phaser Engine lässt sich in dem Beispiel gut lesen:
game.state.add('boot', Boot);
macht den boot
State bekannt. Die Variable Boot
(2ter Parameter)
wurde vorab in der Datei boot.js
definiert und zwar via function Boot() {}
.
Und den Bootstate triggern wir hier auch via game.state.start('boot') - also mit dem Namen der
bei
game.state.add()` gewählt wurde.
Zuletzt das ganze noch in HTML packen, damit der Browser auch eine Webseite mit dem eingebetteten Spiel öffnen und laden kann.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="language" content="en">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Simple Intro</title>
</head>
<body>
<div id="gamecanvas"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/2.4.3/phaser.min.js"></script>
<script src="boot.js"></script>
<script src="preload.js"></script>
<script src="menu.js"></script>
<script src="play.js"></script>
<script src="gameover.js"></script>
<script src="script.js"></script>
</html>
Es wird im HTML Body nur ein DIV Container #gamecanvas
erstellt (auf den wir in Phaser.Game()
referenziert haben) und alle notwendigen JavaScripts in einer notwendigen Reihenfolge geladen.
Die Reihenfolge ist in diesem Beispiel wichtig, da wir zum Beispiel in script.js
die
Phaser Engine nutzen (via new Phaser.Game()
) und daher sollte Phaser vorher geladen sein.
In dem Beispiel wird die Phaser Engine von einem öffentlichen CDN geladen. Es empfiehlt sich in der Entwicklung eine lokale Kopie zu haben, damit nicht bei jedem Testrun aus dem Netz die Phaser Engine geladen werden muss und der CDN nicht unnötig belastet wird.
Um das ganze lokal zu testen wird nun ein Webbrowser und ein Webserver verwendet. Abhängig
von dem Betriebssystem das verwendet wird, ist ein Webserver mit Python die schnellste Lösung.
Auf jedem Betriebssystem außer MS Windows ist fast immer Python installiert und via folgender
Kommoandozeileeingabe wird ein HTTP Server gestartet: python -m SimpleHTTPServer
Im Browser ist nun mit der Adresse http://localhost:8000
das "Spiel" aufrufbar...also ein
schwarzer Bildschirm ist sichtbar der aber alle 5 Sekunden die States wechselt (glaubt mir ;) )
Damit haben wir die Basis für jedes Phaser Spiel erstellt. Das Laden der Phaser Engine, die Erstellung von States die Schrittweise das Laden und das Vorbereiten des eigentlichen Spiels erledigt. Da dies ein sich immer wiederholender Schritt ist, kann man es als Template oder Boilerplate verwenden oder es in einem Workflow automatisieren (was ich gemacht habe).
Zusammenfassung
In diesem Posting ist die Auswahl der Engine beschrieben und der erste Schritt, wie man die Engine nutzt. Die PhaserJS Engine ist ein für die Nutzung im Browser ausgelegtes Framework und nutzt die Funktionen und Features des Browsers nativ ohne ein zusätzliches Plugin (Bsp Unity Webplayer).
Nach der Erstellung des Spiels mittels via new Phaser.Game()
werden States geladen und die
States sind einfache JavaScript Funktionen die angereichert werden durch die Phaser Engine mit
zusätzlichen Eigenschaften (this.game
, this.scale
).
Bei den States gibt es zwei Merkmale, die man sich merken sollte. States werden durch den
Programmierer definiert und gestartet/getriggered. Wenn ein State gestartet wurde, wird durch
die Phaser Engine eine bestimmte Reihenfolge von Methoden in jedem State durchlaufen
(init
, preload
, create
, update
) ohne das der Programmierer extra einen Trigger setzen muss.
Der erste Schritt ist somit getan und es geht weiter im nächsten Blogpost.