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.

Phaser state cycle

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.

Phaser State Methods

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:

  1. 960: Breite in Pixel des Spiels
  2. 480: Höhe in Pixel des Spiels
  3. Phaser.AUTO: Phaser erkennt selbstständig ob 2D Canvas oder WebGl als Renderer benutzt werden soll
  4. 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 beigame.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.