11. Apr

Game Development: Tipps für die Entwicklung plattformübergreifender Spiele mit Phaser

Für eine große Bausparkasse entwickeln wir als App-Agentur derzeit ein Marketingspiel. Das Game-Development befindet sich in der finalen Endphase, die Veröffentlichung des Spieles ist für Mai geplant. Als Anforderung galt, das Spiel plattformübergreifend für Desktop-Browser, aber auch als mobile Apps für iOS und Android zu entwickeln. Um den Budgetrahmen einhalten und eine größtmögliche Plattform-Unterstützung garantieren zu können, entwickelten wir das Spiel mit webbasierten Technologien. Als Spiele-Framework und grundlegende Entwicklungs-Basis kam die HTML5-Game-Engine Phaser zum Einsatz. Um anderen Entwicklern den Einstieg in dieses Thema zu erleichtern, möchten wir unsere Erfahrungen aus diesem Projekt wiedergeben.

Um sich überhaupt mit dem Thema der (mobilen) Spieleentwicklung professionell beschäftigen zu können, sind einige Voraussetzungen und Empfehlungen zu erfüllen.

  • Grafikentwicklung: Eine schöne Grafik und passende Musik ist natürlich das A und O für ein erfolgreiches Projekt. Natürlich kann hier auf kostenfreie oder aber kostenpflichtige Stock-Angebote zurückgegriffen werden. Um ein durchgängiges und stimmiges Ergebnis zu erhalten, ist eine individuelle Grafik- und Asset-Erstellung aber unerlässlich. Da wir mehr auf die Entwicklung spezialisiert sind, haben wir hier auf einen entsprechend kompetenten Partner zurückgegriffen.
  • Phaser: Die Wahl der HTML5-Game-Engine fiel nach einiger Recherche auf phaser.io – ein auf Javascript basiertes Framework, das gut dokumentiert und ständig weiterentwickelt wird. Auf Basis von Phaser können webbasierte HTML5-Spiele mit guter Performance (wenn man sich an bestimmte Grundregeln hält) umgesetzt werden: phaser.io
  • TexturePacker: Eine relativ günstige Software, um aus einzelnen Bildern und Grafiken Spritesheets und sogenannte Texture Atlases zu erstellen. Dazu aber später mehr in den Performance-Tipps. Die Software kostet derzeit nicht einmal 40 Euro: www.codeandweb.com/texturepacker
  • CocoonJS: Die webbasierten Spiele laufen auf Desktop-Browsern und aktuellen mobilen Geräten recht performant. Auf älteren iPads, iPhones oder vielen Android-Geräten ist die Browser-Version aber meistens kaum spielbar. Hier ist ein spezieller nativer Rahmen nötig, der die entsprechende Performance garantiert. Das mit Abstand beste Ergebnis bietet hier der Cloud-Dienst CocoonJS, mit dem der HTML5-Code in einen nativen performance-optimierten Rahmen gepackt wird und damit auch in den App Stores veröffentlicht werden kann. Der Cloud-Dienst bietet sowohl kostenpflichtige, als auch kostenlose Accounts: www.ludei.com/cocoonjs
  • Sonstiges: Wie bei normalen Webprojekten auch, empfehlen wir natürlich CSS und in diesem Fall vor allem JS-Dateien zu packen und komprimieren. Es können hier sowohl clientseitige (auf dem Entwicklungsrechner) Tools wie Grunt, aber auch serverseitige Möglichkeiten (z.B. github.com/matthiasmullie/minify) zum Einsatz kommen. Aufgrund eines Continuous Deployment Ansatzes kamen bei uns serverseitige Skripte zum Einsatz.
    Die Entwicklungsumgebung bleibt natürlich jedem selber überlassen. Unsere Wahl ist aber schon seit längerem auf die PHPStorm IDE gefallen, die vor allem bei größeren (Web-)Projekten unschlagbare Vorteile bietet: www.jetbrains.com/phpstorm

Kommen wir zu den eigentlichen Tipps bei der Spieleentwicklung mit Phaser. Auf die grundlegende Einrichtung und Entwicklung gehen wir nicht näher ein, hier gibt es unter phaser.io/tutorials/getting-started genügend Tutorials und Informationen. Wichtig zu wissen ist nur, dass für den lokalen Test und den späteren Browser-Aufruf von webbasierten Spielen auf Basis von Phaser ein Webserver nötig ist.

