Kleiner JavaScript Kurs

Spiele - Entwicklung mit dem JavaScript Framework Phaser 3

Phaser ist eine umfangreiche JavaScript Bibliothek zur Spiele - Entwicklung. Phaser ist sehr gut dokumentiert und es gibt auf den Phaser Internetseiten einige Tutorials und viele Beispiele. Hier eine kleine Einführung (Tutorial "Making your first Phaser 3 game")
Im head der HTML - Seite muss als erstes die JavaScript Bibliothek phaser.min.js eingebunden werden.

<head> 
    <meta charset="UTF-8" />
    <title>Making your first Phaser 3 Game</title>
    <script src="js/phaser.min.js"></script>
    <style type="text/css">
        body {
            margin: 0;
        }
    </style>
</head>

Das Programmgerüst (im body) sieht dann so aus:
<body>
  <script>
      var config = {
          type: Phaser.AUTO,
          width: 800,
          height: 600,
          scene: {
              preload: preload,
              create: create,
              update: update
          }
      };

      var game = new Phaser.Game(config);

      function preload (){   }
      function create (){   }
      function update (){   }

  </script>
</body>
In der Variablen config werden einige Grundeinstellungen definiert (z.B. die Breite und Höhe des Spielefensters). Dann wird ein neues Phaser - Objekt mit dem Namen game erzeugt.
Mit den 3 Funktionen preload, create und update werden dann die Spielelemente geladen und die Abläufe gesteuert.
Sehen wir uns zunächst die Funktionen preload und create an.
function preload (){
  this.load.image('sky', 'images/phaser/sky.png');
  this.load.image('ground', 'images/phaser/platform.png');
  this.load.image('star', 'images/phaser/star.png');
  this.load.image('bomb', 'images/phaser/bomb.png');
  this.load.spritesheet('dude', 'images/phaser/dude.png', {frameWidth: 32, frameHeight: 48});
}

function create (){
  this.add.image(400, 300, 'sky');
  this.add.image(200, 100, 'star');
}
In der Funktion preload werden alle Bilder und Sprites (und weitere Elemente) geladen. Mit der Funktion create werden die Bilder dann angezeigt. Die angegebenen Koordinaten geben die x und y Werte der Bildmitte an (400, 300 ist also genau in der Bildmitte).

Wir wollen als nächstes die "Physik" - Funktionen von Phaser verwenden (für Bewegungen, feste Gegenstände wie Wände, usw.). Als erstes müssen wir physics in die Variable config einbauen.
var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
          default: 'arcade',
          arcade: {
              gravity: { y: 300 },
              debug: false
          }
      },
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};
Jetzt können wir Funktionen aus der "Physik - Bibliothek" in der create Funktion verwenden:
function create (){
  this.add.image(400, 300, 'sky');
  platforms = this.physics.add.staticGroup();
  platforms.create(400, 568, 'ground').setScale(2).refreshBody();
  platforms.create(600, 400, 'ground');
  platforms.create(50, 250, 'ground');
  platforms.create(750, 220, 'ground');
}

Damit haben wir unsere Spielfläche mit Plattformen und einem Boden versehen.
In der Spiele - Physik gibt es zwei Typen von Bereichen:
  • Statisch (fest, z.B. Wände, Boden, Plattformen)
  • Dynamisch (beweglich, veränderbar, z.B. Spielfiguren, herabfallende Gegenstände, usw.)
Wir haben eine statische Gruppe von Elementen eingefügt.
platforms = this.physics.add.staticGroup();
Für alle Elemente verwenden wir das Bild "platform.png" (ein grünes Rechteck, 400 x 32 Pixel groß), dem wir im proload Bereich den Namen ground gegebeen haben. Für den Boden vergrößern wir das Rechteck um den Faktor 2 (-> neu 800 x 64 Pixel)
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
Als nächstes ergänzen wir den create - Bereich um eine Spielfigur.

player = this.physics.add.sprite(100, 450, 'dude');
player.setBounce(0.2);
player.setCollideWorldBounds(true);

