Talking about Dungeon Raid game, Game development, HTML5, Javascript and Phaser.
The series of match 3 games like Dungeon Raid and Bejeweled continues and today I am showing you how to make tiles fall after you removed them from the game field. In previous posts I explained the whole concept so the code is not commented, but if you open the console while you are running it you will see a lot of hints about how it’s working. Draw to select circles, you can also backtrack. This is the source code:
var game;
var gameOptions = {
gameWidth: 700,
gameHeight: 700,
tileSize: 140,
fieldSize: 5,
fallSpeed: 250
}
window.onload = function() {
game = new Phaser.Game(gameOptions.gameWidth, gameOptions.gameHeight);
game.state.add("TheGame", TheGame);
game.state.start("TheGame");
}
var TheGame = function(){};
TheGame.prototype = {
preload: function(){
game.stage.backgroundColor = 0x444444;
game.load.image("tiles", "assets/sprites/tiles.png");
game.load.spritesheet("arrows", "assets/sprites/arrows.png", 420, 420);
},
create: function(){
game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
game.scale.pageAlignHorizontally = true;
game.scale.pageAlignVertically = true;
this.createLevel();
game.input.onDown.add(this.pickTile, this);
},
createLevel: function(){
this.tilesArray = [];
this.arrowsArray = [];
// group creation and placement to stay in the center of the canvas
this.tileGroup = game.add.group();
this.arrowsGroup = game.add.group();
var groupSize = gameOptions.tileSize * gameOptions.fieldSize;
this.tileGroup.x = (game.width - groupSize) / 2;
this.tileGroup.y = (game.height - groupSize) / 2;
this.arrowsGroup.x = (game.width - groupSize) / 2;
this.arrowsGroup.y = (game.height - groupSize) / 2;
// tile creation
for(var i = 0; i < gameOptions.fieldSize; i++){
this.tilesArray[i] = [];
for(var j = 0; j < gameOptions.fieldSize; j++){
this.addTile(i, j);
}
}
},
addTile: function(row, col){
// adding a new tile
var tileXPos = col * gameOptions.tileSize + gameOptions.tileSize / 2;
var tileYPos = row * gameOptions.tileSize + gameOptions.tileSize / 2;
var theTile = game.add.sprite(tileXPos, tileYPos, "tiles");
theTile.anchor.set(0.5);
theTile.picked = false;
theTile.coordinate = new Phaser.Point(col, row);
this.tilesArray[row][col] = theTile;
var text = game.add.text(-gameOptions.tileSize / 4, 0, "R" + theTile.coordinate.y.toString() + ", C" + theTile.coordinate.x.toString(), {fill: "#000", font:"bold 24px Arial"});
theTile.addChild(text);
this.tileGroup.add(theTile);
},
pickTile: function(e){
// picking the first tile
this.visitedTiles = [];
this.visitedTiles.length = 0;
if(this.tileGroup.getBounds().contains(e.position.x, e.position.y)){
var col = Math.floor((e.position.x - this.tileGroup.x) / gameOptions.tileSize);
var row = Math.floor((e.position.y - this.tileGroup.y) / gameOptions.tileSize);
this.tilesArray[row][col].alpha = 0.5;
this.tilesArray[row][col].picked = true;
game.input.onDown.remove(this.pickTile, this);
game.input.onUp.add(this.releaseTile, this);
game.input.addMoveCallback(this.moveTile, this);
this.visitedTiles.push(this.tilesArray[row][col].coordinate);
console.log("Picked tile at R" + row + ", C" + col);
}
},
moveTile: function(e){
// we are over a tile
if(this.tileGroup.getBounds().contains(e.position.x, e.position.y)){
var col = Math.floor((e.position.x - this.tileGroup.x) / gameOptions.tileSize);
var row = Math.floor((e.position.y - this.tileGroup.y) / gameOptions.tileSize);
var distance = new Phaser.Point(e.position.x - this.tileGroup.x, e.position.y - this.tileGroup.y).distance(this.tilesArray[row][col]);
// we are inside enough a tile
if(distance < gameOptions.tileSize * 0.4){
// a new, adjacent tile
if(!this.tilesArray[row][col].picked && this.checkAdjacent(new Phaser.Point(col, row), this.visitedTiles[this.visitedTiles.length - 1])){
this.tilesArray[row][col].picked = true;
this.tilesArray[row][col].alpha = 0.5;
this.visitedTiles.push(this.tilesArray[row][col].coordinate);
this.addArrow();
console.log("Adding tile at R" + row + ", C" + col);
}
// backtrack
else{
if(this.visitedTiles.length > 1 && row == this.visitedTiles[this.visitedTiles.length - 2].y && col == this.visitedTiles[this.visitedTiles.length - 2].x){
this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].picked = false;
this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].alpha = 1;
this.visitedTiles.pop();
this.arrowsArray[this.arrowsArray.length - 1].destroy();
this.arrowsArray.pop();
console.log("Back to tile at R" + row + ",C" + col);
}
}
}
}
},
releaseTile: function(){
game.input.onUp.remove(this.releaseTile, this);
game.input.deleteMoveCallback(this.moveTile, this);
game.input.onDown.add(this.pickTile, this);
// clear the path
this.arrowsGroup.removeAll(true);
for(var i = 0; i < this.visitedTiles.length; i++){
console.log("Removed tile R" + this.visitedTiles[i].y + ", C" + this.visitedTiles[i].x);
this.tilesArray[this.visitedTiles[i].y][this.visitedTiles[i].x].destroy();
this.tilesArray[this.visitedTiles[i].y][this.visitedTiles[i].x] = null;
console.log("Removed tilesArray entry [" + this.visitedTiles[i].y + "][" + this.visitedTiles[i].x + "]");
}
// make tiles fall down
for(var i = gameOptions.fieldSize - 1; i >= 0; i--){
for(var j = 0; j < gameOptions.fieldSize; j++){
if(this.tilesArray[i][j] != null){
var holes = this.holesBelow(i, j);
if(holes > 0){
var coordinate = new Phaser.Point(this.tilesArray[i][j].coordinate.x, this.tilesArray[i][j].coordinate.y);
var destination = new Phaser.Point(j, i + holes);
console.log("Tile at R" + coordinate.y + ", C" + coordinate.x + " moves to R" + destination.y + ", C" + destination.x)
var tween = game.add.tween(this.tilesArray[i][j]).to({
y: this.tilesArray[i][j].y + holes * gameOptions.tileSize
}, gameOptions.fallSpeed, Phaser.Easing.Linear.None, true);
tween.onComplete.add(function(s){
}, this)
this.tilesArray[destination.y][destination.x] = this.tilesArray[i][j]
console.log("Replenished tilesArray entry [" + destination.y + "][" + destination.x + "]");
this.tilesArray[coordinate.y][coordinate.x] = null;
console.log("Removed tilesArray entry [" + coordinate.y + "][" + coordinate.x + "]");
this.tilesArray[destination.y][destination.x].coordinate = new Phaser.Point(destination.x, destination.y)
this.tilesArray[destination.y][destination.x].children[0].text = "R" + destination.y + ", C" + destination.x;
}
}
}
}
// create new tiles
for(var i = 0; i < gameOptions.fieldSize; i++){
var holes = this.holesInCol(i);
if(holes > 0){
for(var j = 1; j <= holes; j++){
var tileXPos = i * gameOptions.tileSize + gameOptions.tileSize / 2;
var tileYPos = -j * gameOptions.tileSize + gameOptions.tileSize / 2;
var theTile = game.add.sprite(tileXPos, tileYPos, "tiles");
theTile.anchor.set(0.5);
theTile.picked = false;
var tween = game.add.tween(theTile).to({
y: theTile.y + holes * gameOptions.tileSize
}, gameOptions.fallSpeed, Phaser.Easing.Linear.None, true)
theTile.coordinate = new Phaser.Point(i, holes - j);
this.tilesArray[holes - j][i] = theTile;
var text = game.add.text(-gameOptions.tileSize / 4, 0, "R" + theTile.coordinate.y.toString() + ", C" + theTile.coordinate.x.toString(), {fill: "#000", font:"bold 24px Arial"});
console.log("Created a new tile at R" + theTile.coordinate.y.toString() + ", C" + theTile.coordinate.x.toString());
console.log("Added tilesArray entry [" + (holes - j).toString() + "][" + i + "]");
theTile.addChild(text);
this.tileGroup.add(theTile);
}
}
}
console.log("----------------------------------------------------------");
},
checkAdjacent: function(p1, p2){
return (Math.abs(p1.x - p2.x) <= 1) && (Math.abs(p1.y - p2.y) <= 1);
},
addArrow: function(){
// adding the arrows
var fromTile = this.visitedTiles[this.visitedTiles.length - 2];
var arrow = game.add.sprite(this.tilesArray[fromTile.y][fromTile.x].x, this.tilesArray[fromTile.y][fromTile.x].y, "arrows");
this.arrowsGroup.add(arrow);
arrow.anchor.set(0.5);
// this routine handles arrow frame and angle according to its direction
var tileDiff = new Phaser.Point(this.visitedTiles[this.visitedTiles.length - 1].x, this.visitedTiles[this.visitedTiles.length - 1].y)
tileDiff.subtract(this.visitedTiles[this.visitedTiles.length - 2].x, this.visitedTiles[this.visitedTiles.length - 2].y);
if(tileDiff.x == 0){
arrow.angle = -90 * tileDiff.y;
}
else{
arrow.angle = 90 * (tileDiff.x + 1);
if(tileDiff.y != 0){
arrow.frame = 1;
if(tileDiff.y + tileDiff.x == 0){
arrow.angle -= 90;
}
}
}
this.arrowsArray.push(arrow);
},
holesBelow: function(row, col){
var result = 0;
for(var i = row + 1; i < gameOptions.fieldSize; i++){
if(this.tilesArray[i][col] == null){
result ++;
}
}
return result;
},
holesInCol: function(col){
var result = 0;
for(var i = 0; i < gameOptions.fieldSize; i++){
if(this.tilesArray[i][col] == null){
result ++;
}
}
return result;
}
}
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.