Eine erste Umsetzung – sofern man die passenden Grafiken und Assets vorliegen hat – eines webbasierten Spieles ist „schnell“ erledigt. Das Phaser-Framework nimmt einem hier viel Arbeit ab, und kümmert sich beispielsweise um eine mögliche Kameraführung, physikalische Eigenschaften, Kollisionserkennung, sowie Animationen und Effekte. Schwieriger wird es, wenn man das fertige Spiel dann auf anderen Browsern und Rechnern, oder noch gravierender auf mobilen Endgeräten testet. Ohne Erfahrung in der Entwicklung von mobilen Spielen auf Basis von Phaser, läuft man hier sehr schnell in Performance-Engpässe – die den Spielspaß und die Qualität stark beeinträchtigen können.

Performance-Tipps von der App-Agentur für die Spielentwicklung mit Phaser

Canvas anstatt WebGL

Bei Phaser habt ihr die Wahl zwischen 2 Möglichkeiten, wie das Spiel im Browser/HTML gerendert wird. Entweder in einem Canvas-Element, oder über die relativ neue WebGL-API. Man sollte meinen, WebGL wäre die bessere Wahl – ist es aber (noch) nicht. Als erster, ganz wichtiger Tipp: Immer Canvas als Render-Methode verwenden:

var game = new Phaser.Game(960, 640, Phaser.CANVAS);

Texure Atlases und optimierte Grafiken verwenden

Es klingt logisch, wird aber leider leicht übersehen. Alle Grafiken sollten in der Größe entsprechend optimiert erstellt werden. Ein webbasiertes Spiel das auf 960×640 Pixel basiert lädt performanter, als ein Spiel mit 1.920×1024 Pixel. Überlegt euch also, wie groß (in Pixel) euer Spiel wirklich sein muss und wählt eine kleinstmögliche Größe aus. Darauf aufbauend erstellt ihr alle Grafiken entsprechend auf diese Auflösung optimiert. Oder – um flexibel zu sein – ihr wählt als Ausgangsformat eine relativ große Auflösung und rechnet die Assets für das letztendliche Spiel automatisiert herunter. Das kann euch z.B. auch ein Tool wie das bereits oben genannte TexturePacker erledigen.

Und TexturePacker ist auch das Tool, um aus allen einzelnen Grafiken (Spielfigur, Hindernisse, Hintergründe, etc.) eine einzige Grafikdatei mit einer korrespondierenden JSON-Datei zu generieren (in der JSON-Datei steht dann, an welcher genauen Pixelposition jedes Asset platziert ist). Das ganze nennt sich dann Texture Atlas und sorgt für einen deutlichen Performance-Schub in euerem Spiel.

Aber achtet darauf, dass die resultierende Grafikdatei des Texture Atlas nicht zu groß ist. Ab beispielsweise 2048 Pixel Breite/Höhe kann es insbesondere bei mobilen Geräten/Browsern wieder zu Problemen kommen. Aber auch hier informiert euch TexturePacker, so dass ihr euere Assets gegebenenfalls auf mehrere Texture Atlases aufteilen könnt (allgemeine Assets, Assets pro Level,…).

Empfehlung zum Schluss: Verwendet niemals einzelne/separate Grafikdateien, sondern immer Texture Atlases in (webbasierten) Spielen!

Initiales Setup in den Methoden create(), init() und preload()

Erledigt das ganze initiale Setup in den Methoden preload(), init() und/oder create(). Am besten ihr generiert hier auch alle Assets, die ihr im Spiel oder während eines Levels benötigt. Fasst zudem gleiche Assets/Objekte in eine Gruppe zusammen und erstellt in dieser Gruppe die maximale Anzahl, die gleichzeitig auf dem Bildschirm zu sehen ist. In der Update-Methode könnt ihr dann auf diese bereits erstellen Objekte zugreifen, was sich spürbar auf die Performance auswirkt.

var yourGroup = game.add.group();

for (var i = 0; i < 10; i++){
   yourGroup.create(game.world.randomX, game.world.randomY, 'atlas', 'muenze.png');
}

Logik in den Methoden update() und render() minimieren

Die Update- und Render-Methode ist die Kernzelle des Spieles und wird gegebenenfalls bis zu 60 Mal in der Sekunde aufgerufen. Packt hier also wirklich nur die Logik hinein, die für das Spiel, bzw. die Aktualisierung des Spieles zwingend notwendig ist.

  • Vermeidet Objekte manuell zu erstellen. Greift am besten auf vorher in einer Gruppe erstellen Objekte zurück (Stichwort: outOfCamerBoundsKill und getFirstAlive()phaser.io/docs/2.6.2/Phaser.Group.html)
  • Prüft Kollisionen nur anhand von Gruppen. Fasst also alle zusammengehörige Objekte (z.B. alle gleichen Gegner, Plattformen, Hindernisse) in einer Gruppe zusammen.
  • Versucht die Spiellogik optimal umzusetzen, um möglichst wenig Schleifen und/oder Abfragen zu erhalten. Code-Optimierungen machen bei 60 Aufrufen pro Sekunde durchaus Sinn. Lagert aufwendigere Berechnungen, wenn möglich in die Init- oder Create-Methode aus.
  • Haltet euch an gängige Javascript-Performance-Tipps. Beispielsweise sind Multiplikationen um einiges performanter, als Divisionen. Anstatt x=y/10 schreibt ihr also besser x=y*0.1
  • Nutzt die integrierte Javascript Speicherverwaltung (Gargabe Collection). Deklariert zum Beispiel Variablen innerhalb von Funktionen oder Schleifen mit var foo = ‚bar‘, um sie danach wieder automatisch freizugeben.

Rendert Änderungen an der Oberfläche wirklich nur wenn es nötig ist. Die Position von Objekten (z.B. der Spielfigur) muss zwangsweise laufend aktualisiert werden, um ein flüssiges Spielverhalten zu erreichen. Dies wird in der Regel auch in der Update-Methode abgearbeitet. Alles andere, wie zum Beispiel die Spielstands oder Zeitanzeige, sollte in der Render-Methode ausgelagert werden. Beachtet aber, dass auch die Render-Methode bis zu 60 Mal in der Sekunde aufgerufen wird.

  • Aktualisierungen in der Render-Methode also nur wenn nötig durchführen. Die Aktualisierung eines Punktestandes kann durchaus auch nur jede Sekunde oder sogar nur alle 2 Sekunden erfolgen, ohne dass es eine starke Beeinträchtigung für den Benutzer darstellt.
  • Versucht allgemein die Textausgaben zu minimieren. Dabei soll es eigentlich deutlich performanter sein, anstatt regulären Fonts sogenannte Bitmap-Fonts zu verwenden. Bitmap-Fonts bestehen letztendlich aus Grafiken (zusammengesetzt in einem Art Texture Atlas), daher entfällt das „aufwendige“ Rendern der Schriftart im Browser. Allerdings konnten wir vor allem auf mobilen Geräten eher Performance-Probleme beim Einsatz von Bitmap-Fonts ausmachen. 

Tween- und Partikel-Effekte beschränken

Tween- und/oder Partikeleffekte sind tolle Dinge, um die Atmosphäre und den Spielspaß zu erhöhen – kosten aber vor allem in der webbasierten Entwicklung auch einiges an Ressourcen. Geht damit also eher sparsam um – vor allem in der Update- und Render-Methode.

Extreme Performance-Einbußen auf mobilen (iOS-)Geräten erkennen und vermeiden

Einen Fallstrick hatten wir beim Kompilieren eines Spieles für iOS-Geräte. Dort kam es plötzlich zu starken Performance-Problemen. Aus zu Beginn schwer erklärbaren Gründen ruckelte das Spiel enorm, bzw. stockte teilweise ganz. Das Problem war hierbei ein kurzes Audiofile, das beim Aufsammeln von Gegenständen abgespielt wurde. Beim Überlagern (wenn z.B. Gegenstände dicht beieinander liegen) dieser Audiofiles kam es zu Speicherproblemen auf dem iOS-Gerät. Hier half nur der Einsatz eines nativen Audio-Plugins für Cocoon.js (bzw. für Cordova). Aber dazu später mehr.

Es ist aber auf jeden Fall ein naiver Irrglaube, dass webbasiert entwickelte Spieler immer und überall genau gleich funktionieren und laufen.  

Tipps für die Veröffentlichung und Kompilierung webbasierter Spiele mit CocoonJS

Die Performance auf (älteren) mobilen Endgeräten ist bei webbasierten HTML5-Spielen teilweise leider grausam. Zu veraltet sind die Javascript-Engines in den mobilen Browsern oder es fehlt schlichtweg an Rechenleistung auf dem Smartphone. Einen eigenen nativen Rahmen mit einer eingebauten WebView um den HTML-Code zu stülpen, bringt aus Performance-Sicht nicht wirklich viel. Zumal man das Problem hat, dass webbasierte Spiele unter Phaser einen Webserver benötigen – da das dort implementierte dynamische Laden von Ressourcen nur über einen Webserver funktioniert. 

