Das Ziegelfeld erstellen
Dies ist der 6. von 11 Schritten im Tutorial zum Erstellen eines Breakout-Spiels mit reinem JavaScript. Wir sehen uns an, wie Sie eine Gruppe von Ziegeln erstellen, sie mithilfe einer Schleife auf dem Bildschirm zeichnen und entfernen, wenn der Ball sie trifft. Das Ziegelfeld zu erstellen ist etwas komplizierter, als ein einzelnes Objekt auf dem Bildschirm hinzuzufügen.
Die Ziegel zeichnen
Alle Ziegel verwenden dasselbe Bild. Daher können wir es einmal erstellen und decodieren und anschließend für alle Ziegel verwenden. Fügen Sie GameObject eine statische assets-Map und ein url-Feld hinzu und ersetzen Sie den Konstruktor und die preload()-Methode:
class GameObject {
static assets = new Map();
url;
// …
constructor(url, ctx) {
this.url = url;
this.ctx = ctx;
}
async preload() {
if (!GameObject.assets.has(this.url)) {
const asset = new Image();
asset.src = this.url;
GameObject.assets.set(
this.url,
asset.decode().then(() => asset),
);
}
this.asset = await GameObject.assets.get(this.url);
if (this.size.w === undefined) {
this.size.w = this.asset.width;
this.size.h = this.asset.height;
}
}
// …
}
Der Cache ordnet jeder URL ein Promise zu, das sich mit dem decodierten Bild erfüllt. Der erste preload()-Aufruf für eine URL erstellt das Bild und beginnt mit der Decodierung. Spätere Aufrufe warten auf dasselbe Promise und erhalten dasselbe Bild. Jedes Objekt hat weiterhin seine eigene Position und Größe.
Wie Ball und Paddle basiert auch Brick auf der Klasse GameObject. Ein Ziegel hat keine Standardposition und -größe; beides muss im Konstruktor ausdrücklich angegeben werden. Da die Ziegel festgelegte Abmessungen haben, können wir die zusätzlichen Parameter dWidth und dHeight von ctx.drawImage() verwenden. Damit wird das Bild automatisch skaliert, falls es noch nicht die gewünschten Abmessungen hat.
class Brick extends GameObject {
constructor(url, ctx, x, y, w, h) {
super(url, ctx);
this.pos = { x, y };
this.size = { w, h };
}
draw() {
const { left, top } = this.hitbox;
this.ctx.drawImage(this.asset, left, top, this.size.w, this.size.h);
}
}
Sie müssen außerdem das Ziegelbild herunterladen und in Ihrem Verzeichnis /img speichern.
Wir fassen den gesamten Code zum Zeichnen der Ziegel in einer initBricks-Funktion zusammen, damit er vom übrigen Code getrennt bleibt. Fügen Sie unter colliders.push(paddle); einen Aufruf von initBricks hinzu:
// …
const paddle = new Paddle("img/paddle.png", ctx);
colliders.push(paddle);
const bricks = initBricks();
// …
Sorgen Sie außerdem dafür, dass das Spiel vor dem Start auf das Vorladen der Ziegel wartet: Fügen Sie dazu , ...bricks zum Array in Promise.all() hinzu. Diese Aufrufe verwenden gemeinsam das zwischengespeicherte Decodierungs-Promise, sodass das Ziegelbild nur einmal decodiert wird.
Nun zur Funktion selbst. Fügen Sie die initBricks-Funktion am Ende der Datei script.js hinzu. Zunächst fügen wir das Objekt bricksLayout hinzu, das wir gleich benötigen:
function initBricks() {
const bricksLayout = {
width: 50,
height: 20,
count: {
row: 3,
col: 7,
},
offset: {
top: 50,
left: 60,
},
padding: 10,
};
const bricks = [];
// continue adding here...
return bricks;
}
bricksLayout enthält alle benötigten Informationen: die Breite und Höhe eines einzelnen Ziegels, die Anzahl der Ziegelreihen und -spalten auf dem Bildschirm, den oberen und linken Versatz (die Stelle auf dem Canvas, an der wir mit dem Zeichnen der Ziegel beginnen) sowie den Abstand zwischen den Ziegelreihen und -spalten.
Erstellen wir nun die Ziegel. Wir können die Reihen und Spalten mit Schleifen durchlaufen und in jedem Durchlauf einen neuen Ziegel erstellen. Fügen Sie die folgende verschachtelte Schleife unter der vorherigen Codezeile hinzu:
for (let c = 0; c < bricksLayout.count.col; c++) {
for (let r = 0; r < bricksLayout.count.row; r++) {
const brickX =
c * (bricksLayout.width + bricksLayout.padding) +
bricksLayout.offset.left;
const brickY =
r * (bricksLayout.height + bricksLayout.padding) +
bricksLayout.offset.top;
const newBrick = new Brick(
"img/brick.png",
ctx,
brickX,
brickY,
bricksLayout.width,
bricksLayout.height,
);
bricks.push(newBrick);
}
}
Die Position brickX wird berechnet, indem bricksLayout.width und bricksLayout.padding addiert, mit der Spaltennummer c multipliziert und anschließend bricksLayout.offset.left hinzugefügt wird. Die Berechnung von brickY erfolgt genauso, verwendet aber die Reihennummer r, bricksLayout.height und bricksLayout.offset.top. So wird jeder Ziegel an der richtigen Stelle platziert, mit Abstand zu den anderen Ziegeln und mit einem Versatz zum linken und oberen Canvas-Rand gezeichnet.
Schließlich können wir die Ziegel in der Funktion update() auf dem Bildschirm zeichnen. Fügen Sie Folgendes unter dem Aufruf von paddle.draw() hinzu:
for (const brick of bricks) {
brick.draw();
}
Wenn Sie index.html jetzt neu laden, sollten Sie die Ziegel in gleichmäßigen Abständen auf dem Bildschirm sehen.
Kollisionserkennung zwischen Ziegel und Ball
Die nächste Herausforderung ist die Kollisionserkennung zwischen dem Ball und den Ziegeln. Zum Glück haben wir bereits ein allgemeines Kollisionssystem implementiert und müssen die Ziegel nur noch daran anbinden.
Registrieren Sie zunächst jeden Ziegel als Collider, direkt unter dem Aufruf von initBricks():
const bricks = initBricks();
for (const brick of bricks) {
colliders.push(brick);
}
Fügen Sie jedem Ziegel eine onCollide()-Methode hinzu, die ihn aus den Sammlungen bricks und colliders entfernt:
class Brick extends GameObject {
// …
onCollide() {
bricks.splice(bricks.indexOf(this), 1);
colliders.splice(colliders.indexOf(this), 1);
}
}
Der Ziegel muss so schnell wie möglich entfernt werden, damit der Ball nicht erneut von ihm abprallt.
Das war's! Laden Sie Ihren Code neu. Die neue Kollisionserkennung sollte nun wie vorgesehen funktionieren.
Vergleichen Sie Ihren Code
So sollte Ihr bisheriger Code aussehen. Das Beispiel ist direkt ausführbar. Um den Quellcode anzuzeigen, klicken Sie auf die Schaltfläche „Play“.
Nächste Schritte
Wir können die Ziegel nun treffen und entfernen – eine schöne Ergänzung des Spielablaufs. Noch besser wäre es, den Punktestand zu verfolgen und zu gewinnen, sobald alle Ziegel zerstört sind.