2D-Labyrinthspiel mit Geräteausrichtung
In diesem Tutorial gehen wir den Prozess durch, ein HTML-Mobilspiel zu erstellen, das die Geräteausrichtung und die Vibration APIs nutzt, um das Gameplay zu verbessern, und mit dem Phaser Framework erstellt wird. Grundlegende JavaScript-Kenntnisse sind empfohlen, um das Beste aus diesem Tutorial herauszuholen.
Beispielspiel
Am Ende des Tutorials haben Sie ein voll funktionsfähiges Demospiel: Cyber Orb. Es wird ungefähr so aussehen:

Phaser Framework
Phaser ist ein Framework zum Erstellen von Desktop- und mobilen HTML-Spielen. Es ist ziemlich neu, wächst aber dank der engagierten Community, die am Entwicklungsprozess beteiligt ist, schnell. Sie können es auf GitHub ansehen, wo es als Open Source bereitgestellt ist, die Online-Dokumentation lesen und die große Sammlung von Beispielen durchgehen. Das Phaser Framework bietet Ihnen eine Reihe von Werkzeugen, die die Entwicklung beschleunigen und bei der Handhabung generischer Aufgaben helfen, die zur Fertigstellung des Spiels erforderlich sind, sodass Sie sich auf die Spielidee selbst konzentrieren können.
Projektbeginn
Sie können den Cyber Orb Quellcode auf GitHub sehen. Die Ordnerstruktur ist ziemlich einfach: Der Ausgangspunkt ist die index.html Datei, in der wir das Framework initialisieren und ein <canvas> zum Rendern des Spiels einrichten.

