4. Tutorial 04 - Phaser-Einstieg
4.1 GameOverScene
Für das Ende des Spiels - wenn man das letzte Level erfolgreich beendet hat - wird eine neue Szene eingefügt. Dafür wurde der Code um eine neue Klasse GameOverScene (scenes/GameOverScene.js
) ergänzt, welche sehr ähnlich aufgebaut ist, wie die BootScene
. Allerdings müssen keine Assets mehr geladen werden, sondern nur die Texte und der Restart-Button angezeigt werden.
In der GameScene
wird die Methode startNewLevel
um eine Abfrage ergänzt: Wenn die neue Level-Nummer größer als die Anzahl der Levels ist, dann wird die GameOverScene
gestartet (hier im else-Zweig):
1 2 3 4 5 6 7 8 9 10 11 |
|
4.2 Bombe
Nun brauchen wir noch Spielelemente, die den Player daran hindern sein Ziel zu erreichen. Wir starten einmal mit einer Bombe.
4.2.1 Bombe einfügen
Die Grafik wird in der BootScene
geladen:
1 |
|
In der GameScene
wird im create
eine neue Methode createBomb()
aufgerufen, in der wieder eine Gruppe erzeugt wird:
1 2 3 4 |
|
Die Methode addBomb()
, die darin aufgerufen wird ermittelt einen x-Wert, der sich ungefähr an den Koordinaten des Players orientiert. Dazu wird wieder der Ternär-Operator - das abgekürzte if - eingesetzt: Ist der Spieler im linken Bereich, dann wird die Bombe im rechten Bereich geworfen und umgekehrt.
Mit setBounce(1)
wird bewirkt, dass die Bombe ewig "herumspringt", ohne an Geschwindigkeit zu verlieren. setVelocity
setzt den Startvektor für die Geschwindigkeit.
1 2 3 4 5 6 7 8 |
|
4.2.2 Player erweitern - die()
Auch der Player bekommt eine zusätzliche Funktionalität. Die Methode die()
soll bei Aufruf die Grafik rot einfärben und die Animation auf "stand" festlegen.
1 2 3 4 |
|
4.2.3 Collision-Handling
Die Methode addCollisionHandler()
wird um die neuen Collider-Aufrufe ergänzt. Zunächst einmal muss die Bombe mit den Plattformen interagieren:
1 |
|
Wichtig ist vor allem, dass bei "Überlappung" zwischen Player und Bombe etwas passiert. Da wird nämlich die Callback-MethodekillPlayer()
aufgerufen.
1 |
|
Diese Methode ruft zunächst einmal die die()-Methode
des Players auf (Rot einfärben und stehen bleiben) und danach die Methode restartLevel()
.
1 2 3 4 |
|
Die Methode restartLevel()
erhält als Parameter
die aktuelle Levelnummer
und den Score
übergeben (nämlich den Startspielstand dieses Levels, welcher auch direkt im Player-Objekt gespeichert wurde). Dann wird wieder einmal ein Kameraeffekt eingesetzt. Mit shake
und der Dauer in Millisekunden wird die Kamera so richtig durchgeschüttelt - wie es bei einer Bombenexplosion so üblich ist. Über einen Eventlistener (camerashakecomplete)
wird nach Beendigung dieser Animation die Szene mit restart
wieder neu gestartet.
1 2 3 4 5 6 |
|
4.3 Tür zum nächsten Level
4.3.1 Schlüssel als Türöffner - Tweens
In der BootScene
in der Methode preload()
werden wieder die entsprechenden Assets geladen: key
, keyIcon
(für das Scoreboard) und der Sound
für das nehmen des Schlüssels - dieser wird in der GameScene
zum mysound-Objekt
dazugefügt.
1 |
|
1 2 |
|
1 |
|
In der GameScene
wird im create()
die Methode createKey()
aufgerufen. In der wird das Bild des Schlüssels hinzugefügt und, damit der Schlüssel nicht fällt die Eigenschaft allowGravity
auf false
gesetzt. Außerdem wird eine sogenannte Tween-Animation erstellt. Diese bewirkt, dass sich der Schlüssel (Auswahl welches Element mit targets
) in y-Richtung
bewegt und zwar um 6 Pixel
. Dies dauert 800 ms
und mit ease
wird die Art der Bewegung festgelegt (d.h. wie genau verläuft die Bewegung - hier: easeInOut - Am Anfang beschleunigen, zum Schluss abbremsen - siehe z.B. unter https://easings.net/de). Diese Bewegung wird auch wieder in die andere Richtung gemacht (yoyo auf true
) und unendlich oft wiederholt (loop auf -1
).
Info
Tweens werden für sich wiederholende Animationen/Transformationen eingesetzt. In Phaser gibt es dazu eine eigene Tween-Klasse. Mit scene.tweens.add (bzw. this.tweens.add) kann man einen entsprechenden Tweeneffekt zu einem Element hinzufügen.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
In der Methode addScoreBoard()
wird das keyIcon
platziert, welches mit 2 unterschiedlichen Frames anzeigen soll, ob man den Schlüssel schon hat. Auch hier wird der ScrollFactor auf 0,0
gestellt, sodass sich das Bild mit der Kamera mit bewegt und somit statisch erscheint.
1 2 |
|
Damit sich der Player auch merkt, ob er den Schlüssel schon hat oder nicht wird in der Klasse Dude
im Konstruktor eine Eigenschaft hasKey
definiert und auf false
gesetzt.
1 |
|
4.3.2 Tür hinzufügen
Auch für die Tür wird zuerst in der BootScene
das entsprechende SpriteSheet
geladen.
1 2 |
|
Auch der Sound
für das Öffnen der Tür wird geladen und in der GameScene
zum mysound-Objekt
hinzugefügt
1 |
|
Im create()
der GameScene
wird die Methode createDoor()
aufgerufen, welche die Grafik der Tür an der richtigen Stelle platziert (wie im Level, d.h in der JSON-Datei festgelegt).
1 2 3 4 5 |
|
4.3.3 Spieler erweitern - Freeze
Damit der Spieler beim Durchschreiten der Türe (Eine Animation - siehe Kapitel Collision-Handling) nicht mehr mit den Pfeil-Tasten gesteuert werden kann muss er kurz "eingefroren" werden. Dazu bekommt die Dude-Klasse
im Konstruktor die Eigenschaft isFrozen (Startwert false)
zugewiesen.
1 |
|
Im update()
werden alle Aktionen des Spielers nur ausgeführt, wenn er nicht gefroren ist -!this.isFrozen
.
1 2 3 |
|
Die Funktion freeze()
deaktiviert die physikalischen Eigenschaften des Objekts und setzt die Eigenschaft isFrozen
auf true
.
1 2 3 4 |
|
4.3.4 Collision-Handling
Schlussendlich müssen wir noch die einzelnen Collision-Handler festlegen und implementieren.
4.3.4.1 Key
Key und Player sollen bei Überlappung
auslösen und die Methode getKey()
aufgerufen werden.
1 |
|
In dieser Methode wird die Schlüsselgrafik vom Bildschirm genommen und der Player bekommt die Eigenschaft hasKey
auf true
gesetzt. Der Sound wird abgespielt und im Scoreboard
wird die keyIcon-Grafik
auf das Frame 2 (Index 1)
gesetzt - ein voller Schlüssel.
1 2 3 4 5 6 |
|
4.3.4.2 Door - Process-Callback-Funktion
Auch für den Player und die Türe brauchen wir einen Overlap-Collider
. Allerdings darf dieser erst wirklich reagieren, wenn der Player auch wirklich den Schlüssel schon hat. Um Bedingungen für Collider-Ereignisse abfragen zu können gibt es - neben der Collide-Callback-Funktion openDoor()
- eine sogenannte Process-Callback-Funktion
. Diese heißt bei uns hasKey()
.
1 |
|
Diese Funktion hat die Aufgabe false
(darf nicht ausgeführt werden - hat den Schlüssel noch nicht) oder true
(darf ausgeführt werden - hat den Schlüssel) zurück zu liefern. Dies wird hier damit realisiert, dass die Boolean-Werte player.hasKey
(true oder false) mit player.body.touching.down
(berührt den Boden ja oder nein - In der Luft, wenn er gerade springt soll er die Türe ja nicht öffnen können) verknüpft werden und das Ergebnis zurück geliefert.
1 2 3 |
|
Erst wenn hasKey true
zurück liefert wird openDoor()
aufgerufen. Zuerst wird einmal die physikalische Eigenschaft der Türe deaktiviert (disableBody(true,false)
), damit openDoor
nur einmal aufgerufen wird. Die Grafik der Tür wird auf Frame 2 (Index 1) gesetzt. Beim Player wird freeze
aktiviert, damit dieser solange er das Level wechselt nicht mit der Steuerung bewegt werden kann. Dann wird zum Spieler eine Tween-Animation hinzugefügt, in der sein Alpha-Wert sich von 100% auf 0 verändert und er sich der Türe zubewegt. D.h. es sieht so aus, als wenn er durch die Türe verschwinden würde. Erst wenn diese Animation beendet ist (Event-Listener: onComplete
) wird die Methode doorHandler()
aufgerufen, die das nächste Level startet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Methode doorHandler()
:
1 2 3 |
|
4.4 Noch ein Feind - Spider
4.4.1 Spider-Klasse
4.4.1.1 Spider.js
Auch hier starten wir mit dem Laden der Spritesheet-Grafik
in der BootScene
, welche die Animationen für die Spinne enthält.
1 2 |
|
Für die Spinne erstellen wir wieder eine eigene Klasse, da diese ja mehr können soll. Im Konstruktor sind da vor allem drei Dinge wichtig, damit das mit der Physik auch hinhaut: Man muss das Objekt mit group.add(this)
zur Gruppe hinzufügen, die physikalischen Eigenschaften/Methoden aktivieren (physics.world.enable(this)
) und außerdem das Objekt zur Szene hinzufügen (scene.add.existing(this)
). Ansonsten ist die Vorgehensweise ähnlich wie bei anderen Sprites.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Neben der normalen Animation "krabble" ist kommt hier eine weitere Variante der Erstellung von Animationen in der Methode createAnimations()
zum Einsatz. Die Animation die()
führt zuerst 3 mal die Frames 0 und 4 hintereinander aus und danach 6 mal das Frame mit der ID 3.
1 2 3 4 5 6 |
|
Beim update()
der Spinne wird darauf geachtet, ob diese rechts oder links einen Körper berührt oder durch einen Körper blockiert wird (blocked
). Dann wird die x-Geschwindigkeit des Bewegungsvektor umgedreht, d.h. die Spinne versucht in die andere Richtung zu gehen.
1 2 3 4 5 6 7 |
|
Die Methode die()
deaktiviert die Spinne, führt die "die-Animation"
aus - und wenn diese beendet ist nimmt sie das gesamte Objekt aus dem Spiel.
1 2 3 4 5 6 7 |
|
4.4.1.2 Level01.json / Level02.json
Um die Spinne in den Levels einzubauen kommt in den Leveldateien ein zusätzlicher Eintrag dazu, nämlich ein Eintrag mit dem Namen "spiders"
, welches aus einem Array
von Positionsobjekten
besteht (jeweils x
und y
)
1 2 3 4 5 |
|
4.4.1.3 BootScene.js
In der Bootscene laden wir im preload
die Spritesheet-Grafik. Achtung: frameWidth
und frameHeight
müssen auch hier wieder passen, d.h die Grafik in Frames der richtigen Größe unterteilen!
1 2 |
|
4.4.1.4 GameScene.js
In der GameScene
müssen wir folgende Ergänzungen machen, um die Spinne einzufügen:
Im create
erstellen wir eine neue Gruppe, welche wir enemies
nennen - hier könnten ja später auch noch andere Objekte als die Spinne dazukommen. Wenn das Grundverhalten (z.B. Collision-Handler
) gleich ist, dann kann man unterschiedliche Objekte in eine Gruppe geben.
1 2 |
|
Im createSpiders()
gehen wir in einer for-Schleife alle Positionsobjekte durch, die im level
unter dem Namen "spiders"
vorhanden sind (fehlt dieser Eintrag, weil diese Art von Feinden im Level nicht vorkommt macht das auch nichts, dann bricht die Schleife sofort ab). Für jedes dieser Elemente erstellen wir ein neues Spider-Objekt und übergeben die Koordinaten
, den Schlüssel
(Name) und die Enemy-Gruppe (Das Objekt wird ja direkt im Konstruktor in die Gruppe dazugefügt)
1 2 3 4 5 |
|
Auch die update()-Methode
der GameScene
müssen wir erweitern. Nun müssen bei jedem Bildschirmaufbau alle Enemy-Objekte durchlaufen werden - JavaScript bietet hier auch eine forEach-Funktion um Arrays von Objekten zu durchlaufen - und rufen die update()-Methode der Feind-Klasse auf.
1 2 3 4 5 6 |
|
4.4.2 Unsichtbare Wände für den Feind
Oft benötigt man in Spielen unsichtbare Wände, damit sich Objekte nur innerhalb eines bestimmten Bereiches bewegen können. Bei uns wird bei jeder Plattform am Anfang und am Ende eine solche unsichtbare Wand platziert, damit sich die Spinnen, wenn sie auf einer Plattform landen, nur auf dieser hin- und herbewegen können.
Dazu braucht man in der BootScene
natürlich wieder eine entsprechende Grafik:
1 |
|
Die Methode createPlatform()
in der GameScene
wird um zwei Befehle erweitert. Links und rechts soll jeweils eine dieser sichtbaren Wände entstehen.
1 2 3 4 5 6 |
|
Die Funktion createEnemyWall(platform,pos)
platziert entweder links oder rechts eine Wand. Dazu bekommt sie ein Platform-Objekt und die Position (links/rechts) als Parameter übergeben.
Aus dieser Information berechnet sie die jeweilige Position, erstellt dieses Objekt und - am Wichtigsten - stellt die Sichtbarkeit auf false - wall.visible = false
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
4.4.3 Player erweitern - bounce()
Wenn der Player auf eine Spinne hüpft soll diese sterben, ein entsprechender Sound eingespielt werden und das Player zurückprallen (bounce). Das Audio-File wird wieder in der BootScene
geladen (und in der GameScene
zum mysound-Objekt
hinzugefügt).
1 |
|
In der Dude-Klasse wird die Methode bounce() eingefügt, welche dem Spieler einen "Schubs" (Impuls) nach oben gibt - er prallt ab.
1 2 3 4 |
|
4.4.4 Collision-Handling
Auch die Methode addCollisionHandler()
wird wieder entsprechend erweitert. Zuerst müssen die Spinnen mit den Plattformen und den EnemyWalls interagieren:
1 2 |
|
Für die Interaktion mit dem Spieler wird wieder die overlap-Methode eingesetzt, die im gegebenen Fall die Methode dudeVsSpider()
startet.
1 |
|
In der Methode dudeVsEnemy()
müssen wir zwischen zwei Fällen unterscheiden:
- Player kommt von oben: Spinne stirbt und Player prallt ab
- Ansonsten (Player kommt von der Seite): Player stirbt
Die zugrunde liegende Abfrage testet einfach die y-Geschwindigkeit des Spielers. Ist diese > 0 bedeutet das, dass der Spieler gerade von oben herunterfällt.
1 2 3 4 5 6 7 8 9 10 |
|