201419Nov

THREE.BasicThirdPersonGame: Ein WebGL-Game-Starterkit

Für die englische Version meiner Website habe ich ein kleines WebGL-Spiel entwickelt, bei dem es sich um einfaches Jump 'n' Run-Prinzip im dreidimensionalen Raum handelt. Dabei kommen die Bibliotheken THREE.js (3D-Engine) und Cannon.js (Physik-Engine) zum Einsatz. Während der Entwicklung des Spiels ist die Idee zu einem kleinen Framework gekommen, das jetzt unter dem Namen THREE.BasicThirdPersonGame als Open Source-Projekt bereitsteht.

THREE.BasicThirdPersonGame - Game Starter Kit in THREE.js + Cannon.js

Dabei handelt es sich also um eine Basis für mögliche 3D-WebGL-Browserspiele, bei denen der Spieler aus der Perspektive der dritten Person verfolgt wird. Während THREE.js die Darstellung übernimmt, sind durch Cannon.js physikalisch korrekte Berechnungen möglich, was sich vor allem durch die Verwendung von Starrkörpern (Rigid bodies) und einer Echtzeit-Kollisionserkennung auszeichnet. Eine virtuelle Spielwelt kann dank Rigid bodies mit Eigenschaften wie Gravitation, Reibung und Federung versehen werden.

Das Micro-Framework selbst besteht aus verschiedenen Komponenten, die für entsprechende Bereiche zuständig sind. Eine Übersicht:

  • game.three.js: Szene, Rendering, 3D-Modelle, Cannon.js-Helper
  • game.cannon.js: World, Rigid body-Verwaltung
  • game.core.js: Game-Loop, Spieler-Logik, Level-Logik, Modulverwaltung
  • game.events.js: Eingabesystem für Keyboard-Eingaben
  • game.helpers.js: Math-Umwandlungen, allgemeine Hilfsfunktionen
  • game.ui.js: Benutzeroberfläche
  • game.static.js: Statische Konstanten (wie Farbwerte)

In der Datei game.core.js findet also die eigentliche Spiel-Logik statt, während die restlichen Module in dieser Datei initialisiert und verwendet werden. In der Regel müssen die Module nicht angepasst werden. Dieser Artikel soll nur eine grobe Übersicht über das Framework geben, detaillierte Informationen finden sich in der Dokumentation sowe auf der GitHub-Seite.

Um eine neue Instanz des Cores zu erzeugen, sind folgende Zeilen notwendig (direkt per script-Tag auf der HTML-Seite oder in einer separaten JavaScript-Datei):

  • if (!Detector.webgl) {
  • Detector.addGetWebGLMessage();
  • } else {
  • window.gameInstance = window.game.core();
  • window.gameInstance.init({
  • domContainer: document.querySelector("#game"),
  • rendererClearColor: 0xffffff
  • });
  • }

Bei der Initialisierung werden also der domContainer für die eigentliche Darstellung sowie die Hintergrundfarbe des THREE.js-Renderers (rendererClearColor) festgelegt. Auf die verschiedenen Module soll an dieser Stelle nicht weiter eingegangen werden, da für den Anfang mit den Standard-Einstellungen gearbeitet werden kann. In der game.three.js kann das Sichtfeld der Kamera (fov) angepasst werden, während sich in der game.cannon.js die Konfiguration für die physikalischen Eigenschaften befindet. Wenn andere Tasten als W, A, S, D und SPACE zur Steuerung genutzt werden sollen, muss die Datei game.events.js angepasst werden, da hier die Tastatur-Codes mit denen der game.core.js übereinstimmen müssen. Anschließend kann die eigentlich Spieldatei, game.core.js, mit eigener Spiel-Logik gefüllt werden. Dort finden sich eine init-Funktion und eine destroy-Funktion.

_game.core.init:

  • init: function(options) {
  • _game.initComponents(options);
  •  
  • _game.player.create();
  • _game.level.create();
  •  
  • _game.loop();
  • }

_game.core.destroy:

  • destroy: function() {
  • window.cancelAnimationFrame(_animationFrameLoop);
  •  
  • _cannon.destroy();
  • _cannon.setup();
  • _three.destroy();
  • _three.setup();
  •  
  • _game.player = window.game.helpers.cloneObject(_gameDefaults.player);
  • _game.level = window.game.helpers.cloneObject(_gameDefaults.level);
  •  
  • _game.player.create();
  • _game.level.create();
  •  
  • _game.loop();
  • }

