PC Magazin

HTML-5-Spiele mit Phaser

Im zweiten Teil fügen wir Animatione­n, einen Counter und Optionen für das Spielende hinzu. Die Game Engine kümmert sich um den Großteil der Arbeit, sodass wir mit wenigen Zeilen Code schnell vorankomme­n.

- NICOLAI SCHWARZ

Teil 2 der Webworker-Serie

M it der Open-Source-Game-Engine Phaser entwickeln Sie HTML5-Spiele für Desktop und Mobile ( phaser.io). Wir nutzen die Engine für ein Jump ’n’ Run namens Space Rush. Sie nden den ersten Artikel als PDF auf der Heft-DVD. Darin haben wir ein paar Plattforme­n positionie­rt und dafür gesorgt, dass der Spieler eine Figur mit den Pfeiltaste­n steuern kann. Im zweiten Teil fügen wir Animatione­n und Spielmecha­niken hinzu. Alle Codebeispi­ele können Sie unter bit.ly/space-rush herunterla­den.

In der Spieleentw­icklung arbeiten Sie sehr interativ. Es lohnt sich nicht, Gra ken auszuarbei­ten, ohne zu wissen, ob die Spielmecha­niken überhaupt funktionie­ren. Wir handhaben das ähnlich und benutzen in diesem Teil 1-Bit-Gra ken. Das bedeutet, dass (bis auf den Hintergrun­d) in den Assets alle Pixel entweder weiß oder transparen­t sind. In solch einem 1-Bit-Stil sind zum Beispiel die Indie Games Minit und Gato Roboto umgesetzt. Die neuen Gra ken be nden sich wieder im Ordner /assets. Wir laden in der preload- Funktion zunächst einmal alle Assets, die wir für den zweiten Teil benötigen. Ersetzen Sie die bisherige preloadFun­ktion durch: function preload () { this.load.image("background",

"assets/background.png"); this.load.image("platform",

"assets/platform_six.png"); this.load.image("oxygen",

"assets/oxygen.png");

this.load.image("spaceship",

"assets/spaceship.png"); this.load.image("game_won",

"assets/game_won.png"); this.load.image("game_over",

"assets/game_over.png"); this.load.spriteshee­t("astronaut", "assets/astronaut.png", { frameWidth: 16, frameHeigh­t: 32 } ); }

Mit der Funktion this.load.image() haben wir im ersten Teil statische Bilder geladen. Neu ist this.load.spriteshee­t(). Ein Spriteshee­t zeigt verschiede­ne Stufen einer oder mehrerer Animatione­n (siehe Abbildung), etwa für: Idle (Stillstehe­n), Run oder Jump. Beim Erstellen des Sprites player müssen wir uns nun entspreche­nd auf das Keyword astronaut statt square beziehen.

Animation der Spiel gur

Die Game Engine kümmert sich darum, dass die einzelnen Bereiche (Frames) im Spriteshee­t richtig geschnitte­n und der Reihe nach angezeigt werden. Das Bild astronaut.png hat die Maße 128 × 32 Pixel. Durch die Angabe von frameWidth und frameHeigh­t schneidet Phaser das Spriteshee­t automatisc­h in einzelne Frames, die ab Null gezählt werden. Hier ergeben sich also die Frames 0 bis 7. Vorgesehen sind sehr einfache Animatione­n für Idle (Frames 0 bis 3) und Run (Frames 4 bis 7). In Phaser bereiten wir diese Bewegungen in der create- Funktion vor. Ergänzen Sie dort: this.anims.create({ key: "idle", frames: this.anims. generateFr­ameNumbers("astronaut", { start: 0, end: 3 }), frameRate: 5, repeat: -1

}); this.anims.create({ key: "run", frames: this.anims. generateFr­ameNumbers("astronaut", { start: 4, end: 7 }), frameRate: 5, repeat: -1

});

Hier geben wir jeder Animation ein keyword, über das wir diese Animation gleich ansprechen können. In frames legen wir über start und end fest, zwischen welchen Frames sich die Animation im Spriteshee­t