Sie können die Indexdatei in Ihrem bevorzugten Browser öffnen, um das Spiel zu starten und es auszuprobieren. Im Verzeichnis befinden sich außerdem drei Ordner:
img: Alle Bilder, die wir im Spiel verwenden.src: Die JavaScript-Dateien mit dem gesamten Quellcode des Spiels.audio: Die im Spiel verwendeten Audiodateien.
Canvas einrichten
Wir werden unser Spiel auf Canvas rendern, aber wir machen es nicht manuell — das wird vom Framework übernommen. Lassen Sie es uns einrichten: Unser Ausgangspunkt ist die index.html Datei mit folgendem Inhalt. Sie können dies selbst erstellen, wenn Sie mitmachen möchten:
<!doctype html>
<html lang="en-GB">
<head>
<meta charset="utf-8" />
<title>Cyber Orb demo</title>
<style>
body {
margin: 0;
background: #333333;
}
</style>
<script src="src/phaser-arcade-physics.2.2.2.min.js"></script>
<script src="src/Boot.js"></script>
<script src="src/Preloader.js"></script>
<script src="src/MainMenu.js"></script>
<script src="src/Howto.js"></script>
<script src="src/Game.js"></script>
</head>
<body>
<script>
(() => {
const game = new Phaser.Game(320, 480, Phaser.CANVAS, "game");
game.state.add("Boot", Ball.Boot);
game.state.add("Preloader", Ball.Preloader);
game.state.add("MainMenu", Ball.MainMenu);
game.state.add("Howto", Ball.Howto);
game.state.add("Game", Ball.Game);
game.state.start("Boot");
})();
</script>
</body>
</html>
Bisher haben wir eine einfache HTML-Website mit einigen grundlegenden Inhalten im <head>-Abschnitt: Zeichensatz, Titel, CSS-Styling und Einbindung der JavaScript-Dateien. Der <body> enthält die Initialisierung des Phaser Frameworks und die Definitionen der Spielzustände.
const game = new Phaser.Game(320, 480, Phaser.CANVAS, "game");
Die obige Zeile initialisiert die Phaser-Instanz — die Argumente sind die Breite der Canvas, die Höhe der Canvas, die Render-Methode (wir verwenden CANVAS, es gibt aber auch WEBGL und AUTO Optionen) und die optionale ID des DOM-Containers, in den wir die Canvas setzen möchten. Wenn im letzten Argument nichts angegeben ist oder das Element nicht gefunden wird, wird die Canvas dem <body>-Tag hinzugefügt. Ohne das Framework müssten Sie, um das Canvas-Element zur Seite hinzuzufügen, etwas wie das Folgende im <body>-Tag schreiben:
<canvas id="game" width="320" height="480"></canvas>
Das Wichtige ist, dass das Framework hilfreiche Methoden einrichtet, um viele Dinge wie Bildmanipulation oder Anlagenverwaltung zu beschleunigen, die manuell viel schwieriger wären.
Hinweis: Sie können den Artikel Building Monster Wants Candy lesen, um eine eingehende Einführung in die grundsätzlichen, Phaserspezifischen Funktionen und Methoden zu erhalten.
Zurück zu den Spielzuständen: Die folgende Zeile fügt dem Spiel einen neuen Zustand mit dem Namen Boot hinzu:
game.state.add("Boot", Ball.Boot);
Der erste Wert ist der Name des Zustands und der zweite ist das Objekt, das wir ihm zuweisen möchten. Die start-Methode startet den angegebenen Zustand und macht ihn aktiv. Lassen Sie uns sehen, was die Zustände tatsächlich sind.
Verwaltung der Spielzustände
Die Zustände in Phaser sind separate Teile der Spiel-Logik; in unserem Fall laden wir sie aus unabhängigen JavaScript-Dateien für eine bessere Wartbarkeit. Die grundlegenden Zustände, die in diesem Spiel verwendet werden, sind: Boot, Preloader, MainMenu, Howto und Game. Boot kümmert sich um die Initialisierung einiger Einstellungen, Preloader lädt alle Ressourcen wie Grafiken und Audio, MainMenu ist das Menü mit dem Startknopf, Howto zeigt die Spielanweisungen an und der Zustand Game ermöglicht es Ihnen, das Spiel tatsächlich zu spielen. Gehen wir schnell den Inhalt dieser Zustände durch.
Boot.js
Der Boot Zustand ist der erste im Spiel.
const Ball = {
_WIDTH: 320,
_HEIGHT: 480,
};
Ball.Boot = function (game) {};
Ball.Boot.prototype = {
preload() {
this.load.image("preloaderBg", "img/loading-bg.png");
this.load.image("preloaderBar", "img/loading-bar.png");
},
create() {
this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this.game.scale.pageAlignHorizontally = true;
this.game.scale.pageAlignVertically = true;
this.game.state.start("Preloader");
},
};
Das Hauptobjekt Ball ist definiert und wir fügen zwei Variablen hinzu, _WIDTH und _HEIGHT, die die Breite und die Höhe der Spiel-Canvas sind — sie helfen uns, die Elemente auf dem Bildschirm zu positionieren. Zuerst laden wir zwei Bilder, die später im Preload Zustand verwendet werden, um den Fortschritt des Ladens aller anderen Ressourcen anzuzeigen. Die create Funktion enthält einige grundlegende Konfigurationen: Wir richten die Skalierung und die Ausrichtung der Canvas ein und wechseln zum Preload Zustand, wenn alles bereit ist.
Preloader.js
Der Preloader Zustand kümmert sich um das Laden aller Ressourcen:
Ball.Preloader = function (game) {};
Ball.Preloader.prototype = {
preload() {
this.preloadBg = this.add.sprite(
(Ball._WIDTH - 297) * 0.5,
(Ball._HEIGHT - 145) * 0.5,
"preloaderBg",
);
this.preloadBar = this.add.sprite(
(Ball._WIDTH - 158) * 0.5,
(Ball._HEIGHT - 50) * 0.5,
"preloaderBar",
);
this.load.setPreloadSprite(this.preloadBar);
this.load.image("ball", "img/ball.png");
// …
this.load.spritesheet("button-start", "img/button-start.png", 146, 51);
// …
this.load.audio("audio-bounce", [
"audio/bounce.ogg",
"audio/bounce.mp3",
"audio/bounce.m4a",
]);
},
create() {
this.game.state.start("MainMenu");
},
};
Es gibt einzelne Bilder, Sprite-Sheets und Audiodateien, die vom Framework geladen werden. In diesem Zustand zeigt der preloadBar den Fortschritt auf dem Bildschirm an. Dieser Fortschritt der geladenen Ressourcen wird vom Framework mit der Verwendung eines Bildes visualisiert. Mit jedem geladenen Asset können Sie mehr vom preloadBar-Bild sehen: von 0% bis 100%, aktualisiert in jeder Frame. Nachdem alle Ressourcen geladen sind, wird der MainMenu Zustand gestartet.
MainMenu.js
Der MainMenu Zustand zeigt das Hauptmenü des Spiels, in dem Sie durch Klicken auf die Schaltfläche mit dem Spielen beginnen können.
Ball.MainMenu = function (game) {};
Ball.MainMenu.prototype = {
create() {
this.add.sprite(0, 0, "screen-mainmenu");
this.gameTitle = this.add.sprite(Ball._WIDTH * 0.5, 40, "title");
this.gameTitle.anchor.set(0.5, 0);
this.startButton = this.add.button(
Ball._WIDTH * 0.5,
200,
"button-start",
this.startGame,
this,
2,
0,
1,
);
this.startButton.anchor.set(0.5, 0);
this.startButton.input.useHandCursor = true;
},
startGame() {
this.game.state.start("Howto");
},
};
Um einen neuen Knopf zu erstellen, gibt es die add.button Methode mit folgender Liste optionaler Argumente:
- Obere absolute Position auf der Canvas in Pixeln.
- Linke absolute Position auf der Canvas in Pixeln.
- Name der Bildressource, die der Knopf verwendet.
- Funktion, die ausgeführt wird, wenn jemand auf den Knopf klickt.
- Der Ausführungskontext.
- Frame der Bildressource, der als "Hover"-Zustand des Knopfes verwendet wird.
- Frame der Bildressource, der als "Normal"- oder "Out"-Zustand des Knopfes verwendet wird.
- Frame der Bildressource, der als "Click"- oder "Down"-Zustand des Knopfes verwendet wird.
anchor.set legt den Ankerpunkt am Knopf fest, für den alle Berechnungen der Position angewendet werden. In unserem Fall ist es in der Mitte des linken Randes und am Anfang des oberen Randes verankert, sodass es ohne Kenntnis seiner Breite leicht horizontal in der Mitte des Bildschirms zentriert werden kann.
Wenn der Startknopf gedrückt wird, zeigt das Spiel anstelle des direkten Einstiegs in die Aktion zunächst den Bildschirm mit den Informationen, wie man das Spiel spielt.
Howto.js
Ball.Howto = function (game) {};
Ball.Howto.prototype = {
create() {
this.buttonContinue = this.add.button(
0,
0,
"screen-howtoplay",
this.startGame,
this,
);
},
startGame() {
this.game.state.start("Game");
},
};
Der Howto Zustand zeigt die Spielanweisungen auf dem Bildschirm an, bevor das Spiel beginnt. Nach einem Klick auf den Bildschirm wird das tatsächliche Spiel gestartet.
Game.js
Der Game Zustand aus der Game.js-Datei ist, wo all die Magie passiert. Die gesamte Initialisierung erfolgt in der create() Funktion (einmal zu Beginn des Spiels gestartet). Danach erfordert einige Funktionalität weiteren Code zur Steuerung — wir werden unsere eigenen Funktionen schreiben, um kompliziertere Aufgaben zu bewältigen. Besonders beachten Sie die update() Funktion (die bei jedem Frame ausgeführt wird), die Dinge wie die Position des Balls aktualisiert.
Ball.Game = function (game) {};
Ball.Game.prototype = {
create() {},
initLevels() {},
showLevel(level) {},
updateCounter() {},
managePause() {},
manageAudio() {},
update() {},
wallCollision() {},
handleOrientation(e) {},
finishLevel() {},
};
Die create und update Funktionen sind framework-spezifisch, während andere unsere eigenen Kreationen sein werden:
initLevelsinitialisiert die Level-Daten.showLevelzeigt die Level-Daten auf dem Bildschirm an.updateCounteraktualisiert die während jedes Levels gespielte Zeit und erfasst die gesamte Spielzeit.managePausepausiert und setzt das Spiel fort.manageAudioschaltet den Ton ein und aus.wallCollisionwird ausgeführt, wenn der Ball die Wände oder andere Objekte trifft.handleOrientationist die Funktion, die an das Ereignis gebunden ist, das für die Device Orientation API verantwortlich ist, und die Bewegungssteuerungen bietet, wenn das Spiel auf einem mobilen Gerät mit entsprechender Hardware läuft.finishLevellädt ein neues Level, wenn das aktuelle Level abgeschlossen ist, oder beendet das Spiel, wenn das letzte Level abgeschlossen ist.
Hinzufügen des Balls und seiner Bewegungsmechanik
Zuerst gehen wir zur create() Funktion, initialisieren das Ballobjekt selbst und weisen ihm ein paar Eigenschaften zu:
this.ball = this.add.sprite(this.ballStartPos.x, this.ballStartPos.y, "ball");
this.ball.anchor.set(0.5);
this.physics.enable(this.ball, Phaser.Physics.ARCADE);
this.ball.body.setSize(18, 18);
this.ball.body.bounce.set(0.3, 0.3);
Hier fügen wir ein Sprite an der angegebenen Stelle auf dem Bildschirm hinzu und verwenden das 'ball' Bild aus den geladenen grafischen Assets. Wir setzen auch den Anker für alle physikalischen Berechnungen auf die Mitte des Balls, aktivieren die Arcade-Physik-Engine (die die gesamte Physik für die Ballbewegung behandelt) und legen die Größe des Körpers für die Kollisionsdetektion fest. Die bounce Eigenschaft wird verwendet, um die Elastizität des Balls zu bestimmen, wenn er die Hindernisse trifft.
Steuerung des Balls
Es ist cool, den Ball in der Spielzone herumschleudern zu können, aber es ist auch wichtig, ihn tatsächlich steuern zu können! Nun werden wir die Möglichkeit hinzufügen, den Ball mit der Tastatur auf Desktop-Geräten zu steuern, und dann zur Implementierung der Device Orientation API übergehen. Lassen Sie uns zunächst auf die Tastatur konzentrieren, indem wir das Folgende zur create()-Funktion hinzufügen:
this.keys = this.game.input.keyboard.createCursorKeys();
Wie Sie sehen, gibt es eine spezielle Phaser-Funktion namens createCursorKeys(), die uns ein Objekt mit Ereignis-Handlern für die vier Pfeiltasten liefert, mit denen wir spielen können: oben, unten, links und rechts.
Als Nächstes fügen wir den folgenden Code der update()-Funktion hinzu, damit er bei jedem Frame ausgelöst wird. Das this.keys-Objekt wird auf Benutzereingaben überprüft, sodass der Ball entsprechend mit der vordefinierten Kraft reagieren kann:
if (this.keys.left.isDown) {
this.ball.body.velocity.x -= this.movementForce;
} else if (this.keys.right.isDown) {
this.ball.body.velocity.x += this.movementForce;
}
if (this.keys.up.isDown) {
this.ball.body.velocity.y -= this.movementForce;
} else if (this.keys.down.isDown) {
this.ball.body.velocity.y += this.movementForce;
}
Auf diese Weise können wir überprüfen, welche Taste im gegebenen Frame gedrückt wird, und die definierte Kraft auf den Ball anwenden, um die Geschwindigkeit in die richtige Richtung zu erhöhen.
Implementierung der Device Orientation API
Wahrscheinlich der interessanteste Teil des Spiels ist die Verwendung der Device Orientation API zur Steuerung auf mobilen Geräten. Dank dessen können Sie das Spiel spielen, indem Sie das Gerät in die Richtung neigen, in die der Ball rollen soll. Hier ist der Code aus der create()-Funktion, die dafür verantwortlich ist:
window.addEventListener("deviceorientation", this.handleOrientation);
Wir fügen einen Ereignis-Listener zum "deviceorientation" Ereignis hinzu und binden die handleOrientation-Funktion, die folgendermaßen aussieht:
Ball.Game.prototype = {
// …
handleOrientation(e) {
const x = e.gamma;
const y = e.beta;
Ball._player.body.velocity.x += x;
Ball._player.body.velocity.y += y;
},
// …
};
Je mehr Sie das Gerät neigen, desto mehr Kraft wird auf den Ball angewendet, daher bewegt er sich schneller (die Geschwindigkeit ist höher).

