Leitfaden für HTML5-Spiele ohne viel Aufwand

Daniel X. Moore
Daniel X. Moore

Einleitung

Sie möchten ein Spiel mit Canvas und HTML5 entwickeln? Wenn Sie die Anleitung in diesem Tutorial befolgen, sind Sie schon bald auf dem besten Weg.

Für dieses Tutorial werden mindestens Vorkenntnisse in JavaScript vorausgesetzt.

Sie können zuerst das Spiel spielen oder direkt zum Artikel springen und sich den Quellcode für das Spiel ansehen.

Canvas erstellen

Um etwas zu zeichnen, müssen wir einen Canvas erstellen. Da dies kein Tears-Leitfaden ist, verwenden wir jQuery.

var CANVAS_WIDTH = 480;
var CANVAS_HEIGHT = 320;

var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
                      "' height='" + CANVAS_HEIGHT + "'></canvas>");
var canvas = canvasElement.get(0).getContext("2d");
canvasElement.appendTo('body');

Spielschleife

Um das flüssige und fortlaufende Gameplay zu simulieren, möchten wir das Spiel aktualisieren und den Bildschirm nur schneller neu zeichnen, als es der menschliche Verstand und das Auge wahrnehmen können.

var FPS = 30;
setInterval(function() {
  update();
  draw();
}, 1000/FPS);

Vorerst können wir die Methoden zum Aktualisieren und Zeichnen leer lassen. Wichtig ist, dass sie von setInterval() regelmäßig aufgerufen werden.

function update() { ... }
function draw() { ... }

Hello World

Jetzt, da eine Spielschleife läuft, aktualisieren wir die Zeichnen-Methode, um Text auf dem Bildschirm zu zeichnen.

function draw() {
  canvas.fillStyle = "#000"; // Set color to black
  canvas.fillText("Sup Bro!", 50, 50);
}

Das ist ziemlich cool für unbeweglichen Text, aber da wir bereits eine Spielschleife eingerichtet haben, sollten wir sie relativ leicht bewegen können.

var textX = 50;
var textY = 50;

function update() {
  textX += 1;
  textY += 1;
}

function draw() {
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

Probieren Sie es aus. Wenn Sie mitmachen, sollte es sich bewegen, aber auch die vorherigen Stellen auf dem Bildschirm beibehalten. Überlegen Sie kurz, warum das der Fall sein könnte. Das liegt daran, dass der Bildschirm nicht leer ist. Also fügen wir der "draw"-Methode einen Code zum Löschen des Bildschirms hinzu.

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

Jetzt, wo sich Text auf dem Bildschirm bewegt, sind Sie auf dem besten Weg, ein echtes Spiel zu haben. Verbessere einfach die Steuerelemente, verbessere das Gameplay und verbessere die Grafik. Vielleicht ist es bis zu einem Siebtel der Zeit bis zu einem echten Spiel, aber die gute Nachricht ist, dass das Tutorial noch viel mehr enthält.

Player erstellen

Erstellen Sie ein Objekt, das die Spielerdaten enthält und beispielsweise für das Zeichnen verantwortlich ist. Hier erstellen wir ein Spielerobjekt mit einem einfachen Objektliteral, das alle Informationen enthält.

var player = {
  color: "#00A",
  x: 220,
  y: 270,
  width: 32,
  height: 32,
  draw: function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  }
};

Wir verwenden für den Moment ein einfaches farbiges Rechteck, das den Spieler darstellt. Wenn wir das Spiel zeichnen, löschen wir den Canvas und zeichnen den Spieler.

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  player.draw();
}

Bedienung über die Tastatur

jQuery-Hotkeys verwenden

Das jQuery-Hotkeys-Plug-in vereinfacht die Tastenverarbeitung in verschiedenen Browsern erheblich. Anstatt über nicht lesbare, browserübergreifende keyCode- und charCode-Probleme zu heulen, können wir Ereignisse so binden:

$(document).bind("keydown", "left", function() { ... });

Es ist ein großer Vorteil, sich keine Gedanken darüber zu machen, welche Schlüssel welche Codes haben. Mit jQuery-Hotkeys lässt sich einfach sagen: „Wenn der Spieler die Nach-oben-Taste drückt, machen Sie etwas“.

