Phaser Tutorial: Wie erstelle ich ein Snake Spiel mit Phaser?

Im folgenden Tutorial wird erklärt, wie man ein Snake-Spiel mit Phaser entwirft und entwickelt. Das Spiel habe ich vor einiger Zeit hier vorgestellt. Verfügbar ist es im Browser und für Android im Play Store.

Spielkonzept

Das Spielkonzept sollte eigentlich bekannt sein. Aber zur Übersicht erst einmal ein kurzer Abriss über die Spielfunktionen:

  • Das Spielfeld basiert auf Feldern.
  • Das Spielfeld soll quadratisch sein und eine Größe von 6 x 6 Feldern haben.
  • Das Spielfeld ist durch einen Rand begrenzt.
  • Die Schlange startet mit der Größe von einem Feld in der Mitte vom Spielfeld.
  • Die Schlange bewegt sich von Feld zu Feld.
  • Die Bewegung der Schlange soll fließend sein. (Es soll ja nicht zu einfach werden)
  • Auf dem Spielfeld erscheinen Objekte zum Einsammeln.
  • Sammelt die Schlange ein Objekt ein, wird sie länger und die Punktzahl erhöht sich.
  • Trifft die Schlange den Rand vom Spielfeld, stirbt sie.
  • Die Schlange stirbt auch, wenn sie sich selbst trifft.
  • Nachdem die Schlange gestorben ist, wird das Spiel zurückgesetzt.

Das gibt ja schon einmal eine Übersicht über die Funktionen die gebaut werden müssen. Es wird ein Spielfeld benötigt und darauf wird die Schlange gesetzt. Da die Animation der Schlange nicht Feld für Feld ist, sondern sie über die Felder „rutscht“, muss sie extra sein. Aber die Positionsinformationen müssen trotzdem in das Spielfeld eingetragen werden, damit sie sich selbst treffen kann und damit Objekte zum Einsammeln nicht innerhalb der Schlange erscheinen.

Phaser initialisieren

Der erste Schritt ist Phaser.io zu initialisieren und das Spiel zu starten. Damit man es eventuell noch mal wieder verwenden kann, wird alles im Kontext eines States erstellt.


// Anzahl der Spielfelder
var fieldSize = 8;

// Größe eines Spielfelds
var tileSize = 32;

var GameState = {
    preload: function () {
        ...
    },
    create: function () {
        ...
    },
    update: function () {
        ...
    }
};

var snake = new Phaser.Game(fieldSize * tileSize, fieldSize * tileSize, Phaser.AUTO, '');
snake.state.add('Game', GameState);
snake.state.start('Game');

Warum bei fieldSize 8 steht?  Das ist die Spielfläche plus Rand.

Perload – Laden der Materialen

Folgende Materialien werden benötigt:

  • Gras
  • Wand
  • Objekt zum Einsammeln
  • Schlange
  • Todesanimation der Schlange

Das Gras, die Wand und das Objekt bilden das Spielfeld und werden von einer TileMap benutzt, also kommen sie zusammen auf ein Bild. Ebenfalls zusammen auf eines kommen die Schlange und deren Animation.

 

preload: function() {
        this.load.spritesheet('player', 'assets/sprites/player.png', 32, 32);
        this.load.image('tileImages', 'assets/sprites/tileImages.png');
}

Der Player wird als Spritesheet geladen, damit nachher daraus die Animation erzeugt werden kann.

Create – Erstellen des Spiels

Es werden alle Objekte erzeugt, die das Spiel benötigt. Dabei ist zu beachten, dass das Einsammelbare nach der Schlange erzeugt wird, andernfalls könnten diese auf dem gleichen Feld landen.


    create: function () {
        "use strict";
        //Spielflächer erstellen
        this.level = new Level(fieldSize, fieldSize, tileSize, this.game);

        //Erstelle Spieler
        this.player = new Player(this.game, this.level);

        //aktiviere input
        this.cursors = this.game.input.keyboard.createCursorKeys();

        //ein Objekt zum einsammeln erstellen
        this.level.createCollectible();

        //Es wurde noch keine Startrichtung angestoßen
        this.direction = null;
    },