Hinzufügen des Lochs
Das Hauptziel im Spiel ist es, den Ball von der Startposition zur Endposition zu bewegen: ein Loch im Boden. Die Implementierung sieht sehr ähnlich aus wie der Teil, in dem wir den Ball geschaffen haben, und wird auch in der create()-Funktion unseres Game Zustands hinzugefügt:
this.hole = this.add.sprite(Ball._WIDTH * 0.5, 90, "hole");
this.physics.enable(this.hole, Phaser.Physics.ARCADE);
this.hole.anchor.set(0.5);
this.hole.body.setSize(2, 2);
Der Unterschied besteht darin, dass sich der Körper unseres Lochs nicht bewegt, wenn wir ihn mit dem Ball treffen, und dass die Kollisionsdetektion berechnet wird (was später in diesem Artikel besprochen wird).
Bau des Blocklabyrinths
Um das Spiel schwieriger und interessanter zu machen, fügen wir einige Hindernisse zwischen dem Ball und dem Ausgang hinzu. Wir könnten einen Level-Editor verwenden, aber im Interesse dieses Tutorials lassen Sie uns etwas Eigenes schaffen.
Um die Blockinformationen zu halten, verwenden wir ein Level-Datenarray: Für jeden Block speichern wir die obere und linke absolute Position in Pixeln (x und y) und den Typ des Blocks — horizontal oder vertikal (t mit dem Wert 'w' für Breite und 'h' für Höhe). Dann parsen wir die Daten, um das Level zu laden, und zeigen die spezifischen Blöcke für dieses Level an. In der initLevels-Funktion haben wir:
this.levelData = [
[{ x: 96, y: 224, t: "w" }],
[
{ x: 72, y: 320, t: "w" },
{ x: 200, y: 320, t: "h" },
{ x: 72, y: 150, t: "w" },
],
// …
];
Jedes Array-Element enthält eine Sammlung von Blöcken mit einer x- und y-Position und einem t-Wert für jeden. Nach levelData, aber noch in der initLevels-Funktion, fügen wir die Blöcke in einem Array in der for Schleife mit einigen framework-spezifischen Methoden hinzu:
for (let i = 0; i < this.maxLevels; i++) {
const newLevel = this.add.group();
newLevel.enableBody = true;
newLevel.physicsBodyType = Phaser.Physics.ARCADE;
for (const item of this.levelData[i]) {
newLevel.create(item.x, item.y, `element-${item.t}`);
}
newLevel.setAll("body.immovable", true);
newLevel.visible = false;
this.levels.push(newLevel);
}
Zuerst wird add.group() verwendet, um eine neue Gruppe von Elementen zu erstellen. Dann wird der ARCADE-Körpertyp für diese Gruppe gesetzt, um physikalische Berechnungen zu ermöglichen. Die newLevel.create-Methode erstellt neue Elemente in der Gruppe mit Startpositionen auf der linken und oberen Seite und ihrem eigenen Bild. Wenn Sie nicht durch die Liste der Elemente gehen möchten, um explizit einer jeden einen Wert zuzuweisen, können Sie setAll auf eine Gruppe verwenden, um es auf alle Elemente in dieser Gruppe anzuwenden.
Die Objekte werden im this.levels Array gespeichert, das standardmäßig unsichtbar ist. Um spezifische Level zu laden, stellen wir sicher, dass die vorherigen Level ausgeblendet sind und das aktuelle angezeigt wird:
Ball.Game.prototype = {
// …
showLevel(level) {
const lvl = level | this.level;
if (this.levels[lvl - 2]) {
this.levels[lvl - 2].visible = false;
}
this.levels[lvl - 1].visible = true;
},
// …
};
Dank dieser Funktion bietet das Spiel dem Spieler eine Herausforderung — jetzt muss er den Ball über das Spielfeld rollen und durch das Labyrinth aus Blöcken manövrieren. Dies ist nur ein Beispiel für das Laden der Level, und es gibt nur 5 davon, um die Idee zu veranschaulichen, aber Sie können daran arbeiten, dies selbst zu erweitern.
Kollisionsdetektion
An diesem Punkt haben wir den vom Spieler gesteuerten Ball, das Loch zum Erreichen und die Hindernisse, die den Weg blockieren. Es gibt jedoch ein Problem — unser Spiel hat noch keine Kollisionsdetektion, also passiert nichts, wenn der Ball die Blöcke trifft — er geht einfach durch sie hindurch. Lassen Sie uns das beheben! Die gute Nachricht ist, dass das Framework die Berechnung der Kollisionsdetektion übernimmt, wir müssen nur die kollidierenden Objekte in der update()-Funktion spezifizieren:
this.physics.arcade.collide(
this.ball,
this.borderGroup,
this.wallCollision,
null,
this,
);
this.physics.arcade.collide(
this.ball,
this.levels[this.level - 1],
this.wallCollision,
null,
this,
);
Dies wird dem Framework mitteilen, die wallCollision-Funktion auszuführen, wenn der Ball auf eine der Wände trifft. Wir können die wallCollision-Funktion verwenden, um jede gewünschte Funktionalität hinzuzufügen, wie das Abspielen des Bounce-Sounds und die Implementierung der Vibration API.
Hinzufügen des Sounds
Unter den vorab geladenen Assets befand sich ein Audio-Track (in verschiedenen Formaten für die Browser-Kompatibilität), den wir jetzt verwenden können. Es muss zuerst in der create() Funktion definiert werden:
this.bounceSound = this.game.add.audio("audio-bounce");
Wenn der Status des Audios true ist (also die Sounds im Spiel aktiviert sind), können wir ihn in der wallCollision-Funktion abspielen:
if (this.audioStatus) {
this.bounceSound.play();
}
Das ist alles — das Laden und Abspielen der Sounds wird mit Phaser erreicht.
Implementierung der Vibration API
Wenn die Kollisionsdetektion wie erwartet funktioniert, lassen Sie uns einige Spezialeffekte mit Hilfe der Vibration API hinzufügen.

