Spieleentwicklung mit JavaScript - Die PlayField-Klasse
Inhaltsverzeichnis |
Die PlayField-Klasse
Inzwischen ist das JavaScript-Programm ein bißchen unübersichtlich geworden. Funktionen mischen sich mit Variablen und Eventhandlern. Bevor wir dieses Chaos erweitern, sollten wir erst ein bißchen aufräumen. Es bietet sich an, die Funktionen, die zur Darstellung des Spielfelds notwendig sind zusammenzufassen.
Exkurs: Klassen in JavaScript
Hier kommt die "objektorientierte Programmierung" (kurz OOP) ins Spiel, die es ermöglicht, zusammengehörende Daten und Funktionen sinnvoll zu strukturieren. JavaScript ist zwar keine objektorientierte Sprache, sie bietet aber ein paar Sprachkonstrukte, die es ermöglichen, einige Ansätze der Objektorientierung umzusetzen.
Ein Beispiel:
function Position( x, y ) { this.x = x; this.y = y; } Position.prototype.getX = function() { return this.x; } Position.prototype.getY = function() { return this.y; } Position.prototype.setXY = function( x, y ) { this.x = x; this.y = y; } var pos = new Position( 10, 20 ); alert( "x: " + pos.getX() + ", y: " + pos.getY() ); pos.setXY( 99, 100 ); alert( "x: " + pos.getX() + ", y: " + pos.getY() );
Die Klassendeklaration sieht aus wie eine ganz normale Funktion. Innerhalb dieser Funktion können Member-Variablen definiert und gleichzeitig initialisiert werden. Die Klassendeklaration dient somit auch gleichzeitig als Konstruktor. Ein Konstruktor einer Klasse ist eine Funktion, die direkt beim Erstellen aufgerufen wird und das Objekt "konstruiert", d.h. alle Member-Variablen initialisiert und andere Initialisierungen durchführt, so dass das Objekt benutzt werden kann.
In diesem Fall erhält die Klasse im Konstruktor x und y, die in den Membervariablen gespeichert werden.
Für die Definition der Methoden gibt es drei unterschiedliche Varianten. Ich empfehle die oben verwendete, da sie am übersichtlichsten ist. Die Alternativen wären, die Methoden direkt im Konstruktor zu definieren oder sie im JSN-Format anzugeben, was ich nur für sehr kleine Klassen empfehle.
Vorüberlegungen
Nun muss entschieden werden, welche Informationen in die PlayField-Klasse aufgenommen werden sollen:
- Die Referenzen auf die HTML-Elemente, die für die Darstellung des Spielfelds notwendig sind
- Alle Variablen, die dynamisch berechnet werden
- Die Tiles, die zum Zeichnen benötigt werden
- Die aktuelle Position des sichtbaren Bereichs
Außerdem wird die drawPlayField-Funktion in die Klasse aufgenommen.
Die Eventhandler und Steuerung wird nicht in die PlayField-Klasse integriert, da sich die Art der Steuerung von Spiel zu Spiel unterscheiden kann. Daher beschränken wir uns bei der Klasse auf die reine Darstellung behalten uns vor, für die Steuerung eigene Klassen zu erstellen, die man dann beliebig mit der PlayField-Klasse kombinieren kann.
Alle statischen Informationen wie die Anzahl der Tiles im Tileset und die Leveldefinition wird im Konstruktor übergeben.
Der PlayField-Konstruktor
function PlayField( playfield_id, tileset_id, tile_count_x, tile_count_y, level ) { this.canvas = document.getElementById( playfield_id ); this.context = this.canvas.getContext("2d"); var imgTileSet = document.getElementById( tileset_id ); this.tiles = new Array(); this.context.drawImage( imgTileSet, 0, 0 ); this.level = level; this.display_pixel_width = this.canvas.width; this.display_pixel_height = this.canvas.height; this.tile_pixel_width = imgTileSet.width / tile_count_x; this.tile_pixel_height = imgTileSet.height / tile_count_y; this.display_tile_width = Math.ceil( this.display_pixel_width / this.tile_pixel_width )+1; this.display_tile_height = Math.ceil( this.display_pixel_height / this.tile_pixel_height )+1; this.map_tile_width = level[0].length; this.map_tile_height = level.length; this.map_pixel_width = this.map_tile_width * this.tile_pixel_width; this.map_pixel_height = this.map_tile_height * this.tile_pixel_height; this.posx = 0; this.posy = 0; for( var y = 0; y < tile_count_y; y++ ) { for( var x = 0; x < tile_count_x; x++ ) { var i = y * tile_count_x + x; var imgData = this.context.getImageData( x * this.tile_pixel_width, y * this.tile_pixel_height, this.tile_pixel_width, this.tile_pixel_height ); this.tiles[i] = document.getElementById("tile"+i); this.tiles[i].width = this.tile_pixel_width; this.tiles[i].height = this.tile_pixel_height; this.tiles[i].getContext("2d").putImageData( imgData, 0, 0 ); } } }
Der für das PlayField relevatnte Code aus der Init()-Funktion wurde nun in den Konstruktor übertragen. Alle Variablen-Zugriffe müssen nun mit this.<variable> angegeben werden.
Der Konstruktor erhält nun folgende Parameter: