Softwareentwicklung und Managed Hosting
ANEXIA
JUN
17
2016

Phaser Tutorial: HTML5 Rennspiel

Geschrieben am  17. Juni 2016 von Daniel Wuggenig

Phaser bietet Einsteigern in der Spieleentwicklung die Möglichkeit, HTML5 basierte Spiele zu kreieren. In diesem Tutorial möchte ich den Einstieg in Phaser anhand eines Rennspiels zeigen. Den gesamten Source Code könnt ihr am Ende des Posts finden. Wie das fertige Rennspiel in meinen Fall ausschaut, könnt ihr hier ansehen: Anexia Schlepperacer

 

1. Einführung

Um Phaser in ein eigenes Projekt einzubinden, kann man es entweder von der offiziellen Seite downloaden, oder ein CDN verwenden.

Ich habe in meinen Fall ein CDN verwendet, somit reicht es folgendes Script Tag in das eigene Projekt einzubinden:

<script  src="https://cdnjs.cloudflare.com/ajax/libs/phaser/2.4.8/phaser.min.js"></script>

Achtung: Dieses Script Tag bindet Phaser Version 2.4.8 ein. Checkt bitte zuerst, ob es bereits eine neuere Version gibt!

Ein Phaser Spiel besteht grundsätzlich aus 3 Funktionen: preload, create & update.

Die preload-Funktion ist der Eintrittspunkt des Spiels und wird als erste aufgerufen. Sie ist, wie der Name schon sagt, dafür da, Dateien wie Grafiken, Bounding Boxes, Schriften, Scripts, Sounds und Ähnliches zu laden. Eine typische Codezeile in der Preload-Funktion sieht zum Beispiel so aus:

game.load.spritesheet('map','assets/map.jpg');

 

Die create-Funktion wird nach der preload-Funktion aufgerufen. Hier hat man die Möglichkeit, einmaligen Code auszuführen. Der wird erst aufgerufen, wenn bereits alle anderen Daten zur Verfügung stehen. Hier spielt sich schon einiges ab. Man fügt typischerweise die meisten Grafiken, die in der preload-Funktion geladen wurden zum Spiel hinzu, mapt die Bounding Boxes zu Grafiken, initialisiert und konfiguriert Partikeleffekte und Animationen, schreibt die Physik, setzt Texte oder ladet die Keys der Tastatur. Das ist notwenig, um später abfragen zu können, ob diese gedrückt sind.

In der update-Funktion findet dann die richtige Action statt. Diese wird beim Rendern eines jeden Frames aufgerufen. Hier werden User Inputs & Kollisionen gehandelt, die Sprites bewegt, usw.

Um ein Phaser Spiel schließlich zu intialisieren, muss man nur folgende Zeile JavaScript ins Projekt einbinden:

var game = new Phaser.Game(1280, 839, Phaser.AUTO, 'main_game', { preload: preload, create: create, update: update });

Die ersten zwei Parameter geben die Höhe und Breite des Spiels in Pixel an. Man kann die Größe aber auch nachträglich ändern, um das Spiel auf verschiedene Auflösungen anzupassen, ohne das sich die Sprites im Spiel ändern.

Mit den 3. Parameter kann man einstellen, wie das Spiel gerendert werden soll (WebGL oder Canvas). Normalerweise setzt man dies auf Phaser.AUTO, das WebGL verwendet, falls es verfügbar ist. Sonst wird auf Canvas.

Der 4. Parameter ist die ID des Div, in dem sich das HTML Element des Spiels befinden soll. Man kann diesen Parameter auch leer lassen, solange keine HTML properties benötigt werden.

Im letzten Parameter werden dann noch unsere preload, create und update Funktionen an Phaser übergeben. (Lasst die 3 vorerst einfach leer)

 

2.  Auto & Map initialisieren

Für unser Rennspiel benötigen wir zuerst eine Map auf der das Auto fahren kann, und natürlich ein Auto. Ab hier wird ein Webserver verwendet, da der Browser nicht berechtigt ist, auf lokale Dateien am PC zuzugreifen. Also am einfachsten XAMPP oder je nach System und Geschmack ähnliches installieren.

Hier ist die von mir verwendete Map mit unserem Bürogebäude:

map

Das Auto ist unserem Anexia Lieferwagen nachempfunden:

car

Um die Grafiken in unser Spiel zu laden, müssen wir sie zuerst in unserer preload Methode laden:

function preload() {
    game.load.spritesheet('map','assets/map.jpg');
    game.load.spritesheet('car','assets/car.png');
}

Der erste Parameter kann beliebig gewählt werden. Er sorgt dafür, dass später auf die Grafik zugegriffen werden kann.

Der zweite Parameter gibt den Pfad zur Datei an.

Nun ist die Grafik geladen, wir müssen sie aber noch ins Spiel einbinden. Dies machen wir in der create-Funktion:

function create() {
    /*Adding Map*/
    var map = game.add.sprite(0,0,'map');
    /*Adding car*/
    car = game.add.sprite(570,100,'car');
}

