Animationen und Tweens
Dies ist der 9. Schritt von 11 im Tutorial zum Erstellen eines Breakout-Spiels mit reinem JavaScript. Wir sehen uns an, wie Sie Animationen und Tweens in unserem Spiel implementieren, damit es lebendiger wirkt und mehr Spaß macht.
Animationen
Bei einer Sprite-Animation werden die Sprites eines Spritesheets nacheinander angezeigt. Als Beispiel lassen wir den Ball wackeln, wenn er auf etwas trifft.
Laden Sie zunächst das Spritesheet herunter und speichern Sie es in Ihrem Verzeichnis /img.
Ersetzen Sie die Bild-URL des Balls durch die URL des Spritesheets:
const ball = new Ball("img/wobble.png", ctx);
Fügen Sie in der Klasse Ball eine explizite size hinzu, damit GameObject.preload() nicht die Abmessungen des gesamten Spritesheets als Größe des Balls verwendet. Fügen Sie außerdem Eigenschaften für die Frame-Sequenz und die seit Beginn der Animation verstrichene Zeit hinzu:
class Ball extends GameObject {
size = { w: 20, h: 20 };
wobbleFrames = [0, 1, 0, 2, 0, 1, 0, 2, 0];
wobbleTime = null;
// ... existing properties and methods ...
}
Die Frame-Sequenz verweist über ihre Positionen auf die drei Sprites: 0, 1 und 2. Ein wobbleTime-Wert von null bedeutet, dass keine Animation läuft; deshalb zeigen wir Frame 0 an.
Die Animation abspielen
Fügen Sie Ball die folgenden Methoden hinzu und behalten Sie die vorhandenen Methoden move() und onCollide() bei:
class Ball extends GameObject {
// ...
playWobble() {
this.wobbleTime = 0;
}
updateAnimation(dt) {
if (this.wobbleTime === null) {
return;
}
this.wobbleTime += dt;
if (this.wobbleTime >= this.wobbleFrames.length * (1 / 24)) {
this.wobbleTime = null;
}
}
draw() {
const frame =
this.wobbleTime === null
? 0
: this.wobbleFrames[Math.floor(this.wobbleTime / (1 / 24))];
const { left, top } = this.hitbox;
this.ctx.drawImage(
this.asset,
frame * this.size.w,
0,
this.size.w,
this.size.h,
left,
top,
this.size.w,
this.size.h,
);
}
}
Die Methode playWobble() startet die Animation oder startet sie neu, wenn sie bereits läuft. Die Methode updateAnimation() setzt sie anhand der verstrichenen Zeit in Sekunden fort. Wir spielen die Animation mit 24 Frames pro Sekunde ab, sodass jeder Frame 1 / 24 Sekunden dauert.
Die überschriebene Methode draw() verwendet die Variante von ctx.drawImage() mit neun Argumenten. Die ersten vier Zahlen nach dem Bild wählen einen rechteckigen Ausschnitt aus dem Spritesheet aus; die letzten vier bestimmen Position und Größe dieses Ausschnitts auf dem Canvas.
Die Animation auslösen, wenn der Ball den Schläger trifft
Fügen Sie Paddle eine Methode onCollide() hinzu, die die Animation des Balls startet, wenn das Kollisionssystem einen Treffer meldet:
class Paddle extends GameObject {
// ...
onCollide() {
ball.playWobble();
}
}
Die Animation wird jedes Mal abgespielt, wenn der Ball den Schläger trifft. Sie können ball.playWobble() auch innerhalb der Methode onCollide() des Ziegelsteins aufrufen, wenn das Spiel dadurch Ihrer Meinung nach besser aussieht.
Tweens
Während Sprite-Animationen Frames nacheinander anzeigen, animieren Tweens Eigenschaften eines Objekts in der Spielwelt, etwa seine Breite oder Deckkraft, stufenlos.
Fügen wir unserem Spiel einen Tween hinzu, damit Ziegelsteine nach einem Treffer durch den Ball allmählich verschwinden. Wir müssen jeden getroffenen Ziegelstein weiterhin sofort aus bricks und colliders entfernen, damit er nicht erneut getroffen werden oder Punkte einbringen kann. Damit er während des Tweens weiter gezeichnet wird, fügen Sie neben bricks ein separates Array hinzu:
const disappearingBricks = [];
Ersetzen Sie die Klasse Brick durch Folgendes:
class Brick extends GameObject {
shrinkTime = 0;
constructor(url, ctx, x, y, w, h) {
super(url, ctx);
this.pos = { x, y };
this.size = { w, h };
}
draw() {
const scale = 1 - Math.min(this.shrinkTime / 0.2, 1);
const width = this.size.w * scale;
const height = this.size.h * scale;
this.ctx.drawImage(
this.asset,
this.pos.x - width / 2,
this.pos.y - height / 2,
width,
height,
);
}
onCollide() {
bricks.splice(bricks.indexOf(this), 1);
colliders.splice(colliders.indexOf(this), 1);
disappearingBricks.push(this);
score += 10;
}
}
Die Eigenschaft shrinkTime erfasst in Sekunden, wie lange der Ziegelstein bereits schrumpft. Der Tween soll 0,2 Sekunden dauern. Wenn wir shrinkTime durch 0,2 teilen, erhalten wir den Fortschritt von 0 bis 1. Ziehen wir diesen Fortschritt von 1 ab, ergibt sich ein Skalierungsfaktor, der linear von der vollen Größe auf null sinkt. Die Zeichenkoordinaten sorgen dafür, dass das schrumpfende Bild an der ursprünglichen Position des Ziegelsteins zentriert bleibt.
Nachdem der Ziegelstein aus dem Spielgeschehen entfernt wurde, bleibt er also in disappearingBricks erfasst, damit er animiert werden kann.
Die Effekte aktualisieren und zeichnen
Ersetzen Sie update() durch Folgendes, um beide Effekte in jedem Frame fortzusetzen:
function update(timestamp) {
const dt = lastTimestamp === null ? 0 : (timestamp - lastTimestamp) / 1000;
lastTimestamp = timestamp;
ball.updateAnimation(dt);
for (let i = disappearingBricks.length - 1; i >= 0; i--) {
const brick = disappearingBricks[i];
brick.shrinkTime += dt;
if (brick.shrinkTime >= 0.2) {
disappearingBricks.splice(i, 1);
}
}
moveBall(dt);
// drawing code...
for (const brick of disappearingBricks) {
brick.draw();
}
drawStatus();
if (bricks.length === 0 && disappearingBricks.length === 0) {
alert("You won the game, congratulations!");
location.reload();
return;
}
requestAnimationFrame(update);
}
Wir aktualisieren bestehende Effekte, bevor wir den Ball bewegen. So beginnen Animationen, die durch Kollisionen in diesem Frame ausgelöst werden, bei Zeit null. Beim Entfernen abgeschlossener Tweens durchlaufen wir disappearingBricks rückwärts, damit das Entfernen von Einträgen die Indizes noch nicht besuchter Elemente nicht verschiebt. Das Zeichnen erfolgt nach diesen Aktualisierungen und umfasst sowohl intakte als auch verschwindende Ziegelsteine.
Die Siegbedingung wartet nun darauf, dass beide Arrays leer sind. So schrumpft der letzte Ziegelstein vollständig, bevor die Siegesmeldung erscheint. Die vorhandene Bedingung bricks.length > 0 in moveBall() stoppt den Ball, sobald alle Ziegelsteine getroffen wurden, während die Zeichenschleife die Tweens noch zu Ende führt.
Vergleichen Sie Ihren Code
So sollte Ihr Spiel bisher aussehen. Das Beispiel wird live ausgeführt. Klicken Sie auf die Schaltfläche „Play“, um den Quellcode anzuzeigen.
Nächste Schritte
Animationen und Tweens sehen gut aus, aber wir können unser Spiel noch weiter verbessern: Im nächsten Abschnitt beschäftigen wir uns mit der Verarbeitung von Eingaben über Buttons.