JavaScriptsSpiele

BreakOut

Der Arkade-Klassiker: Mit dem Schläger den Ball ins Steinfeld lenken, bis alle vierzig Steine abgeräumt sind.

BreakOut kennt fast jeder — mal unter diesem Namen, mal als Arkanoid oder schlicht als „Steinchen abräumen“. Ein Ball springt über ein Spielfeld, ihr fangt ihn unten mit einem Schläger auf, und jeder Treffer oben nimmt einem der vierzig Steine eine Farbstufe weg. Erst nach fünf Treffern ist ein Stein endgültig verschwunden — bis dahin wandert er von Gelb über Orange nach Rot.

Bedient wird das Spiel allein mit der Maus: Der Schläger folgt eurem Mauszeiger, ein Klick auf START schickt den Ball los. Rechts neben dem Feld seht ihr, wie viele Bälle euch noch bleiben und wie lange ihr schon spielt. Mit dem Knopf darunter schaltet ihr das Tempo um, wenn euch der Ball zu flott oder zu zäh unterwegs ist. Die erste Runde bekommt ihr fünf Bälle geschenkt, jede weitere startet mit drei.

Interessant ist die Physik am Schläger: Wo der Ball auftrifft, entscheidet über den Abflugwinkel. In der Mitte kommt er fast senkrecht zurück, an den Außenkanten sehr flach und schnell zur Seite. Das ist beim Original von 1976 nicht anders gewesen und macht den Reiz des Spiels aus — mit dem Schlägerrand lenkt man den Ball gezielt in die Ecken, wo er sich dann durch mehrere Steinreihen arbeitet.

Das Script stammt ursprünglich von Nick Young; wir haben es seinerzeit browsertauglich gemacht und jetzt gründlich entstaubt. Es kommt weiterhin ohne eine einzige Bilddatei aus — Feld, Steine, Ball und Schläger sind schlichte farbige div-Elemente, die per JavaScript verschoben werden.

Wenn euch das Spielprinzip gefällt, findet ihr in derselben Kategorie noch Pong, das Ballspiel und Asteroids; wer es ruhiger mag, probiert Snake.

Spiele Baujahr 2004 läuft in deinem Browser

ALL CLEAR!
GAME OVER!

Der Schläger folgt der Maus. START beginnt eine Runde — die erste mit fünf Bällen, jede weitere mit drei.

Script von Nick Young

So funktioniert das Script

Das Script besteht aus fünf Teilen: Es baut das Steinfeld auf, lässt eine Spielschleife im Takt von wenigen Millisekunden laufen, verschiebt darin den Ball, prüft die Zusammenstöße mit Wänden, Schläger und Steinen und hält schließlich Bälle und Zeit nach. Alles steckt in einer sofort ausgeführten Funktion, damit die vielen Zählvariablen nicht mit anderen Scripts auf der Seite kollidieren.

Das Steinfeld

for (let ib = 0; ib < 5; ib++) {
  for (let ia = 0; ia < 8; ia++) {
    steine += "<div id='bo-b" + (ib * 8 + ia) + "' style='background-color:" + blcol[ib] +
      "; width:42px; height:16px; position:absolute; top:" + (48 + 20 * ib) +
      "px; left:" + (29 + 46 * ia) + "px'></div>";
    blsta[ib * 8 + ia] = ib;
  }
}

Zwei ineinander geschachtelte Schleifen erzeugen fünf Reihen zu je acht Steinen. ib ist die Reihe, ia die Spalte; aus beiden entsteht mit ib * 8 + ia eine fortlaufende Nummer von 0 bis 39. Die Position ergibt sich aus dem Raster: jede Reihe liegt 20 Pixel tiefer als die vorige (48 + 20 * ib), jede Spalte 46 Pixel weiter rechts (29 + 46 * ia). Genau diese beiden Formeln tauchen später in der Kollisionsprüfung wieder auf — nur rückwärts gerechnet.

Das Feld blsta merkt sich für jeden Stein, wie oft er schon getroffen wurde: 0 heißt „unberührt“, 5 heißt „weg“. Die Farbe dazu steht in blcol, wobei blcol[5] Schwarz ist — der Stein verschwindet also nicht wirklich, er nimmt nur die Farbe des Hintergrunds an.

Die Spielschleife

function mainF() {
  clearTimeout(timerID);
  tim = tim + 1;
  feldZeit.value = Math.floor(tim / 10);
  ballX = ballX + ballDX;
  ballY = ballY + ballDY;
  outCHK();
  blkCHK();
  ball.style.top = ballY + "px";
  ball.style.left = ballX + "px";
  schlaeger.style.left = tmpRL + "px";
  if (gameFLG === 1) {
    timerID = setTimeout(mainF, INT);
  }
}

mainF() ist das Herz des Spiels und zeichnet ein einzelnes Bild. Zuerst wird der Ball um seine Geschwindigkeit weitergeschoben — ballDX und ballDY sind die Schrittweiten pro Bild in x- und y-Richtung. Dann prüfen outCHK() und blkCHK(), ob er dabei irgendwo angestoßen ist, und korrigieren Position und Richtung. Erst danach werden Ball und Schläger tatsächlich an ihre neue Stelle gesetzt.

Am Ende ruft sich die Funktion über setTimeout(mainF, INT) selbst wieder auf — aber nur, solange gameFLG auf 1 steht. Setzt das Spielende diese Variable auf 0, läuft das nächste Bild nicht mehr an und die Schleife endet von selbst. INT ist der Abstand in Millisekunden: kleiner heißt schneller. Der Tempoknopf tauscht diesen Wert zwischen 10 und 100 — wer lieber eine andere Grundgeschwindigkeit möchte, ändert einfach die Startzeile let INT = 30;.