Update – Lass das Spiel laufen

Aktualisiere einmal pro Frame die Eingabe und starte die Schlange, falls noch nicht geschehen. Gleichzeitig wird verhindert, dass die Schlange rückwärts durch sich selbst laufen kann. Wenn die Schlange erst einmal läuft, läuft sie bis sie Stirbt (das macht sie selber). Wichtig ist nur die Eingaben aktuell zu halten.

    update: function () {
        "use strict";
        //Aktualisiere den Input
        //Verhindere dabei das Rüchwärtsgehen, wenn die Schlange länger als 1 ist
        if (this.cursors.up.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.DOWN || this.player.sprites.length === 1)) {
            this.direction = Phaser.UP;

        } else if (this.cursors.down.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.UP || this.player.sprites.length === 1)) {
            this.direction = Phaser.DOWN;

        } else if (this.cursors.left.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.RIGHT || this.player.sprites.length === 1)) {
            this.direction = Phaser.LEFT;

        } else if (this.cursors.right.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.LEFT || this.player.sprites.length === 1)) {
            this.direction = Phaser.RIGHT;
        }

        //Starte den Spieler, sobald deine Richtung festgelegt wurde und dieser sich nicht schon bewegt.
        if (this.direction !== null && !this.player.isMoving && this.player.alive) {
            this.player.startMoving();
        }
    },

 

GameOver – Neustart

Um das Spiel zu beenden, bekommt GameState eine die Funktion gameOver(). Damit wird die momentane Runde beendet und der State neu geladen.

var GameState = {
    
    /**
    * Beendet das Spiel:
    * Tötet die Schlange und startet neu
    */
    gameOver: function (a, v) {
        "use strict";
        this.player.alive = false;
        this.player.deathAnimation();
        this.timeout = setTimeout(changeState, 800);
        var that = this;

        function changeState() {
            that.state.start('Game');
        }

    },
...

Spielfeld

Das Spielfeld besteht aus einer Tilemap und einem 2-Dimensionalen Array, welches die Belegung des Spielfeldes speichert. Mit der Tilemap wird ein Layer erstellt mit dem das Level generiert wird. Zum Speichern der Belegung könnte man einen unsichtbaren Layer benutzen, aber ich mache es mit einem einfachen 2-Dimensionalen Array, da dieses Ressourcen-effizienter ist.

Jetzt werden für das Spielfeld noch Funktionen zum interagieren mit dem Main-Loop und der Schlange benötigt. Die Schlange muss ihre Position verändern können. Weiterhin muss sie das Objekt einsammeln und neu erstellen können.

/**
 * Stellt das Spielfeld bereit, sowie Funktionen zum Updaten 
 *
 * @param {int} width
 * @param {int} heigth
 * @param {int} tilesize
 * @param {Phaser.Game} game
 */
function Level(width, height, tileSize, game) {
    "use strict";
    this.height = height;
    this.width = width;
    this.tileSize = tileSize;
    this.game = game;

    //Tilemap erstellen
    var tileMap = game.add.tilemap();
    //Bilder für die Tiles hinzufügen
    tileMap.addTilesetImage('tileImages');
    //Sichtbaren Layer erstellen
    var mapLayer = tileMap.create('layer1', width, height, tileSize, tileSize);
    //Wände erstellen
    //obere Wand
    tileMap.fill(this.tiles.wall, 0, 0, height, 1, mapLayer);
    //linke Wand
    tileMap.fill(this.tiles.wall, 0, 1, 1, width - 2, mapLayer);
    //untere Wand
    tileMap.fill(this.tiles.wall, 0, width - 1, height, 1, mapLayer);
    //rechte Wand
    tileMap.fill(this.tiles.wall, height - 1, 1, 1, width - 2, mapLayer);

    //Spielfläche
    tileMap.fill(this.tiles.grass, 1, 1, height - 2, width - 2, mapLayer);

    // Initialisierung des Arrays zum Verfolgen des Blockierstatus
    var freeFields = [];
    for (var i = 0; i < width; i++) {
        var line = [];
        for (var j = 0; j < height; j++) {
            if (j === 0 || i === 0 || i === width - 1 || j === height - 1) {
                // Alle Ränder blockieren 
                line.push(2);
            } else {
                // Alle anderen Felder sind Frei
                line.push(0);
            }
        }
        freeFields.push(line);
    }

    /** 
     * Füge ein Feld in die Liste der freien Felder ein.
     * 
     * @param {int} x - x-Koordinate des Feldes
     * @param {int} y - y-Koordinate des Feldes
     * @returns {boolean} Erfolg?
     */
    this.freeTileAdd = function (x, y) {
        if (freeFields[y][x] === 1) {
            freeFields[y][x] = 0;
            return true;
        } else {
            return false;
        }
    };

    /** 
     * Entferne ein Feld aus der Liste der freien Felder. 
     *
     * @param {int} x - x-Koordinate des Feldes
     * @param {int} y - y-Koordinate des Feldes
     * @returns {boolean} Erfolg?
     */
    this.freeTileRemove = function (x, y) {
        if (freeFields[y][x] === 0) {
            freeFields[y][x] = 1;
            return true;
        } else {
            return false;
        }
    };

    /**
     * Gibt eine Liste aller freien Felder zurück.
     *
     * @returns {list} Liste aller freien Felder
     */
    this.freeTileList = function () {
        var list = [];
        for (var y = 0; y < freeFields.length; y++) {
            for (var x = 0; x < freeFields[y].length; x++) { if (freeFields[y][x] === 0) { list.push(new Phaser.Point(x, y)); } } } return list; }; /** * Erstellt auf einer freien Fläche des Spielfelds (grass) ein Objekt zum Einsammeln * und entfernt dieses aus der Liste der freien Felder. * * @returns {boolean} Erfolg? */ this.createCollectible = function () { var list = this.freeTileList(); // Wenn es mehr als ein freies Feld gibt, wird ein neues Objekt erstellt if (list.length > 1) {
            //Wählt ein zufälliges Feld aus der Liste aus.
            var ele = Math.floor(Math.random() * list.length);
            tileMap.putTile(this.tiles.collectible, list[ele].x, list[ele].y, mapLayer);
            // Entferne die Stelle aus den freien Feldern
            return this.freeTileRemove(list[ele].x, list[ele].y);
        } else {
            return false;
        }
    };

    /**
     * Versucht das Objet ein zu sammeln.
     * @param {Phaser.Point} point
     * @retruns {boolean} Erfolg?
     */

    this.collectCollectible = function (point) {
        if (tileMap.getTile(point.x, point.y, mapLayer).index === this.tiles.collectible) {
            tileMap.putTile(this.tiles.grass, point.x, point.y, mapLayer);
            this.freeTileAdd(point.x, point.y);
            return true;

        } else {
            return false;
        }
    };
};

Level.prototype = {
    /**
     * Ordnet die Tiles den Bildern zu
     */
    get tiles() {
        return {
            grass: 0,
            wall: 1,
            collectible: 2
        };
    },
};

Spieler

Kommen wir zum wichtigsten Element, das wir brauchen, die Schlange. Sie wird einmal angestoßen und läuft dann bis sie Stirbt. Dabei sagt sie dem Spielfeld immer, wo sie ist. Des Weiteren kann sie das Objekt einsammeln, um zu wachsen.

Die Bewegung der Schlange läuft dabei auf errechneten Position der obersten-linken Tile-Ecke ab, dazu bewegen sich die Teile der Schlange zwischen diesen Punkten. Die Bewegung der Schlange setzt sich aus zwei Teilen zusammen.

  1. wird der Kopf geklont und der Klon wird auf das nächste Feld geschoben.
  2. wird das Ende der Schlange ein Feld entlang der Schlange weiter geschoben und dann gelöscht. Falls die Schlange größer wird (Objekt eingesammelt) passiert nichts.