Spielerbewegung

Die Art und Weise, wie JavaScript Tastaturereignisse verarbeitet, ist vollständig ereignisgesteuert. Das bedeutet, dass es keine integrierte Abfrage gibt, mit der überprüft werden kann, ob ein Schlüssel inaktiv ist. Daher müssen wir unsere eigene verwenden.

Vielleicht fragen Sie sich, warum Sie nicht einfach eine ereignisgesteuerte Methode zur Verarbeitung von Schlüsseln verwenden sollten. Das liegt daran, dass die Tastaturwiederholungsrate je nach System variiert und nicht an das Timing der Spielschleife gebunden ist. Das Gameplay kann also von System zu System stark variieren. Für eine einheitliche Funktionsweise ist es wichtig, die Tastaturereigniserkennung eng in die Spielschleife zu integrieren.

Die gute Nachricht ist, dass ich einen 16-zeiligen JS-Wrapper eingefügt habe, der die Ereignisabfrage zur Verfügung stellt. Sie heißt key_status.js. Sie können den Status eines Schlüssels jederzeit abfragen, indem Sie z. B. keydown.left prüfen.

Da wir nun abfragen können, ob Tasten gedrückt sind, können wir diese einfache Aktualisierungsmethode verwenden, um den Player zu bewegen.

function update() {
  if (keydown.left) {
    player.x -= 2;
  }

  if (keydown.right) {
    player.x += 2;
  }
}

Probier es aus.

Vielleicht ist dir aufgefallen, dass der Player vom Bildschirm entfernt werden kann. Wir passen die Position des Spielers an, damit er innerhalb des vorgegebenen Rahmens bleibt. Außerdem scheint der Player etwas langsam zu sein, also erhöhen wir auch die Geschwindigkeit.

function update() {
  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

Weitere Eingaben lassen sich genauso einfach hinzufügen, also fügen wir Projektile hinzu.

function update() {
  if (keydown.space) {
    player.shoot();
  }

  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

player.shoot = function() {
  console.log("Pew pew");
  // :) Well at least adding the key binding was easy...
};

Weitere Spielobjekte hinzufügen

Projektile

Fügen wir nun die Geschosse hinzu. Zuerst brauchen wir eine Sammlung, in der wir sie speichern können:

var playerBullets = [];

Als Nächstes benötigen wir einen Konstruktor, um Aufzählungspunkte zu erstellen.

function Bullet(I) {
  I.active = true;

  I.xVelocity = 0;
  I.yVelocity = -I.speed;
  I.width = 3;
  I.height = 3;
  I.color = "#000";

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.active = I.active && I.inBounds();
  };

  return I;
}

Wenn der Spieler schießt, sollten wir eine Geschossinstanz erstellen und zur Geschosssammlung hinzufügen.

player.shoot = function() {
  var bulletPosition = this.midpoint();

  playerBullets.push(Bullet({
    speed: 5,
    x: bulletPosition.x,
    y: bulletPosition.y
  }));
};

player.midpoint = function() {
  return {
    x: this.x + this.width/2,
    y: this.y + this.height/2
  };
};

Nun müssen wir die Aktualisierung der Aufzählungszeichen zur Funktion für den Aktualisierungsschritt hinzufügen. Um zu verhindern, dass die Sammlung von Aufzählungszeichen unbegrenzt gefüllt wird, filtern wir die Aufzählungsliste so, dass sie nur die aktiven Aufzählungszeichen enthält. So können wir auch Kugeln entfernen, die einen Gegner getroffen haben.

function update() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.update();
  });

  playerBullets = playerBullets.filter(function(bullet) {
    return bullet.active;
  });
}

Im letzten Schritt zeichnen Sie die Aufzählungspunkte:

function draw() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.draw();
  });
}

Feinde

Jetzt fügen wir Feinde hinzu, wie wir das bei den Geschossen getan haben.

  enemies = [];

function Enemy(I) {
  I = I || {};

  I.active = true;
  I.age = Math.floor(Math.random() * 128);

  I.color = "#A2B";

  I.x = CANVAS_WIDTH / 4 + Math.random() * CANVAS_WIDTH / 2;
  I.y = 0;
  I.xVelocity = 0
  I.yVelocity = 2;

  I.width = 32;
  I.height = 32;

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.xVelocity = 3 * Math.sin(I.age * Math.PI / 64);

    I.age++;

    I.active = I.active && I.inBounds();
  };

  return I;
};