this.anims.create({
  key: 'left',
  frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
  frameRate: 10,
  repeat: -1
});

this.anims.create({
  key: 'turn',
  frames: [ { key: 'dude', frame: 4 } ],
  frameRate: 20
});

this.anims.create({
  key: 'right',
  frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
  frameRate: 10,
  repeat: -1
});

Die Spielfigur fällt aber noch komplett nach unten; der Grund: wir testen noch nicht auf eine Kollision mit dem Boden. Das beheben wir mit einer einzigen Zeile Code:
this.physics.add.collider(player, platforms);
Bevor wir die Spielfigur steuern können benötigen wir noch 3 weitere globale Variable:
var player;
var platforms;
var cursors;

var game = new Phaser.Game(config);
In die create Funktion nehmen wir jetzt die Tastatursteuerung auf:
cursors = this.input.keyboard.createCursorKeys();
this.physics.add.collider(player, platforms);
Durch die Funktion createCursorKeys werden die 4 Cursortasten left, right, up und down aktiviert. Die eigentliche Tastatursteuerung programmieren wir in der update Funktion:
function update (){
    if (cursors.left.isDown){
        player.setVelocityX(-160);
        player.anims.play('left', true);
    }
    else if (cursors.right.isDown){
        player.setVelocityX(160);
        player.anims.play('right', true);
    }
    else {
        player.setVelocityX(0);
        player.anims.play('turn');
    }
    if (cursors.up.isDown && player.body.touching.down){
        player.setVelocityY(-330);
    }
    }
Für left und right setzen wir die Geschwindigkeit in x - Richtung auf -160 bzw 160 und aktivieren die Animation nach rechts bzw. links. Wird keine Taste gedrückt ist die x - Geschwindigkeit 0 und die Spielfigur schaut uns an. Beim Drücken der up - Taste springt die Spielfigur nach oben (y - Geschwindigkeit -330). Das Springen funktioniert jedoch nur, wenn die Spielfigur auf einer Plattform steht (&& player.body.touching.down).
Als nächstes werden die Sterne hinzugefügt (am Ende der Funktion create). Insgesamt fügen wir 12 Sterne (1 + 11 Wiederholungen) am oberen Rand (y=0) ein. Der x - Abstand zwischen den Sternen beträgt 70 Pixel. Damit die Sterne nicht durch die Plattformen fallen ist eine Kollisionerkennung zwischen Sternen und Plattformen vorhanden (this.physics.add.collider(stars, platforms);). Außerdem soll festgestellt werden, ob die Spielfigur einen Stern berührt (this.physics.add.overlap(player, stars, collectStar, null, this);). Bei Berührung wird die Funktion collectStar (sammle den Stern ein) ausgeführt.
stars = this.physics.add.group({
  key: 'star',
  repeat: 11,
  setXY: { x: 12, y: 0, stepX: 70 }
});

stars.children.iterate(function (child) {
  child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
});

this.physics.add.collider(stars, platforms);
this.physics.add.overlap(player, stars, collectStar, null, this);
function collectStar (player, star){
    star.disableBody(true, true);
}