/*
 * Der Spieler aka die Schlange
 *
 * @param {Phaser.Game} game - Game Referenz
 * @param {Level} level - Das eingesetzte Spielfeld
 */
function Player(game, level) {
    this.level = level;
    this.game = game;
    this.alive = true;
    this.sprites = [];
    this.isMoving = false;
    var lastMoveDirection = null;

    //Es wird in der Mitte gestartet
    var startPosition = {
        x: Math.floor(this.level.width / 2),
        y: Math.floor(this.level.height / 2)
    };

    //Erstellt den Schlangenkopf an der Startposition
    var sprite = game.add.sprite(startPosition.x * level.tileSize, startPosition.y * level.tileSize, 'player', 0);
    this.sprites.push(sprite);

    //Blockt die Startposition
    this.level.freeTileRemove(startPosition.x, startPosition.y);

    /**
     * Gibt die momentan TilePosition des Kopfes aus.
     *
     * @returns {Phaser.Point} 
     */
    this.getPoint = function () {
        var marker = new Phaser.Point();
        marker.x = game.math.snapToFloor(Math.floor(this.sprites[0].x), level.tileSize) / level.tileSize;
        marker.y = game.math.snapToFloor(Math.floor(this.sprites[0].y), level.tileSize) / level.tileSize;
        return marker;
    };

    /*
     * Bewegt die Schlange ein Feld weiter
     * Nach dem die Animation abgeschlossen ist, wird startMoving() aufgerufen
     *
     * @param {nextTile} Phaser.Point - Das nächste Feld, auf den die Schlange eines vorrücken soll.
     * @param {extend} boolean
     */
    this.performTween = function (nextTile, extend) {

        var oldHead = this.getPoint();
        if (this.sprites.length >= 2 || extend) {

            var sprite = this.game.add.sprite(this.sprites[0].x, this.sprites[0].y, 'player');
            this.sprites.splice(1, 0, sprite);

            //Wenn die Schlange nicht länger wird, wird das letzt Glieg weitergeschoben und gelöscht.
            if (!extend) {
                var tweenLast = this.game.add.tween(this.sprites[this.sprites.length - 1])
                    .to({
                        x: this.sprites[this.sprites.length - 2].x,
                        y: this.sprites[this.sprites.length - 2].y
                    }, 200);
                tweenLast.onComplete.add(this.playerTileRemoveLast, this);
                tweenLast.start();
            }
        }

        //erstes Feld bewegen
        var args = {
            x: nextTile.x * tileSize,
            y: nextTile.y * tileSize
        };
        var tweenFirst = this.game.add.tween(this.sprites[0]).to(args, 200);
        tweenFirst.onComplete.add(this.startMoving, this);
        tweenFirst.start();
    };

    /*
     * Entfernt das letzt Glied der Schlange
     *
     */
    this.playerTileRemoveLast = function () {
        var sprite = this.sprites.pop();
        sprite.destroy();
    };

    /*
     * Spielt die Animation für den Tod der Schlange auf allen
     * Kettengliedern ab.
     */
    this.deathAnimation = function () {
        for (var i = 0; i < this.sprites.length; i++) {
            this.sprites[i].animations.add('death', [1, 2, 3, 4, 5], 15);
            this.sprites[i].animations.play('death');
        }
    };

    /** 
    * Diese Funktion wird für jeden Schritt aufgerufen.
    * Den Anfang macht der Update-Loop, danach läuft die Schlange automatisch
    * indem am Ende einer Bewegung wieder diese Funktion aufgerufen wird, 
    * bis die Schlange stirbt.
    *
    * Diese Funktion sorgt dafür, dass das Level auf dem aktuellen Stand ist,
    * wo die Schlange ist. Sie sorgt dafür, dass sie das Objekt einsammelt + neu erstellt 
    * und wenn ein Hindernis getroffen wird, sie stribt.
    *
    */
    this.startMoving = function () {
        "use strict";

        //keine Bewegung, wenn die Schlange tot ist
        if (!this.alive) {
            return;
        }

        //ab sofort bewegt sich die Schlange
        this.isMoving = true;

        //Suche das nächte Tile, dass angesteuert wird
        var currentTile = this.getPoint();
        var dircetion = this.game.state.getCurrentState().direction;
        var collectibleCollected = false;

        var nextTile = currentTile;
        if (dircetion == Phaser.LEFT) {
            lastMoveDirection = Phaser.LEFT;
            nextTile.x -= 1;
        }
        if (dircetion == Phaser.RIGHT) {
            lastMoveDirection = Phaser.RIGHT;
            nextTile.x += 1;
        }
        if (dircetion == Phaser.UP) {
            lastMoveDirection = Phaser.UP;
            nextTile.y -= 1;
        }
        if (dircetion == Phaser.DOWN) {
            lastMoveDirection = Phaser.DOWN;
            nextTile.y += 1;
        }

        //Zuerst einsammeln
        if (this.level.collectCollectible(nextTile)) {
            collectibleCollected = true;
            //Score hochzählen
        }

        //Wenn das nächste Feld immer noch belegt ist = Tot
        //Gleichzeitig Entferne das neue Feld von den freien Feldern, da sich die Schlange auf dieses Feld bewegt.
        if (!this.level.freeTileRemove(nextTile.x, nextTile.y)) {
            //end the game if collided
            this.alive = false;
            this.game.state.getCurrentState().gameOver();
            return;
        }

        //Wenn nichts eingesammelt wurde, bewegt sich das Schlangeende eins weiter.
        if (!collectibleCollected) {
            this.level.freeTileAdd(this.sprites[this.sprites.length - 1].x / this.level.tileSize,
                this.sprites[this.sprites.length - 1].y / this.level.tileSize);
        }

        //Starte die Bewegung
        this.performTween(nextTile, collectibleCollected);

        //Erstelle ein neues Objekt zum einsammeln, falls dieses eingesammel wurde
        if (collectibleCollected) {
            this.level.createCollectible();
        }

    };
    /**
     * Gibt die zuletzt durchgeführte Bewegungsrichtung zurück
     * @returns {int} Richtung
     */
    this.getLastMoveDirection = function () {
        return lastMoveDirection;
    };
};

 

