Talking about Spellfall game, Game development, HTML5, Javascript and Phaser.
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 itstarget
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;
}
}
}
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.