Steuerung mit der Maus

function MouseMv(e) {
  const rahmen = buehne.getBoundingClientRect();
  X = e.clientX - rahmen.left;
  if (loadFLG === 1) {
    tmpRL = X - 20;
    if (tmpRL < 16) { tmpRL = 16; }
    if (tmpRL > 370) { tmpRL = 370; }
  }
}

Der Browser meldet mit e.clientX die Mausposition im Fenster. Interessant ist aber die Position innerhalb des Spielfelds. getBoundingClientRect() liefert die Maße und die Lage eines Elements im Fenster; zieht man deren linke Kante ab, bleibt der Abstand zum Spielfeldrand übrig. Früher stand hier ein fester Zahlenwert, der nur zum damaligen Seitenlayout passte — so gerechnet sitzt der Schläger jetzt überall richtig.

Die beiden if-Zeilen halten den Schläger im Feld: Er darf nicht weiter nach links als 16 und nicht weiter nach rechts als 370 Pixel. Das - 20 davor sorgt dafür, dass die Maus in der Mitte des 40 Pixel breiten Schlägers sitzt und nicht an seiner linken Kante.

Wände und Schläger

if (ballX < 16) { ballX = 32 - ballX; ballDX = -ballDX; }
if (ballX > 401) { ballX = 802 - ballX; ballDX = -ballDX; }
if (ballY < 16) { ballY = 32 - ballY; ballDY = -ballDY; }

Das ist die gesamte Wandphysik. Ist der Ball über die linke Kante (16 Pixel) hinausgeschossen, wird sein Richtungsvorzeichen umgedreht — aus „nach links“ wird „nach rechts“. Damit er nicht in der Wand stecken bleibt, wird er zugleich um genau den Betrag zurückgespiegelt, um den er zu weit war: 32 - ballX ist die Spiegelung an der Linie 16. Oben und rechts funktioniert es genauso, nur mit anderen Grenzen.

ballRD = tmpX - tmpRL;
ballDX = 8 * Math.abs(ballDX) / ballDX;
if (ballRD < -4) { ballDX = -15; }
if (ballRD > 36) { ballDX = 15; }
if (ballRD >= 14) { if (ballRD <= 16) { ballDX = -2; } }

Unten wird es spannender. ballRD ist der Abstand vom linken Schlägerrand — also die Stelle, an der der Ball auftrifft. Die zweite Zeile setzt die waagerechte Geschwindigkeit zunächst auf 8 und behält dabei nur das Vorzeichen bei (Math.abs(x) / x ergibt +1 oder −1). Danach wird sie je nach Trefferstelle überschrieben: ganz außen ±15, in der Mitte nur ±2. So kommt der Ball aus der Schlägermitte fast senkrecht zurück und von der Kante flach zur Seite. Wer das Spiel leichter machen möchte, verkleinert diese Zahlen.

Verfehlt ihr den Ball, merkt sich missFLG das. Erst wenn er danach unter 290 Pixel fällt — also wirklich aus dem Feld ist — wird ein Ball abgezogen und gameEnd() gerufen.

Treffer am Stein

ia = Math.floor((tmpX - 29) / 46);
ib = Math.floor((tmpY - 48) / 20);
ic = ib * 8 + ia;
if (blsta[ic] <= 4) {
  tmpbc = blsta[ic] + 1;
  blsta[ic] = tmpbc;
  chc(ic + 1, tmpbc);
  if (tmpbc === 5) { blclr = blclr + 1; }
}

Statt alle vierzig Steine der Reihe nach abzufragen, rechnet das Script aus der Ballposition direkt aus, welcher Stein getroffen sein muss — das ist der umgekehrte Weg zum Aufbau des Feldes. Math.floor() schneidet die Nachkommastellen ab, so wird aus „2,7 Spalten rechts vom Rand“ die Spalte 2. Diese Rechnung ist unabhängig von der Anzahl der Steine immer gleich schnell.

Steht der Stein noch (blsta[ic] <= 4), wird sein Zähler erhöht und chc() färbt ihn neu. Erreicht der Zähler 5, ist der Stein abgeräumt und blclr wächst; bei 40 ist das Feld leer und die Runde vorbei. Der Rest der Funktion entscheidet nur noch, ob der Ball oben, unten oder seitlich angestoßen ist, und dreht entsprechend ballDY oder ballDX um.

Bälle, Zeit und Ende

function gameEnd() {
  feldBaelle.value = ballN;
  gameFLG = 0;
  loadFLG = 0;
  zeigen(starter, 200, 180);
  if (blclr >= 40) { zeigen(geschafft, 150, 160); }
  if (ballN <= 0) { zeigen(ende, 150, 160); blclr = 40; }
}

gameEnd() stoppt die Schleife, indem es gameFLG auf 0 setzt, und holt den START-Knopf wieder ins Bild. Die Meldungen „ALL CLEAR!“ und „GAME OVER!“ sind ganz gewöhnliche div-Elemente, die die übrige Zeit auf Position −1000 Pixel geparkt sind — ein alter, aber wirkungsvoller Trick, um etwas aus dem sichtbaren Bereich zu schieben. Die Hilfsfunktionen zeigen() und verstecken() erledigen genau das.

Anpassen lässt sich hier einiges ohne Risiko: die Farben in blcol, die Zahl der Bälle (let ballN = 5; und das ballN = 3; für die Folgerunden) sowie die Texte der beiden Meldungen im HTML.

Script für die eigene Homepage

Kopiert euch den kompletten Code und fügt ihn an der Stelle eurer Seite ein, an der das Script erscheinen soll. Er läuft ohne weitere Dateien und ohne fremde Server.

Mehr aus der Kategorie Spiele