Zusammenfassung

Hier der komplette Quelltext zum aufklappen:

/*===================================
 *  Konfiguration
 *===================================*/

// Anzahl der Spielfelder
var fieldSize = 8;

// Größe eines Spielfelds
var tileSize = 32;

/*===================================
 * GameObjects
 ====================================*/


/**
 * Stellt das Spielfeld bereit, sowie Funktionen zum Updaten 
 *
 * @param {int} width
 * @param {int} heigth
 * @param {int} tilesize
 * @param {Phaser.Game} game
 */
function Level(width, height, tileSize, game) {
    "use strict";
    this.height = height;
    this.width = width;
    this.tileSize = tileSize;
    this.game = game;

    //Tilemap erstellen
    var tileMap = game.add.tilemap();
    //Bilder für die Tiles hinzufügen
    tileMap.addTilesetImage('tileImages');
    //Sichtbaren Layer erstellen
    var mapLayer = tileMap.create('layer1', width, height, tileSize, tileSize);
    //Wände erstellen
    //obere Wand
    tileMap.fill(this.tiles.wall, 0, 0, height, 1, mapLayer);
    //linke Wand
    tileMap.fill(this.tiles.wall, 0, 1, 1, width - 2, mapLayer);
    //untere Wand
    tileMap.fill(this.tiles.wall, 0, width - 1, height, 1, mapLayer);
    //rechte Wand
    tileMap.fill(this.tiles.wall, height - 1, 1, 1, width - 2, mapLayer);

    //Spielfläche
    tileMap.fill(this.tiles.grass, 1, 1, height - 2, width - 2, mapLayer);

    // Initialisierung des Arrays zum Verfolgen des Blockierstatus
    var freeFields = [];
    for (var i = 0; i < width; i++) {
        var line = [];
        for (var j = 0; j < height; j++) {
            if (j === 0 || i === 0 || i === width - 1 || j === height - 1) {
                // Alle Ränder blockieren 
                line.push(2);
            } else {
                // Alle anderen Felder sind Frei
                line.push(0);
            }
        }
        freeFields.push(line);
    }

    /** 
     * Füge ein Feld in die Liste der freien Felder ein.
     * 
     * @param {int} x - x-Koordinate des Feldes
     * @param {int} y - y-Koordinate des Feldes
     * @returns {boolean} Erfolg?
     */
    this.freeTileAdd = function (x, y) {
        if (freeFields[y][x] === 1) {
            freeFields[y][x] = 0;
            return true;
        } else {
            return false;
        }
    };

    /** 
     * Entferne ein Feld aus der Liste der freien Felder. 
     *
     * @param {int} x - x-Koordinate des Feldes
     * @param {int} y - y-Koordinate des Feldes
     * @returns {boolean} Erfolg?
     */
    this.freeTileRemove = function (x, y) {
        if (freeFields[y][x] === 0) {
            freeFields[y][x] = 1;
            return true;
        } else {
            return false;
        }
    };

    /**
     * Gibt eine Liste aller freien Felder zurück.
     *
     * @returns {list} Liste aller freien Felder
     */
    this.freeTileList = function () {
        var list = [];
        for (var y = 0; y < freeFields.length; y++) {
            for (var x = 0; x < freeFields[y].length; x++) {
                if (freeFields[y][x] === 0) {
                    list.push(new Phaser.Point(x, y));
                }
            }
        }
        return list;
    };

    /**
     * Erstellt auf einer freien Fläche des Spielfelds (grass) ein Objekt zum Einsammeln
     * und entfernt dieses aus der Liste der freien Felder.
     *
     * @returns {boolean} Erfolg?
     */
    this.createCollectible = function () {
        var list = this.freeTileList();
        // Wenn es mehr als ein freies Feld gibt, wird ein neues Objekt erstellt
        if (list.length > 1) {
            //Wählt ein zufälliges Feld aus der Liste aus.
            var ele = Math.floor(Math.random() * list.length);
            tileMap.putTile(this.tiles.collectible, list[ele].x, list[ele].y, mapLayer);
            // Entferne die Stelle aus den freien Feldern
            return this.freeTileRemove(list[ele].x, list[ele].y);
        } else {
            return false;
        }
    };

    /**
     * Versucht das Objet ein zu sammeln.
     * @param {Phaser.Point} point
     * @retruns {boolean} Erfolg?
     */

    this.collectCollectible = function (point) {
        if (tileMap.getTile(point.x, point.y, mapLayer).index === this.tiles.collectible) {
            tileMap.putTile(this.tiles.grass, point.x, point.y, mapLayer);
            this.freeTileAdd(point.x, point.y);
            return true;

        } else {
            return false;
        }
    };
};