Beim Einsammeln der Sterne sollen Punkte gezählt werden. Dazu benötigen wir 2 weitere globale Variable (am Anfang des Scripts). In der Variablen score werden die Punkte gezählt; die Variable scoreText enthält die Punkte - Anzeige.
var score = 0;
var scoreText;
Am Ende der create Funktion wird die Textanzeige für die Punktezählung eingerichtet
scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
Damit beim "Einsammeln" eines Sterns 10 Punkte gezählt werden, ergänzen wir die collectStar Funktion um 2 Zeilen.
score += 10;
scoreText.setText('Score: ' + score);
Damit das Spiel spannender wird, können weitere Level eingebaut werden. Nach dem Einsammeln aller Sterne erscheint eine Bombe im Spiel, die der Spieler nicht berühren darf (sonst ist das Spiel beendet). Hier das gesamtte Spiel im Quellcode. Finde selbst heraus, wie es funktioniert und versuche Dinge zu ändern (z.B. mehr Punkte je Level, was passiert, wenn die Bombe mit einem Stern collidiert, usw.)
<!doctype html> 
<html lang="de"> 
  <head> 
      <meta charset="UTF-8" />
      <title>Making your first Phaser 3 Game</title>
      <script src="js/phaser.min.js"></script>
      <style type="text/css">
          body {
              margin: 0;
          }
      </style>
  </head>
  <body>
  <script>
      var config = {
          type: Phaser.AUTO,
          width: 800,
          height: 600,
          physics: {
                default: 'arcade',
                arcade: {
                    gravity: { y: 300 },
                    debug: false
                }
            },
          scene: {
              preload: preload,
              create: create,
              update: update
          }
      };
      var player;
      var platforms;
      var cursors;
      var score = 0;
      var scoreText;

      var game = new Phaser.Game(config);

      function preload (){
        this.load.image('sky', 'images/phaser/sky.png');
        this.load.image('ground', 'images/phaser/platform.png');
        this.load.image('star', 'images/phaser/star.png');
        this.load.image('bomb', 'images/phaser/bomb.png');
        this.load.spritesheet('dude', 'images/phaser/dude.png', {frameWidth: 32, frameHeight: 48});
      }

      function create (){
        this.add.image(400, 300, 'sky');
        platforms = this.physics.add.staticGroup();
        platforms.create(400, 568, 'ground').setScale(2).refreshBody();
        platforms.create(600, 400, 'ground');
        platforms.create(50, 250, 'ground');
        platforms.create(750, 220, 'ground');
        player = this.physics.add.sprite(100, 450, 'dude');
        player.setBounce(0.2);
        player.setCollideWorldBounds(true);

        this.anims.create({
            key: 'left',
            frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
            frameRate: 10,
            repeat: -1
        });

        this.anims.create({
            key: 'turn',
            frames: [ { key: 'dude', frame: 4 } ],
            frameRate: 20
        });

        this.anims.create({
            key: 'right',
            frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
            frameRate: 10,
            repeat: -1
        });
        cursors = this.input.keyboard.createCursorKeys();
        this.physics.add.collider(player, platforms);
        stars = this.physics.add.group({
            key: 'star',
            repeat: 11,
            setXY: { x: 12, y: 0, stepX: 70 }
        });

        stars.children.iterate(function (child) {
            child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
         });
        this.physics.add.collider(stars, platforms);
        this.physics.add.overlap(player, stars, collectStar, null, this);
        function collectStar (player, star){
              star.disableBody(true, true);
              score += 10;
              scoreText.setText('Score: ' + score);

          if (stars.countActive(true) === 0){
              stars.children.iterate(function (child) {
                child.enableBody(true, child.x, 0, true, true);
                });
            var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);
            var bomb = bombs.create(x, 16, 'bomb');
            bomb.setBounce(1);
            bomb.setCollideWorldBounds(true);
            bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);
              }
          }
        scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });

        bombs = this.physics.add.group();
        this.physics.add.collider(bombs, platforms);
        this.physics.add.collider(player, bombs, hitBomb, null, this);
        function hitBomb (player, bomb){
          this.physics.pause();
          player.setTint(0xff0000);
          player.anims.play('turn');
          gameOver = true;
          }
       }

      function update (){
          if (cursors.left.isDown){
              player.setVelocityX(-160);
              player.anims.play('left', true);
          }
          else if (cursors.right.isDown){
              player.setVelocityX(160);
              player.anims.play('right', true);
          }
          else {
              player.setVelocityX(0);
              player.anims.play('turn');
          }
          if (cursors.up.isDown && player.body.touching.down){
              player.setVelocityY(-330);
          }
      }
  </script>
  </body>
</html>


So könnte es aussehen: phaser_tutorial1_v5.html
Phaser is distributed under the MIT License. This covers the framework itself. Please see the trademark policy for details about using the Phaser logo or branding. Copyright © Richard Davey, Phaser Studio Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.