Die ersten 2 Parameter hier geben die Koordinaten (X,Y) an, wo die Grafik plaziert werden soll, der 3. gibt an wie sie heißt. Der Name muss der gleiche sein, wie der, in der preload-Funktion verwendete.

Führt man nun das Spiel aus, sieht man das Auto mit der Map als Hintergrund.

Als nächstes gehen wir zur Steuerung des Autos.

 

3. Auto Steuerung

Damit wir wissen in welche Richtung das Auto fahren soll, müssen wir zuerst die Eingaben auf der Tastatur abfragen. Hierfür müssen wir zuerst Phaser sagen, von welchen Tasten wir Daten brauchen.

Legen wir zuerst eine globale Variable „cursors“ an:

var cursors;

Mit folgender Zeile können wir Phaser in der create-Funktion sagen, dass wir alle „Cursor-Keys“ (also alle Pfeiltasten) benötigen:

cursors = game.input.keyboard.createCursorKeys();

Anschließend können wir in der update-Funktion mit z.B.

if (cursors.up.isDown) {
}

abfragen, ob die Pfeil-oben Taste gedrückt ist.

Um den Auto nun eine Geschwindigkeit zu geben, müssen wir eine Physik Engine einschalten. Phaser kommt mit mehreren Physik Engines. Am häufigsten sieht man im Internet die Arcade Engine, welche wir aber nicht verwenden werden, da diese keine komplexen Bounding Boxes unterstützt. Die P2 Engine unterstützt diese. Um sie zu starten, fügen wir folgende Zeile in der Create-Funktion hinzu:

game.physics.startSystem(Phaser.Physics.P2JS);

Jetzt ist die Physiks Engine gestartet, wir müssen ihr aber noch sagen, dass unser Auto Teil der Engine sein wird:

game.physics.p2.enable(car,false);

Jetzt drehen wir erstmal das Auto so, dass es entlang der Strecke steht:

car.body.angle = 90;

Jetzt wird es endlich Zeit dem Auto eine Geschwindigkeit zu geben. Legen wir hierfür eine globale Variable an die zu Beginn 0 ist:

var velocity = 0;

Jetzt geht es an den Code der das Auto steuert:

if (cursors.up.isDown && velocity <= 400)
        velocity+=7;
else {
    if (velocity >= 7)
        velocity -= 7;
}
                        
car.body.velocity.x = velocity * Math.cos((car.angle-90)*0.01745);
car.body.velocity.y = velocity * Math.sin((car.angle-90)*0.01745);
                
if (cursors.left.isDown)
    car.body.angularVelocity = -5*(velocity/1000);
else if (cursors.right.isDown)
    car.body.angularVelocity = 5*(velocity/1000);
else
    car.body.angularVelocity = 0;

Das Ganze ist eigentlich ganz einfach:

if (cursors.up.isDown && velocity <= 400)
        velocity+=7;
else {
    if (velocity >= 7)
        velocity -= 7;
}

Ist die Pfeil-oben Taste gedrückt und die Geschwindigkeit kleiner als 400 Pixel/Sekunde (maximale Geschwindigkeit) , wird sie erhöht. Ist die Taste nicht gedrückt, wird das Auto langsamer.

Dannach wird aus der Geschwindigkeit berechnet, wie groß die Geschwindigkeit auf der X und Y Achse ist:

car.body.velocity.x = velocity * Math.cos((car.angle-90)*0.01745);
car.body.velocity.y = velocity * Math.sin((car.angle-90)*0.01745);

Zuerst zieht man vom Winkel des Auto 90 ab. (Wir haben es ja vorhin um 90 Grad gedreht, für das Programm ist die Front des Autos sonst auf der rechten Seite.) Dadurch haben wir den echten Winkel des Autos, welchen wir dann in Radiant umwandeln (*0.01745). Nehmen wir davon die Cosinus Funktion, erhalten wir davon den X-Vector, der angibt, wieweit wir das Auto auf der X-Achse verschieben müssen. Diesen Multiplizieren wir dann mit der gesamten Geschwindigkeit und setzen dies als X Geschwindigkeit.

Die Berechnung für die Y-Achse funktioniert gleich, nur mit der Sinus Funktion.

Dannach setzten wir noch die Rotation des Autos:

if (cursors.left.isDown)
    car.body.angularVelocity = -5*(velocity/1000);
else if (cursors.right.isDown)
    car.body.angularVelocity = 5*(velocity/1000);
else
    car.body.angularVelocity = 0;

Die Geschwindigkeit der Rotation ist abhängig von der Geschwindigkeit des Autos, ist das Auto langsam, lenkt es auch langsam.

Da der Code mittlerweile schon etwas komplexer geworden ist, hier nochmal der ganze:

<html>
    <head>
        <title>Tutorial</title>
        <script  src="https://cdnjs.cloudflare.com/ajax/libs/phaser/2.4.8/phaser.min.js"></script>
    </head>
    <body>
        <script>
            
            var game = new Phaser.Game(1280, 839, Phaser.AUTO, 'main_game', { preload: preload, create: create, update: update });
            
            function preload() {
                game.load.spritesheet('map','assets/map.jpg');
                game.load.spritesheet('car','assets/car.png');
            }
            
            var cursors;
            var velocity = 0;
            function create() {
                /*Enable Phyics Engine*/
                game.physics.startSystem(Phaser.Physics.P2JS);
                /*Adding Map*/
                var map = game.add.sprite(0,0,'map');
                /*Adding car*/
                car = game.add.sprite(570,100,'car');
                game.physics.p2.enable(car);
                car.body.angle = 90;
                
                cursors = game.input.keyboard.createCursorKeys();
            }
            
            function update()
            {
                /*Update Velocity*/
                if (cursors.up.isDown && velocity <= 400) {
                        velocity+=7;
                }
                else {
                    if (velocity >= 7)
                        velocity -= 7;
                }
                        
                /*Set X and Y Speed of Velocity*/
                car.body.velocity.x = velocity * Math.cos((car.angle-90)*0.01745);
                car.body.velocity.y = velocity * Math.sin((car.angle-90)*0.01745);
                
                /*Rotation of Car*/
                if (cursors.left.isDown)
                    car.body.angularVelocity = -5*(velocity/1000);
                else if (cursors.right.isDown)
                    car.body.angularVelocity = 5*(velocity/1000);
                else
                    car.body.angularVelocity = 0;
            }
        </script>
    </body>
</html>

4. Kollisionen

Als nächstes kümmern wir uns um die Kollisionserkennung. Hierfür habe ich den Physics Editor verwendet. Ich werde hier nicht genauer auf die Bedienung eingehen, es gibt da bereits relativ viele Informationen im Netz. Der Physics Editor generiert eine JSON Datei, welche alle Bounding Boxes beinhaltet. Diese Datei laden wir in der Preload-Funktion mit:

game.load.physics("collision","assets/collision.json");

Ich werde hier das Erzeugen einer Kollision mit dem Gebäude in der Mitte erklären, mit den restlichen Objekten funktioniert es aber ähnlich.

Da das Gebäude dreidimensional aus der Map sticht, wollte ich, dass das Auto „unter“ das Ge bäudefahren kann. Das Ganze sollte ungefähr so aussehen:

car Under Building

 

Dafür schneidet man das Bild dementsprechend aus, und speichert es als PNG Datei mit transparenten Hintergrund ab. Wir fügen nun das Gebäude gleich wie die anderen Grafiken am Ende der create-Funktion hinzu. Damit die Grafik des Autos unter der des Gebäudes liegt, müssen wir die Grafik des Gebäudes nach der des Autos hinzufügen.

Zuerst müssen wir Collision Groups für die Objekte erzeugen:

var carCollisionGroup = game.physics.p2.createCollisionGroup();
var buildingCollisionGroup = game.physics.p2.createCollisionGroup();
game.physics.p2.updateBoundsCollisionGroup();

Dann fügen wir das Gebäude hinzu (nachdem wir es in der preload-Funktion geladen haben):

var building = game.add.sprite(640,420,'building');

Wir schalten die Physik-Engine für das Gebäude ein:

game.physics.p2.enable(building);

Dann stellen wir noch ein, dass das Gebäude kein bewegliches Objekt ist:

building.body.kinematic = true;

Wir löschen die Bounding Box die standardmäßig auf den Gebäude liegt:

building.body.clearShapes();

Und laden unsere eigene Bounding Box aus der vom Physics Editor exportierten Datei:

building.body.loadPolygon('collision','building');

Jetzt sagen wir noch, welchen Collision Groups das Auto und das Gebäude angehören:

car.body.setCollisionGroup(carCollisionGroup);
building.body.setCollisionGroup(buildingCollisionGroup);

Und schlussendlich sagen wir, dass das Gebäude und das Auto kollidieren sollen:

car.body.collides([carCollisionGroup,buildingCollisionGroup]);
building.body.collides([buildingCollisionGroup,carCollisionGroup]);

Hinweis: Habt ihr Probleme mit der Bounding Box, versucht folgendes:

game.physics.p2.enable(building,true);

Gebt ihr hier „true“ als 2. Parameter mit, wird die Bounding Box des Gebäudes angezeigt. Das hilft beim Debuggen. 😉

Habt ihr alles richtig gemacht, solltet ihr jetzt das Grundgerüst für euer eigenes Rennspiel haben!

Falls nicht, gibt es hier nochmal das gesamte Spiel zum Download: HTML5 Racing Game

Falls ihr noch Fragen habt, schreibt mir einfach eine E-Mail.

Ihr wollt wissen, was in meinem Fall aus dem Spiel geworden ist?

Besucht doch einfach den Anexia Schlepperacer auf Facebook und seht es euch an, bis 1. Juli 2016 könnt ihr dort ein iPad Air 2 gewinnen!

Ihr wollt eure eigene Social Media Applikation, aber seit euch nicht sicher wie man den Weg dorthin findet?

Wir von Anexia realisieren liebend gerne eure Social Media Applikationen, besucht uns einfach hier.