Level.prototype = {
    /**
     * Ordnet die Tiles den Bildern zu
     */
    get tiles() {
        return {
            grass: 0,
            wall: 1,
            collectible: 2
        };
    },
};

/*
 * Der Spieler aka die Schlange
 *
 * @param {Phaser.Game} game - Game Referenz
 * @param {Level} level - Das eingesetzte Spielfeld
 */
function Player(game, level) {
    this.level = level;
    this.game = game;
    this.alive = true;
    this.sprites = [];
    this.isMoving = false;
    var lastMoveDirection = null;

    //Es wird in der Mitte gestartet
    var startPosition = {
        x: Math.floor(this.level.width / 2),
        y: Math.floor(this.level.height / 2)
    };

    //Erstellt den Schlangenkopf an der Startposition
    var sprite = game.add.sprite(startPosition.x * level.tileSize, startPosition.y * level.tileSize, 'player', 0);
    this.sprites.push(sprite);

    //Blockt die Startposition
    this.level.freeTileRemove(startPosition.x, startPosition.y);

    /**
     * Gibt die momentan TilePosition des Kopfes aus.
     *
     * @returns {Phaser.Point} 
     */
    this.getPoint = function () {
        var marker = new Phaser.Point();
        marker.x = game.math.snapToFloor(Math.floor(this.sprites[0].x), level.tileSize) / level.tileSize;
        marker.y = game.math.snapToFloor(Math.floor(this.sprites[0].y), level.tileSize) / level.tileSize;
        return marker;
    };

    /*
     * Bewegt die Schlange ein Feld weiter
     * Nach dem die Animation abgeschlossen ist, wird startMoving() aufgerufen
     *
     * @param {nextTile} Phaser.Point - Das nächste Feld, auf den die Schlange eines vorrücken soll.
     * @param {extend} boolean
     */
    this.performTween = function (nextTile, extend) {

        var oldHead = this.getPoint();
        if (this.sprites.length >= 2 || extend) {

            var sprite = this.game.add.sprite(this.sprites[0].x, this.sprites[0].y, 'player');
            this.sprites.splice(1, 0, sprite);

            //Wenn die Schlange nicht länger wird, wird das letzt Glieg weitergeschoben und gelöscht.
            if (!extend) {
                var tweenLast = this.game.add.tween(this.sprites[this.sprites.length - 1])
                    .to({
                        x: this.sprites[this.sprites.length - 2].x,
                        y: this.sprites[this.sprites.length - 2].y
                    }, 200);
                tweenLast.onComplete.add(this.playerTileRemoveLast, this);
                tweenLast.start();
            }
        }

        //erstes Feld bewegen
        var args = {
            x: nextTile.x * tileSize,
            y: nextTile.y * tileSize
        };
        var tweenFirst = this.game.add.tween(this.sprites[0]).to(args, 200);
        tweenFirst.onComplete.add(this.startMoving, this);
        tweenFirst.start();
    };

    /*
     * Entfernt das letzt Glied der Schlange
     *
     */
    this.playerTileRemoveLast = function () {
        var sprite = this.sprites.pop();
        sprite.destroy();
    };

    /*
     * Spielt die Animation für den Tod der Schlange auf allen
     * Kettengliedern ab.
     */
    this.deathAnimation = function () {
        for (var i = 0; i < this.sprites.length; i++) {
            this.sprites[i].animations.add('death', [1, 2, 3, 4, 5], 15);
            this.sprites[i].animations.play('death');
        }
    };

    /** 
    * Diese Funktion wird für jeden Schritt aufgerufen.
    * Den Anfang macht der Update-Loop, danach läuft die Schlange automatisch
    * indem am Ende einer Bewegung wieder diese Funktion aufgerufen wird, 
    * bis die Schlange stirbt.
    *
    * Diese Funktion sorgt dafür, dass das Level auf dem aktuellen Stand ist,
    * wo die Schlange ist. Sie sorgt dafür, dass sie das Objekt einsammelt + neu erstellt 
    * und wenn ein Hindernis getroffen wird, sie stribt.
    *
    */
    this.startMoving = function () {
        "use strict";

        //keine Bewegung, wenn die Schlange tot ist
        if (!this.alive) {
            return;
        }

        //ab sofort bewegt sich die Schlange
        this.isMoving = true;

        //Suche das nächte Tile, dass angesteuert wird
        var currentTile = this.getPoint();
        var dircetion = this.game.state.getCurrentState().direction;
        var collectibleCollected = false;

        var nextTile = currentTile;
        if (dircetion == Phaser.LEFT) {
            lastMoveDirection = Phaser.LEFT;
            nextTile.x -= 1;
        }
        if (dircetion == Phaser.RIGHT) {
            lastMoveDirection = Phaser.RIGHT;
            nextTile.x += 1;
        }
        if (dircetion == Phaser.UP) {
            lastMoveDirection = Phaser.UP;
            nextTile.y -= 1;
        }
        if (dircetion == Phaser.DOWN) {
            lastMoveDirection = Phaser.DOWN;
            nextTile.y += 1;
        }

        //Zuerst einsammeln
        if (this.level.collectCollectible(nextTile)) {
            collectibleCollected = true;
            //Score hochzählen
        }

        //Wenn das nächste Feld immer noch belegt ist = Tot
        //Gleichzeitig Entferne das neue Feld von den freien Feldern, da sich die Schlange auf dieses Feld bewegt.
        if (!this.level.freeTileRemove(nextTile.x, nextTile.y)) {
            //end the game if collided
            this.alive = false;
            this.game.state.getCurrentState().gameOver();
            return;
        }

        //Wenn nichts eingesammelt wurde, bewegt sich das Schlangeende eins weiter.
        if (!collectibleCollected) {
            this.level.freeTileAdd(this.sprites[this.sprites.length - 1].x / this.level.tileSize,
                this.sprites[this.sprites.length - 1].y / this.level.tileSize);
        }

        //Starte die Bewegung
        this.performTween(nextTile, collectibleCollected);

        //Erstelle ein neues Objekt zum einsammeln, falls dieses eingesammel wurde
        if (collectibleCollected) {
            this.level.createCollectible();
        }

    };
    /**
     * Gibt die zuletzt durchgeführte Bewegungsrichtung zurück
     * @returns {int} Richtung
     */
    this.getLastMoveDirection = function () {
        return lastMoveDirection;
    };
};