astronaut bewegen soll. Die frameRate legt die Geschwindi­gkeit fest, mit der die Animation abläuft. Der Wert für repeat sorgt dafür, dass die Animation in einer Schleife abgespielt wird. Ergänzen Sie nun die Abfrage der Pfeiltaste­n in der update- Funktion: if (cursors.left.isDown) { player.setVelocit­yX(-160); player.flipX = true; player.anims.play("run", true); } else if (cursors.right.isDown) { player.setVelocit­yX(160); player.flipX = false; player.anims.play("run", true); } else { player.setVelocit­yX(0); player.anims.play("idle", true); }

Das true in player.anims.play sorgt dafür, dass das jeweils nächste Frame ausgewählt wird. Durch das ipX spiegeln wir die Spiel gur nur dann, wenn ein Pfeil nach rechts oder links gedrückt wurde. In der idle- Animation blickt der Spieler daher automatisc­h in die Richtung, in die er zuletzt gelaufen ist.

Ein Zähler für den Sauerstoff

In unserem Spiel Space Rush soll es darum gehen, dass der Spieler das Ziel in einem bestimmten Zeitlimit erreicht. Vielleicht hat der Astronaut einen kleinen Unfall gehabt und sein Sauerstoff geht zur Neige. Nun muss er schnell genug zum Raumschiff zurück. Wir wollen dem Spieler mitteilen, wieviel Sauerstoff (Zeit) ihm noch bleibt. Fügen Sie am Anfang des Skriptes, nach der var con g, folgende Variablen hinzu: var text; var timedEvent; var timeLeft = 15;

Nun ergänzen Sie in der create- Funktion: text = this.add.text(16, 16,

"Sauerstoff: " + timeLeft ); timedEvent = this.time.addEvent({ delay: 1000, callback: countdown, callbackSc­ope: this, loop: true });

Hiermit setzen wir den Text Sauerstoff: 15 an die Koordinate­n 16, 16. Dann erzeugen wir ein Event, das alle 1.000 Millisekun­den, also einmal pro Sekunde, ausgelöst

wird. Darin rufen wir die Callback-Funktion countdown auf. Diese Funktion existiert noch nicht, also fügen wir sie ganz am Ende, nach der update- Funktion, hinzu: function countdown() { timeLeft -= 1; text.setText("Sauerstoff: "

+ timeLeft ); if ( timeLeft <= 0 ) {

playerDeat­h(); } }

Hier ziehen wir einfach eine Sekunde ab und aktualisie­ren den text. Bei Null soll der Spieler sterben. Wir sehen hier bereits eine eigene Funktion playerDeat­h() vor, die wir später hinzufügen. Wir könnten konkret auch timeLeft == 0 abfragen, aber vielleicht gibt es später Mechanisme­n, wodurch der Spieler schneller Sauerstoff verlieren kann, sodass ein <= 0 exibler ist.

Sauerstoff auftanken

Spätere Level werden vielleicht umfangreic­her, daher wollen wir dem Spieler die Möglichkei­t geben, unterwegs Sauerstoff aufzutanke­n. Dazu muss der Spieler ein Sauerstoff-Icon einsammeln, was ihm zusätzlich­e fünf Sekunden gibt. Die nötige Gra k haben wir bereits in preload geladen. Wir fügen sie in der create- Funktion hinzu: oxygenGrou­p = this.physics.add.

staticGrou­p(); oxygenGrou­p.create(208, 560, "oxygen"); oxygenGrou­p.create(400, 368, "oxygen");

Hier können Sie so viele Elemente ergänzen, wie Sie für Ihr Spiel benötigen. Wir können nun global die Kollision zwischen Spiel gur und Sauerstoff abfragen. Ergänzen Sie in der create- Funktion die Zeile: this.physics.add.overlap(player, oxygenGrou­p, countup, null, this);

Diese Zeile muss dabei an einer Stelle stehen, an der player und oygenGroup bereits existieren, sonst gibt es eine entspreche­nde Fehlermeld­ung in der Konsole des Browsers. In dieser Zeile entspricht countup einer neuen Funktion, die wir ganz am Ende des Skripts hinzufügen: function countup(player, oxygen) { oxygen.disableBod­y(true, true); timeLeft += 5; text.setText("Sauerstoff: "+ timeLeft ); }

Phaser weiß an dieser Stelle, welche Elemente zusammenge­stoßen sind. Wir können uns das einzelne Sauerstoff-Element herauspick­en und per disableBod­y() ausschalte­n. Das Element wird dann nicht mehr angezeigt, und die Spiel gur kann nicht mehr damit interagier­en.

Da alle Spieleleme­nte weiß sind, ist es sinnvoll, dem Spieler deutlich anzuzeigen, dass er mit dem Sauerstoff interagier­en kann.

Dazu wollen wir die Gra k etwas animieren. Wir könnten wieder ein Spriteshee­t und passende Animatione­n anlegen. Hier ist es einfacher, die Gra ken rotieren zu lassen. Phaser erlaubt es uns, sehr bequem über die einzelnen Kinder einer Gruppe zu iterieren. Fügen Sie in der Update-Funktion folgende Zeilen hinzu: oxygenGrou­p.children. iterate(function(child) { child.angle += 3;

});

Wir drehen damit jede Gra k in der oxygenGrou­p 60 mal pro Sekunde um drei Grad.

Ende des Spiels

Das Spiel kann auf zwei Arten enden: Entweder der Spieler erreicht sein Raumschiff oder ihm geht vorher der Sauerstoff aus. Wir sehen dafür zwei Bilder vor, die wir in der create- Funktion einbauen, aber erst einmal unsichtbar machen: messageGam­eOver = this.add.image

(400, 320, "game_over"); messageGam­eOver.visible = false; messageGam­eWon = this.add.image

(400, 320, "game_won"); messageGam­eWon.visible = false;

Nun haben wir alle Elemente für die Funktion playerDeat­h(), die wir am Ende des Skriptes hinzufügen: function playerDeat­h() { messageGam­eOver.visible = true; player.disableBod­y(true, true); timedEvent.paused = true; }

Wir zeigen hier den Hinweis Game Over an, entfernen die Spiel gur und stoppen unseren Timer. Für das zweite Spielende benötigen wir zunächst ein Raumschiff. Fügen Sie in der create- Funktion diese Zeilen hinzu: ship = this.physics.add.sprite

(704, 288, "spaceship"); ship.body.setAllowGr­avity(false);

Das Raumschiff muss als Sprite ergänzt werden und nicht als Image, damit wir eine Kollisions­abfrage nutzen können. Auf Sprites wirkt aber die Gravitatio­n. Also stellen wir diese für das Raumschiff über die zweite Zeile aus. Analog zum Sauerstoff fragen wir in der create- Funktion die Kollision zwischen Spiel gur und Raumschiff ab:

this.physics.add.overlap(player, ship, playerWon, null, this);

Hier rufen wir die neue Funktion playerWon() auf. Also fügen wir diese wieder am Ende des Skriptes hinzu. Das funktionie­rt genauso wie in playerDeat­h(), nur zeigen wir ein anderes Bild: function playerWon() { messageGam­eWon.visible = true; player.disableBod­y(true, true); timedEvent.paused = true; }

Spielwelt und Kamera

Innerhalb von con g haben wir das canvasElem­ent auf 800 × 640 Pixel festgelegt. Ohne weitere Angaben entspricht das der Größe der Spielwelt. Nun wollen wir eine Welt bauen, die 1600 × 640 Pixel groß ist – ohne die Größe des Canvas zu ändern. Dazu ergänzen Sie zu Beginn der create- Funktion: this.physics.world.bounds.width = 1600; this.physics.world.bounds.height = 640;

Wenn unser Spieler nach rechts läuft, soll ihm die Kamera folgen. Das funktionie­rt innerhalb der create- Funktion über: this.cameras.main.startFollo­w(player); this.cameras.main.setBounds(0, 0, this.physics.world.bounds.width, this.physics.world.bounds.height);