In der init-Methode werden also die verschiedenen Komponenten initialisiert und der Game-Loop wird angestoßen. Die destroy-Methode wird bei einem "Game Over" ausgeführt, also wenn der Spieler beispielsweise eine bestimmte Höhe unterschreitet. Die destroy-Funktion initialisiert die entsprechenden THREE.js- und Cannon.js-Bereiche neu (Scene, World) und anschließend werden die Spieler- und Level-Eigenschaften zurückgesetzt. Hierfür kommt die cloneObject-Hilfsmethode zum Einsatz: Am Ende der game.core.js werden die Objekte _game.player sowie _game.level beim Spielstart einmalig rekursiv kopiert, so dass alle initialen Eigenschaften in einem Objekt-Klon gespeichert sind. Um einen Reset des Spiels zu erreichen, wird den Instanzen _game.player und _game.level schließlich in destroy eine neue Kopie der ursprünglichen Objekte zugewiesen. Dadurch wird ein aufwendiges Zuweisen bzw. Zurücksetzen einzelner Spiel-Variablen überflüssig.

Bevor wir einen letzten Blick auf den primären Inhalt der game.core.js werfen, hier die Aufrufe innerhalb der Funktion _game.loop und _game.player.update.

_game.loop:

  • loop: function() {
  • _animationFrameLoop = window.requestAnimationFrame(_game.loop);
  • _cannon.updatePhysics();
  • _game.player.update();
  • _three.render();
  • }

_game.player.update:

  • update: function() {
  • _game.player.processUserInput();
  • _game.player.accelerate();
  • _game.player.rotate();
  • _game.player.updateCamera();
  • }

Der Game-Loop sorgt also per requestAnimationFrame dafür, dass die physikalische Welt von Cannon.js sowie der Zustand des Spielers aktualisiert werden und schließlich die Darstellung von THREE.js erzeugt wird. Die Aufrufe in der Funktion _game.player.update dürften selbsterklärend sein: Benutzereingaben werden entgegengenommen, die Position und Rotation des Spielers werden festgelegt und schließlich wird die Kamera aktualisiert.

Zuletzt können wir einen Blick auf den Inhalt der game.core.js werfen. Nebensächliche Attribute und Methoden wurden zur Veranschaulichung ausgelassen.

  • window.game.core = function () {
  • var _game = {
  • // Attributes
  • player: {
  • // Attributes
  • speed: 2,
  • speedMax: 65,
  • rotationSpeed: 0.007,
  • rotationSpeedMax: 0.040,
  • damping: 0.9,
  • rotationDamping: 0.8,
  • cameraOffsetH: 280,
  • cameraOffsetV: 180,
  •  
  • // Methods
  • create: function() {},
  • update: function() {},
  • updateCamera: function() {},
  • updateAcceleration: function() {},
  • processUserInput: function() {},
  • accelerate: function() {},
  • rotate: function() {},
  • jump: function() {},
  • updateOrientation: function() {}
  • },
  • level: {
  • // Methods
  • create: function() {}
  • },
  •  
  • // Methods
  • init: function() {},
  • destroy: function() {},
  • loop: function() {},
  • initComponents: function () {}
  • };
  •  
  • return _game;
  • };

Hier werden also Attribute des Spielers festgelegt, die sich auf die Steuerung und die Kamera auswirken. Über cameraOffsetH und cameraOffsetV kann also der horizontale und vertikale Abstand der Kamera zum Spieler definiert werden. Neben den Spieler-Funktionen ist letztendlich die _game.level.create-Methode verantwortlich für das ganze Spielgeschehen. Der Spieler und die Kamera werden durch die Attribute im _game.player-Objekt bewegt, wohingegen Level-Bausteine in der genannten Funktion Platz finden. Hier sind sowohl prozedurale Spielwelten (wie im Platforms-Beispiel) oder fertige Levels möglich. THREE.BasicThirdPersonGame soll nur ein Einstiegspunkt für 3D-Browsergames sein, derartige Features könnten aber beispielsweise über Forks beigesteuert werden.

Zum Abschluss seien noch die Beispiele erwähnt, die einige Funktionen des Micro-Frameworks veranschaulichen sollen. Viel Spaß beim Ausprobieren!