function update() {
  ...

  enemies.forEach(function(enemy) {
    enemy.update();
  });

  enemies = enemies.filter(function(enemy) {
    return enemy.active;
  });

  if(Math.random() < 0.1) {
    enemies.push(Enemy());
  }
};

function draw() {
  ...

  enemies.forEach(function(enemy) {
    enemy.draw();
  });
}

Bilder laden und zeichnen

Es ist toll, all diese Kartons umherfliegen zu sehen, aber Bilder für sie würden noch cooler machen. Das Laden und Zeichnen von Bildern auf Canvas ist normalerweise ein nervenaufreibender Vorgang. Um diesem Ärger vorzubeugen, können wir eine einfache Dienstprogrammklasse verwenden.

player.sprite = Sprite("player");

player.draw = function() {
  this.sprite.draw(canvas, this.x, this.y);
};

function Enemy(I) {
  ...

  I.sprite = Sprite("enemy");

  I.draw = function() {
    this.sprite.draw(canvas, this.x, this.y);
  };

  ...
}

Kollisionserkennung

Wir fliegen all diese Dealies über den Bildschirm, aber sie interagieren nicht miteinander. Damit alle wissen, wann sie explodieren sollen, benötigen wir eine Art Kollisionserkennung.

Verwenden wir einen einfachen Algorithmus zur Erkennung von rechteckigen Konflikten:

function collides(a, b) {
  return a.x < b.x + b.width &&
         a.x + a.width > b.x &&
         a.y < b.y + b.height &&
         a.y + a.height > b.y;
}

Es gibt ein paar Kollisionen, die wir überprüfen sollten:

  1. Spielerkugeln => Schiffe der Feind
  2. Spieler => Schiffe der Feind

Erstellen wir nun eine Methode zum Verarbeiten der Kollisionen, die wir aus der Aktualisierungsmethode aufrufen können.

function handleCollisions() {
  playerBullets.forEach(function(bullet) {
    enemies.forEach(function(enemy) {
      if (collides(bullet, enemy)) {
        enemy.explode();
        bullet.active = false;
      }
    });
  });

  enemies.forEach(function(enemy) {
    if (collides(enemy, player)) {
      enemy.explode();
      player.explode();
    }
  });
}

function update() {
  ...
  handleCollisions();
}

Nun müssen wir die explode-Methoden für den Spieler und die Feinde hinzufügen. Dadurch werden sie zum Entfernen gekennzeichnet und eine Explosion hinzugefügt.

function Enemy(I) {
  ...

  I.explode = function() {
    this.active = false;
    // Extra Credit: Add an explosion graphic
  };

  return I;
};

player.explode = function() {
  this.active = false;
  // Extra Credit: Add an explosion graphic and then end the game
};

Ton

Um das Ganze abzurunden, werden wir noch ein paar nette Soundeffekte hinzufügen. Die Verwendung von Tönen in HTML5 ist wie bei Bildern mühsam, aber dank unserer genialen Formel „sound.js“ ist der Ton ganz einfach.

player.shoot = function() {
  Sound.play("shoot");
  ...
}

function Enemy(I) {
  ...

  I.explode = function() {
    Sound.play("explode");
    ...
  }
}

Die API ist jetzt zwar reißerlos, aber das Hinzufügen von Tönen ist derzeit die schnellste Möglichkeit, Ihre Anwendung zum Absturz zu bringen. Es ist nicht ungewöhnlich, dass Töne den gesamten Browser-Tab ausschneiden oder entfernen, also bereite dein Gewebe vor.

Auf Wiedersehen

Hier ist noch einmal die vollständige Spieldemo. Sie können auch den Quellcode als ZIP-Datei herunterladen.

Ich hoffe, dass Ihnen die Grundlagen der Erstellung eines einfachen Spiels mit JavaScript und HTML5 gefallen haben. Durch die Programmierung auf der richtigen Abstraktionsebene können wir uns von den schwierigeren Teilen der APIs isolieren und auch auf zukünftige Änderungen reagieren.

Verweise