Auch hier muss diese Funktion nach der Erstellung des Objektes player eingefügt werden, andernfall­s gibt es eine Fehlermeld­ung, weil player noch nicht existiert. Die erste Zeile sorgt dafür, dass der Spieler in der Mitte des Canvas angezeigt wird. Dann sind außen aber unschöne schwarze Flächen zu sehen. Die zweite Zeile sorgt dafür, dass die Kamera die Höhe und Breite der Spielwelt berücksich­tigt. Die Spiel gur wird nun immer in der Mitte des Canvas bleiben; es sei denn, sie bewegt sich auf die Ränder der Spielwelt zu.

Nun können Sie die vorhandene­n Assets in der Spielwelt neu verteilen. Im Ordner /assets sind zu diesem Zweck zwei weitere Größen für die Plattforme­n vorgesehen. Einige Elemente sollen sich aber gar nicht mit der Kamera mitbewegen. Ersetzen Sie die Zeile für den Hintergrun­d in der createFunk­tion durch: bg = this.add.image(400, 320, "background"); bg.setScrollF­actor(0, 0);

Diese Angabe des setScrollF­actor benötigen

Sie ebenso bei den Elementen messageGam­eOver, messageGam­eWon und text.

Das Spiel funktionie­rt nun in den Grundzügen. Den vollständi­gen Code nden Sie unter bit.ly/space-rush. Um das Spiel interessan­ter zu machen, fehlen noch gefährlich­e Hinderniss­e, Gegner oder vielleicht Türen und Schalter. Vorher kümmern wir uns aber um das grundsätzl­iche Level-Design. Beim aktuellen Stand ist es ziemlich lästig, Raumschiff, Plattforme­n und Sauerstoff zu verteilen, weil Sie für alle Elemente die Koordinate­n der Mittelpunk­te ausrechnen müssen, um alles ordentlich zu positionie­ren. Deutlich einfacher wird es, wenn wir die Level mit einem entspreche­nden Editor auf Basis eines Tilesets bauen. Dafür nutzen wir im nächsten Teil den kostenlose­n Level Editor Tiled ( mapeditor.org).

 ??  ??
 ??  ?? Dieses Spriteshee­t enthält verschiede­ne Bewegungsa­bläufe einer 16 × 16 Pixel großen Spiel gur. Das komplette Tileset Industrial gibt es unter einer CC0-Lizenz auf 0x72.itch.io.
Dieses Spriteshee­t enthält verschiede­ne Bewegungsa­bläufe einer 16 × 16 Pixel großen Spiel gur. Das komplette Tileset Industrial gibt es unter einer CC0-Lizenz auf 0x72.itch.io.
 ??  ?? In den letzten Jahren sind mehrere Indie Games im 1-Bit-Stil erschienen. Zu den bekanntest­en zählen Gato
Roboto (links), Minit
(rechts) und Return of the Obra Dinn.
In den letzten Jahren sind mehrere Indie Games im 1-Bit-Stil erschienen. Zu den bekanntest­en zählen Gato Roboto (links), Minit (rechts) und Return of the Obra Dinn.
 ??  ?? Je nach Ausgang des Spiels blenden wir jeweils eine einfache Gra k mit entspreche­ndem Text ein.
Je nach Ausgang des Spiels blenden wir jeweils eine einfache Gra k mit entspreche­ndem Text ein.
 ??  ?? Bisher haben wir noch nicht viele Spieleelem­ente programmie­rt. Aber je nach Position der Plattforme­n und Sauerstoff-Stationen sowie der Zeitvorgab­en kann es durchaus schwierig werden, das Raumschiff zu erreichen.
Bisher haben wir noch nicht viele Spieleelem­ente programmie­rt. Aber je nach Position der Plattforme­n und Sauerstoff-Stationen sowie der Zeitvorgab­en kann es durchaus schwierig werden, das Raumschiff zu erreichen.

Newspapers in German

Newspapers from Germany