/**=================================== 
 * GameState
 *====================================*/

var GameState = {

    /**
     * Beendet das Spiel:
     * Tötet die Schlange und startet neu
     */
    gameOver: function (a, v) {
        "use strict";
        this.player.alive = false;
        this.player.deathAnimation();
        this.timeout = setTimeout(changeState, 800);
        var that = this;

        function changeState() {
            that.state.start('Game');
        }

    },


    preload: function () {

        this.load.spritesheet('player', 'assets/player.png', tileSize, tileSize);
        this.load.image('tileImages', 'assets/tileImages.png');
    },

    create: function () {
        "use strict";
        //Spielflächer erstellen
        this.level = new Level(fieldSize, fieldSize, tileSize, this.game);

        //Erstelle Spieler
        this.player = new Player(this.game, this.level);

        //aktiviere input
        this.cursors = this.game.input.keyboard.createCursorKeys();

        //ein Objekt zum einsammeln erstellen
        this.level.createCollectible();

        //Es wurde noch keine Startrichtung angestoßen
        this.direction = null;
    },

    update: function () {
        "use strict";
        //Aktualisiere den Input
        //Verhindere dabei das Rüchwärtsgehen, wenn die Schlange länger als 1 ist
        if (this.cursors.up.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.DOWN || this.player.sprites.length === 1)) {
            this.direction = Phaser.UP;

        } else if (this.cursors.down.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.UP || this.player.sprites.length === 1)) {
            this.direction = Phaser.DOWN;

        } else if (this.cursors.left.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.RIGHT || this.player.sprites.length === 1)) {
            this.direction = Phaser.LEFT;

        } else if (this.cursors.right.isDown &&
            (this.player.getLastMoveDirection() !== Phaser.LEFT || this.player.sprites.length === 1)) {
            this.direction = Phaser.RIGHT;
        }

        //Starte den Spieler, sobald deine Richtung festgelegt wurde und dieser sich nicht schon bewegt.
        if (this.direction !== null && !this.player.isMoving && this.player.alive) {
            this.player.startMoving();
        }
    },
};

/* ===================================
 * Init 
 *===================================*/
var snake = new Phaser.Game(fieldSize * tileSize, fieldSize * tileSize, Phaser.AUTO, '');
snake.state.add('Game', GameState);
snake.state.start('Game');


Share on FacebookShare on Google+Flattr the authorTweet about this on TwitterShare on RedditEmail this to someone

J. Wiese

Hallo du, schön, dass du dich hierhin verirrt hast! Mein Name ist Johannes Wiese und ich studiere Informatik und blogge hier ab und zu über dieses und jenes. Wenn dir etwas gefällt oder du Fragen hast, dann lass es mich jeder Zeit wissen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.