Um webbasierte Spiele auf Basis von Phaser als App veröffentlichen zu können, gibt es daher verschiedene Cloud-Angebote, die für die Kompilierung, bzw. den nativen Rahmen sorgen. Aus all den Möglichkeiten kann aus Performance-Sicht CocooJS (www.ludei.com/cocoonjs)  empfohlen werden. Cocoon basiert im Hintergund übrigens auf Cordova (cordova.apache.org), über die CocoonJS-Oberfläche können beliebige Cordova-Plugins (z.B. für Push-Nachrichten) mit einem Klick installiert werden. Der einzige Unterschied zur Ausführung des Spieles im normalen Browser ist, dass die Cordova-Bibliothek integriert werden muss und die Initialisierung des Spieles im DeviceReady-Callback erfolgen sollte:

<script type="text/javascript" src="cordova.js"></script>
...
<script>
  function init(){
    //Game Initialisierung
  }

  document.addEventListener("deviceready", init, false);
</script>

CocconJS besticht durch einen kostenlosen Account, einer übersichtlichen Verwaltungsoberfläche und einer guten Performance der generierten Apps. Folgende Tipps sollten aber beherzigt werden.

Canvas+ verwenden

Für eine optimale Performance sollte der Modus Canvas+ bei CocconJS verwendet werden. Dieser beruht auf einer eigenen/internen Render-Engine. Allerdings ist damit der Zugriff auf HTML-DOM-Elemente nicht, bzw. sehr eingeschränkt möglich. Solltet ihr also zum Beispiel Login-Formulare mit Benutzereingaben haben, könnt ihr nicht auf die normalen HTML input-Elemente zurückgreifen. Ihr müsst diese selber mit allen Keydown-Events/etc. selbstständig implementieren. Selbst Phaser-Plugins wie github.com/orange-games/phaser-input werfen in CocoonJS kompilierten Spielen Fehlermeldungen, Die einzige Möglichkeit besteht hier den Quellcode zu erweitern, um bei Methoden wie InputField.prototype.startFocus entsprechende „native“ Funktionen wie Cocoon.Dialog.prompt() aufzurufen.

Hier wird es dann relativ schnell relativ hässlich – vor allem wenn einem die Erfahrung fehlt, wo was anzupacken und zu optimieren ist. Als wichtiger Hinweis können wir nur eines mitgeben: Für aufwendige Benutzereingaben ist Phaser eher nicht geeignet – bzw. Bedarf sehr viel und aufwendiger individueller Programmierung.

Cross-Plattform Problem Audio

Ein weiteres Problem, dass es zu beachten gilt, ist der Einsatz von Audio in Spielen mit Phaser. Musik und Töne machen ein Spiel erst so richtig lebendig, bringen aber durchaus Fallstricke mit sich. Im Browser lässt sich das ganze relativ einfach beheben, indem man mehrere Formate verwendet. Als Beispiel seien hier mp3 (Achtung: Lizenz beachten!) oder ogg genannt. Das Framework Phaser nimmt einem hier viel Arbeit ab und lädt je nach Browser automatisch das passende Format im Hintergrund:

var audio = game.add.audio(['hg_audio.mp3', 'hg_audio.ogg']);

Problematisch wird es, wenn man ein Spiel mit einer so integrierten Audiodatei über CocoonJS für Android kompiliert. Ihr ahnt es? Es funktioniert nicht. Für Android muss das Cordova Media-Plugin im CocoonJS-Projekt integriert werden, der Aufruf im Javascript-Code lautet dann wie folgt:

var audio = new Media(''file:///android_asset/www/hg_audio.mp3''); 

Toll denkt man sich –  dann muss man für iOS ja nur den Pfad anpassen und alles läuft. Leider nicht wirklich. Denn das standardmäßige Cordova Media-Plugin hat unter iOS enorme Schwächen – oder besser gesagt eigentlich Bugs. Paralleles Abspielen von Audiofiles funktioniert beispielsweise gar nicht und die App reagiert mit enormen Performance-Problemen (siehe oben). Hier muss wiederum auf das NativeAudio-Plugin unter Cordova zurückgegriffen werden, der Aufruf im Code resultiert wie folgt:

window.plugins.NativeAudio.preloadComplex('hg_audio', 'hg_audio.mp3')

Die ganzen Audio-Funktionen müssen also sinnvollerweise in einer eigenen Klasse gekapselt werden, um je nach Plattform die unterschiedlichen APIs aufzurufen.

Fazit zur Spieleentwicklung mit Phaser und CocoonJS

Prinzipiell bieten Phaser und CocoonJS eine tolle Möglichkeit Spiele auf Basis von HTML5 plattformübergreifend zu entwickeln. Und mit dem Wissen um die Fallstricke funktioniert das auch mit einem optimierten Kosten-Nutzen-Aufwand und einem erfolgreichen Ergebnis für den Desktop, sowie iOS- und Android-Geräte.

Gerne unterstützen wir euch als App-Agentur bei der Entwicklung von mobilen, webbasierten Spielen.

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.