Der beste Weg, dies in unserem Fall zu verwenden, besteht darin, das Telefon jedes Mal vibrieren zu lassen, wenn der Ball die Wände trifft — innerhalb der wallCollision-Funktion:
if ("vibrate" in window.navigator) {
window.navigator.vibrate(100);
}
Wenn die vibrate-Methode durch den Browser unterstützt wird und im window.navigator-Objekt verfügbar ist, lassen Sie das Telefon für 100 Millisekunden vibrieren. Das ist es!
Hinzufügen der verstrichenen Zeit
Um die Wiederholbarkeit zu verbessern und den Spielern die Möglichkeit zu geben, gegeneinander anzutreten, speichern wir die verstrichene Zeit — die Spieler können dann versuchen, ihre beste Spielzeit zu verbessern. Um dies zu implementieren, müssen wir eine Variable erstellen, die die tatsächliche Anzahl der Sekunden speichert, die seit dem Start des Spiels vergangen sind, und sie den Spielern im Spiel anzeigen. Lassen Sie uns zuerst die Variablen in der create-Funktion definieren:
this.timer = 0; // time elapsed in the current level
this.totalTimer = 0; // time elapsed in the whole game
Dann können wir direkt danach die notwendigen Textobjekte initialisieren, um diese Informationen für den Benutzer anzuzeigen:
this.timerText = this.game.add.text(
15,
15,
`Time: ${this.timer}`,
this.fontBig,
);
this.totalTimeText = this.game.add.text(
120,
30,
`Total time: ${this.totalTimer}`,
this.fontSmall,
);
Wir definieren die oberen und linken Positionen des Texts, den Inhalt, der angezeigt wird, und das auf den Text angewendete Styling. Wir haben diese Information auf dem Bildschirm ausgedruckt, aber es wäre gut, die Werte jede Sekunde zu aktualisieren:
this.time.events.loop(Phaser.Timer.SECOND, this.updateCounter, this);
Diese Schleife, ebenfalls in der create-Funktion, wird die updateCounter-Funktion jede einzelne Sekunde ab dem Beginn des Spiels ausführen, sodass wir die Änderungen entsprechend anwenden können. So sieht die vollständige updateCounter-Funktion aus:
Ball.Game.prototype = {
// …
updateCounter() {
this.timer++;
this.timerText.setText(`Time: ${this.timer}`);
this.totalTimeText.setText(`Total time: ${this.totalTimer + this.timer}`);
},
// …
};
Wie Sie sehen, inkrementieren wir die this.timer-Variable und aktualisieren den Inhalt der Textobjekte bei jeder Iteration mit den aktuellen Werten, sodass der Spieler die verstrichene Zeit sieht.
Das Level und das Spiel beenden
Der Ball rollt auf dem Bildschirm, der Timer funktioniert und wir haben das Loch erstellt, das wir erreichen müssen. Lassen Sie uns jetzt die Möglichkeit schaffen, das Level tatsächlich zu beenden! Die folgende Zeile in der update()-Funktion fügt einen Listener hinzu, der ausgelöst wird, wenn der Ball das Loch erreicht.
this.physics.arcade.overlap(this.ball, this.hole, this.finishLevel, null, this);
Dies funktioniert ähnlich wie die zuvor erklärte collide-Methode. Wenn der Ball sich mit dem Loch überschneidet (statt zu kollidieren), wird die finishLevel-Funktion ausgeführt:
Ball.Game.prototype = {
// …
finishLevel() {
if (this.level >= this.maxLevels) {
this.totalTimer += this.timer;
alert(
`Congratulations, game completed!\nTotal time of play: ${this.totalTimer} seconds!`,
);
this.game.state.start("MainMenu");
} else {
alert(`Congratulations, level ${this.level} completed!`);
this.totalTimer += this.timer;
this.timer = 0;
this.level++;
this.timerText.setText(`Time: ${this.timer}`);
this.totalTimeText.setText(`Total time: ${this.totalTimer}`);
this.levelText.setText(`Level: ${this.level} / ${this.maxLevels}`);
this.ball.body.x = this.ballStartPos.x;
this.ball.body.y = this.ballStartPos.y;
this.ball.body.velocity.x = 0;
this.ball.body.velocity.y = 0;
this.showLevel();
}
},
// …
};
Wenn das aktuelle Level gleich der maximalen Anzahl von Levels ist (in diesem Fall 5), dann ist das Spiel beendet — Sie erhalten eine Glückwunschnachricht mit der Anzahl der Sekunden, die während des gesamten Spiels verstrichen sind, und einen Knopf, der Sie zurück zum Hauptmenü führt.
Wenn das aktuelle Level niedriger als 5 ist, werden alle notwendigen Variablen zurückgesetzt und das nächste Level geladen.
Ideen für neue Funktionen
Dies ist lediglich ein funktionierendes Demo eines Spiels, das viele zusätzliche Funktionen haben könnte. Wir können zum Beispiel Power-Ups hinzufügen, die entlang des Weges gesammelt werden können und unser Ball schneller rollen lassen, den Timer für einige Sekunden anhalten oder dem Ball spezielle Kräfte verleihen, um Hindernisse zu durchdringen. Es gibt auch Platz für Fallen, die den Ball verlangsamen oder es schwieriger machen, das Loch zu erreichen. Sie können mehr Levels mit zunehmendem Schwierigkeitsgrad erstellen. Sie können sogar Erfolge, Bestenlisten und Medaillen für verschiedene Aktionen im Spiel implementieren. Es gibt endlose Möglichkeiten — sie hängen nur von Ihrer Vorstellungskraft ab.
Zusammenfassung
Ich hoffe, dieses Tutorial wird Ihnen helfen, in die 2D-Spielentwicklung einzutauchen und Sie inspirieren, eigene großartige Spiele zu erstellen. Sie können das Demo-Spiel Cyber Orb spielen und seinen Quellcode auf GitHub überprüfen.
HTML gibt uns rohe Werkzeuge, die darauf aufbauenden Frameworks werden immer schneller und besser, daher ist jetzt ein großartiger Zeitpunkt, um in die Web-Spielentwicklung einzusteigen. In diesem Tutorial haben wir Phaser verwendet, aber es gibt eine Reihe von anderen Frameworks, die ebenfalls in Betracht gezogen werden können, wie ImpactJS, Construct 3 oder PlayCanvas — es hängt von Ihren Vorlieben, Ihren Programmierkenntnissen (oder deren Fehlen), dem Projektskalierung, den Anforderungen und anderen Aspekten ab. Sie sollten sich alle ansehen und entscheiden, welches am besten zu Ihren Bedürfnissen passt.