Quantcast
Channel: Spellfall – Emanuele Feronato
Viewing all articles
Browse latest Browse all 3

“Spellfall” HTML5 prototype made with Phaser updated to 2.8.2 and reusing tweens to improve performance

$
0
0

With the update of Phaser CE to 2.8.2, I am updating a 3 years old prototype – Spellfall – to the latest Phaser version, adding more comments to the source code and above all reusing tweens – actually, reusing THE tween – to save memory, CPU and improve performance.

Rather than creating a new tween at every player move, we create only one tween and update its target property according to the tile to be moved.

Also, a new to method is called each time to update tween destination. Finally, to prevent multiple to calls to create “waypoints” before the final tween destination, we have to reset its timeline property to an empty array.

This is very important, as I see people asking how to “reset” a tween destination in various threads and forums.

Well, just set timeline property to an empty array.

Let’s have a look at the result:

Just drag a tile over another tile to swap them. Yes, this is the simplest approach to a “match 3” game design.

And now, the source code with all required comments:

// the game itself
var game;

// global variable containing all game options
var gameOptions = {

    // width of the game, in pixels
    gameWidth: 300,

    // height of the game, in pixels
    gameHeight: 300,

    // size of each game tile, in pixels
    tileSize: 50,

    // number of tiles per row/column
    fieldSize: 6,

    // different kind of tiles in game
    tileTypes: 6,

    // zoom ratio to be applied on a tile when the player picks it up
    pickedZoom: 1.1
}
window.onload = function() {
    game = new Phaser.Game(gameOptions.gameWidth, gameOptions.gameHeight);
    game.state.add("PreloadGame", preloadGame);
    game.state.add("PlayGame", playGame);
    game.state.start("PreloadGame");
}
var preloadGame = function(game){}
preloadGame.prototype = {
    preload: function(){

        // these four lines will make the game run at the maximum scale allowed, while keeping it centered in the browser window
        game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
        game.scale.pageAlignHorizontally = true;
        game.scale.pageAlignVertically = true;
        game.stage.disableVisibilityChange = true;

        // loading the spritesheet containing all tiles
        game.load.spritesheet("tiles", "tiles.png", gameOptions.tileSize, gameOptions.tileSize);
    },
    create: function(){
        game.state.start("PlayGame");
    }
}
var playGame = function(game){}
playGame.prototype = {
    create: function(){

        // the tween we will use and recycle through the entire prototype
        this.tileTween = game.add.tween();

        // once the tween is completed update tileArray array and wait for input again
        this.tileTween.onComplete.add(function(){
            this.tileGroup.add(this.tileArray[this.landingRow][this.landingCol]);
            game.input.onDown.add(this.pickTile, this);
            var temp = this.tileArray[this.landingRow][this.landingCol];
            this.tileArray[this.landingRow][this.landingCol] = this.tileArray[this.movingRow][this.movingCol];
            this.tileArray[this.movingRow][this.movingCol] = temp;
        }, this);

        // dragging a tile is not allowed at the moment
        this.dragging = false;

        // tileArray is the two dimension array which will contain all tiles
        this.tileArray = [];

        // adding groups. movingTileGroup needs to be above tileGroup so moving tiles
        // will always have an higher z index and will always stay on top of the game
        this.tileGroup = game.add.group();
        this.movingTileGroup = game.add.group();

        // placing all tiles on stage
        for(var i = 0; i < gameOptions.fieldSize; i++){
    		this.tileArray[i] = [];
    		for(var j = 0; j < gameOptions.fieldSize; j++){

                // tossing a random number between 0 and tileTypes - 1, included
    			var randomTile = game.rnd.integerInRange(0, gameOptions.tileTypes - 1);

                // creation of the tile itself
                var theTile = game.add.sprite(j * gameOptions.tileSize + gameOptions.tileSize / 2, i * gameOptions.tileSize + gameOptions.tileSize / 2, "tiles", randomTile);

                // setting tile registration point at its center
                theTile.anchor.setTo(0.5);

                // adding tile to tileArray
                this.tileArray[i][j] = theTile;

                // adding tile to tileGroup
                this.tileGroup.add(theTile);
    		}
		}

        // waiting for player input
        game.input.onDown.add(this.pickTile, this);
    },
    pickTile: function(e){

        // saving input coordinates
		this.startX = e.position.x;
		this.startY = e.position.y;

		// retrieving picked row and column
		this.movingRow = Math.floor(this.startY / gameOptions.tileSize);
		this.movingCol = Math.floor(this.startX / gameOptions.tileSize);

        // moving the tile to the upper group, so it will surely be at top of the stage
		this.movingTileGroup.add(this.tileArray[this.movingRow][this.movingCol]);

        // zooming the tile
		this.tileArray[this.movingRow][this.movingCol].width = gameOptions.tileSize * gameOptions.pickedZoom;
		this.tileArray[this.movingRow][this.movingCol].height = gameOptions.tileSize * gameOptions.pickedZoom;

        // now dragging is allowed
		this.dragging = true;

        // updating listeners
		game.input.onDown.remove(this.pickTile, this);
        game.input.onUp.add(this.releaseTile, this);

    },
    releaseTile: function(){

        // removing the listener
        game.input.onUp.remove(this.releaseTile, this);

        // returning the tile to its originary group
        this.tileGroup.add(this.tileArray[this.movingRow][this.movingCol]);

        // determining landing row and column
        this.landingRow = Math.floor(this.tileArray[this.movingRow][this.movingCol].y / gameOptions.tileSize);
        this.landingCol = Math.floor(this.tileArray[this.movingRow][this.movingCol].x / gameOptions.tileSize);

        // resetting the moving tile to its original size
        this.tileArray[this.movingRow][this.movingCol].width = gameOptions.tileSize;
        this.tileArray[this.movingRow][this.movingCol].height = gameOptions.tileSize;

        // swapping tiles, both visually and in tileArray array...
        this.tileArray[this.movingRow][this.movingCol].x = this.landingCol * gameOptions.tileSize + gameOptions.tileSize / 2;
        this.tileArray[this.movingRow][this.movingCol].y = this.landingRow * gameOptions.tileSize + gameOptions.tileSize / 2;

        // ...but only if there moving and landing tiles are different!!
        if(this.movingRow != this.landingRow || this.movingCol != this.landingCol){

            // placing the tile to move on the upper group
        	this.movingTileGroup.add(this.tileArray[this.landingRow][this.landingCol]);

            // destination tile will move to start tile with a tween
        	this.tileTween.target = this.tileArray[this.landingRow][this.landingCol];

            // important!! We need to reset the timeline to prevent to create more and more waypoints
            this.tileTween.timeline = [];

            // setting tween destination
            this.tileTween.to({
                x: this.movingCol * gameOptions.tileSize + gameOptions.tileSize / 2,
        		y: this.movingRow * gameOptions.tileSize + gameOptions.tileSize / 2
        	}, 800, Phaser.Easing.Cubic.Out);
            this.tileTween.start();
        }

        // else just let the player be able to swap another tile
        else {
            game.input.onDown.add(this.pickTile, this);
        }

        // we aren't dragging anymore
        this.dragging = false;
    },
    update: function(){
        if(this.dragging){

            // checking x and y distance from starting to current input location
			var distX = game.input.worldX - this.startX;
            var distY = game.input.worldY - this.startY;

            // updating tile position
            this.tileArray[this.movingRow][this.movingCol].x = this.movingCol * gameOptions.tileSize + gameOptions.tileSize / 2 + distX;
            this.tileArray[this.movingRow][this.movingCol].y = this.movingRow * gameOptions.tileSize + gameOptions.tileSize / 2 + distY;
		}
    }
}

Using only one tween for the entire prototype is a great improvement as it will save Phaser to manage garbage collection, resulting in an overall performance improvement as well as a good practice.

Next time I will add more animations, meanwhile you can download the source code.


Viewing all articles
Browse latest Browse all 3

Latest Images

Trending Articles





Latest Images