HTML5 prototype of iOS hit “Hero Slide” made with Phaser – adding explosions when matching bombs

Read all posts about "" game
The Hero Slide series continue, with another important feature to add and some code optimization. Let’s see what you learned in steps 1 and 2: * Initialize the game * Place random tiles on the map * Move tiles with WASD keys * Add animation to tile creation and movement * Match tiles Now I optimized a bit the code because it was a bit redundant, using an universal function to move the tiles rather than writing distinct code for each direction, and added the capability of removing tiles adjacent to a match when you match bombs. I used an array called detonations to save all tiles we will detonate – it could be more than one if you match more than two bombs. First, have a look at the commented code:
// the game
var game;

// size of each tile, in pixels
var tileSize = 120;

// different kinds of tiles
var tileTypes = 5;

// the game array, the board will be stored here
var gameArray = [];

// field size, in tiles. This will represent a 4x4 tiles field
var fieldSize = 4;

// creation of the game
window.onload = function() {	
	game = new Phaser.Game(480, 480);
     game.state.add("PlayGame", playGame);
     game.state.start("PlayGame");
}


var playGame = function(game){}


playGame.prototype = {
     preload: function(){
          
          // preloading the assets
          game.load.spritesheet("tiles", "tiles.png", tileSize, tileSize);          
     },
     create: function(){ 
          
          // initializing game board
          for(var i = 0; i < fieldSize; i++){
               gameArray[i] = [];
               for(var j = 0; j < fieldSize; j++){
                    
                    // each array item is an object with a tile value (0: empty) and a sprite (null: no sprite)
                    gameArray[i][j] = {
                         tileValue : 0,
                         tileSprite: null
                    };
               }
          }
          
          // function to add a new item, will be explained later
          this.addItem(); 
          
          // liteners to handle WASD keys. Each key calls handleKey function
          this.upKey = game.input.keyboard.addKey(Phaser.Keyboard.W);
          this.upKey.onDown.add(this.handleKey, this);
    		this.downKey = game.input.keyboard.addKey(Phaser.Keyboard.S);
    		this.downKey.onDown.add(this.handleKey, this);
    		this.leftKey = game.input.keyboard.addKey(Phaser.Keyboard.A);
    		this.leftKey.onDown.add(this.handleKey, this);
    		this.rightKey = game.input.keyboard.addKey(Phaser.Keyboard.D);
    		this.rightKey.onDown.add(this.handleKey, this);    
     },
     
     // this function will add a new item to the board
     addItem: function(){
     
          // emptySpots is an array which will contain all the available empty tiles where to place a new item
          var emptySpots = [];
          
          // now we loop through the game board to check for empty tiles
          for(var i = 0; i < fieldSize; i++){
               for(var j = 0; j < fieldSize; j++){
               
                    // remember we define an empty tile as a tile whose tileValue is zero
                    if(gameArray[i][j].tileValue == 0){
                    
                         // at this time we push a Point with tile coordinates into emptySpots array
                         emptySpots.push(new Phaser.Point(j, i));
                    }
               }
          }
          
          // newSpot is a randomly picked item in emptySpots array
          var newSpot = Phaser.ArrayUtils.getRandomItem(emptySpots);
          
          // if newSpot is not null this means we have a place where to put a new tile
          if(newSpot != null){
          
               // selecting a random value between 1 and tileTypes
               var tileType = game.rnd.between(1, tileTypes);
               
               // updating game array with the new tile value and sprite
               gameArray[newSpot.y][newSpot.x] = {
                    tileValue: tileType,
                    tileSprite: game.add.sprite(newSpot.x * tileSize, newSpot.y * tileSize, "tiles", tileType - 1)
               }
               
               // we start with the alpha at zero to create a "fade in" tween
               gameArray[newSpot.y][newSpot.x].tileSprite.alpha = 0;
               
               // here is the fade in effect
               var fadeTween = game.add.tween(gameArray[newSpot.y][newSpot.x].tileSprite).to({
                    alpha: 1
               }, 500, Phaser.Easing.Linear.None, true);
               
               // now the player can move
               fadeTween.onComplete.add(function(e){
                    
                    // the player can move again
                    this.canMove = true;    
               }, this);
          }
     },
     
     // this function handles player movements
     handleKey: function(e){
          
          // first of all, let's see if the player can move
          if(this.canMove){
          
               // if the player can move, let's set canMove to false to prevent the player to move twice
               this.canMove=false;
               
               // initialize a detonation array. Will store all detonations, if any, to perform at the end of the turn
               this.detonations = [];
               this.detonations.length = 0;
               
               // time to check for the keycode which generated the event
               switch(e.keyCode){
               
                    // "A" key (left)
                    case Phaser.Keyboard.A:
                         
                         // we scan for game field, from TOP to BOTTOM and from LEFT to RIGHT starting from the 2nd column
                         for(var i = 0; i < fieldSize; i++){
                              for(var j = 1; j < fieldSize; j++){
                              
                                   // we can move a tile if it's not empty and the tile on its left is empty
                                   if(gameArray[i][j].tileValue != 0 && gameArray[i][j - 1].tileValue == 0){
                                        
                                        // moving the tile
                                        this.moveTile(i, j, i, j - 1);
                                   }
                                   else{
                                        // we can match a tile if it's not empty and the tile on its left has the same value
                                        if(gameArray[i][j].tileValue != 0 && gameArray[i][j - 1].tileValue == gameArray[i][j].tileValue){
                                             
                                             // removing the item
                                             this.moveAndRemove(i, j, {x: gameArray[i][j].tileSprite.x - tileSize}, i, j - 1)
                                        }
                                   }
                              }
                         }
                    break;
                    
                    // "W" key (up)
                    case Phaser.Keyboard.W:
                    
                         // we scan for game field, from TOP to BOTTOM and from LEFT to RIGHT starting from the 2nd row
                         // applying the same concepts seen before
                         for(var i = 1; i < fieldSize; i++){
                              for(var j = 0; j < fieldSize; j++){
                                   if(gameArray[i][j].tileValue != 0 && gameArray[i - 1][j].tileValue == 0){    
                                        this.moveTile(i, j, i - 1, j);
                                   }
                                   else{
                                        if(gameArray[i][j].tileValue != 0 && gameArray[i - 1][j].tileValue == gameArray[i][j].tileValue){
                                             this.moveAndRemove(i, j, {y: gameArray[i][j].tileSprite.y - tileSize}, 1 - 1, j)
                                        }     
                                   }
                              }
                         }
                    break;
                    
                    // "D" key (right)
                    case Phaser.Keyboard.D:
                    
                         // we scan for game field, from TOP to BOTTOM and from RIGHT to LEFT starting from the next-to-last column
                         // applying the same concepts seen before
                         for(var i = 0; i < fieldSize; i++){
                              for(var j = fieldSize - 2; j >= 0; j--){
                                   if(gameArray[i][j].tileValue != 0 && gameArray[i][j + 1].tileValue == 0){
                                        this.moveTile(i, j, i, j + 1);
                                   }
                                   else{
                                        if(gameArray[i][j].tileValue != 0 && gameArray[i][j + 1].tileValue == gameArray[i][j].tileValue){
                                             this.moveAndRemove(i, j, {x: gameArray[i][j].tileSprite.x + tileSize}, i, j + 1)
                                        }
                                   }
                              }
                         }
                    break;
                    
                    // "S" key (down)
                    case Phaser.Keyboard.S:
                         
                         // we scan for game field, from BOTTOM to TOP and from LEFT to RIGHT starting from the next-to-last row
                         // applying the same concepts seen before
                         for(var i = fieldSize - 2; i >= 0; i--){
                              for(var j = 0; j < fieldSize; j++){
                                   if(gameArray[i][j].tileValue != 0 && gameArray[i + 1][j].tileValue == 0){
                                        this.moveTile(i, j, i + 1, j);
                                   }
                                   else{
                                        if(gameArray[i][j].tileValue != 0 && gameArray[i + 1][j].tileValue == gameArray[i][j].tileValue){
                                             this.moveAndRemove(i, j, {y: gameArray[i][j].tileSprite.y + tileSize}, i + 1, j)
                                        }
                                   }
                              }
                         }
                    break;
                    
               } 
               
               // at the end of player input, we handle the detonations and add a new item
               this.handleDetonations();
               this.addItem();               
          }
     },
     
     // function to move a tile
     moveTile: function(row, col, toRow, toCol){
          
          // moving the tile
          var moveTween = game.add.tween(gameArray[row][col].tileSprite).to({
               x: gameArray[row][col].tileSprite.x + tileSize * (toCol - col),
               y: gameArray[row][col].tileSprite.y + tileSize * (toRow - row)   
          }, 200,  Phaser.Easing.Linear.None, true);
          
          // copying the content of the current tile on the destination tile
          gameArray[toRow][toCol] = {
               tileValue: gameArray[row][col].tileValue,
               tileSprite: gameArray[row][col].tileSprite
          }
          
          //setting current tile to empty
          gameArray[row][col] = {
               tileValue: 0,
               tileSprite: null
          }
     },
     
     // function to move and remove a tile
     moveAndRemove: function(row, col, toObject, toRow, toCol){
          var moveTween = game.add.tween(gameArray[row][col].tileSprite).to(toObject, 200,  Phaser.Easing.Linear.None, true);
          moveTween.onComplete.add(function(e){
               e.destroy();   
          }, this);                  
          
          // looking at tile type to see what to do next - at the moment we only manage bombs
          
          switch(gameArray[row][col].tileValue){
               
               // bomb
               case 4:
                    
                    // let's add detonation coordinates to detonations array
                    this.detonations.push(new Phaser.Point(toCol, toRow));
          }        
                  
          gameArray[row][col] = {
               tileValue: 0,
               tileSprite: null
          } 
     },
     
     // handling detonations
     handleDetonations: function(){
          
          // looping through all detonations
          for(var i = 0; i < this.detonations.length; i++){
          
               // removing the bomb
               gameArray[this.detonations[i].y][this.detonations[i].x].tileSprite.destroy();
               gameArray[this.detonations[i].y][this.detonations[i].x] = {
                    tileValue: 0,
                    tileSprite: null
               }
               
               // handle detonations
               if(this.detonations[i].y - 1 >= 0 && gameArray[this.detonations[i].y - 1][this.detonations[i].x].tileValue != 0){
                    gameArray[this.detonations[i].y - 1][this.detonations[i].x].tileSprite.destroy(); 
                    gameArray[this.detonations[i].y - 1][this.detonations[i].x] = {
                         tileValue: 0,
                         tileSprite: null
                    }     
               }
               if(this.detonations[i].y + 1 < fieldSize && gameArray[this.detonations[i].y + 1][this.detonations[i].x].tileValue != 0){
                    gameArray[this.detonations[i].y + 1][this.detonations[i].x].tileSprite.destroy();
                    gameArray[this.detonations[i].y + 1][this.detonations[i].x] = {
                         tileValue: 0,
                         tileSprite: null
                    }      
               }
               if(this.detonations[i].x - 1 >= 0 && gameArray[this.detonations[i].y][this.detonations[i].x - 1].tileValue != 0){
                    gameArray[this.detonations[i].y][this.detonations[i].x - 1].tileSprite.destroy();
                    gameArray[this.detonations[i].y][this.detonations[i].x - 1] = {
                         tileValue: 0,
                         tileSprite: null
                    }      
               }
               if(this.detonations[i].x + 1 < fieldSize && gameArray[this.detonations[i].y][this.detonations[i].x + 1].tileValue != 0){
                    gameArray[this.detonations[i].y][this.detonations[i].x + 1].tileSprite.destroy(); 
                    gameArray[this.detonations[i].y][this.detonations[i].x + 1] = {
                         tileValue: 0,
                         tileSprite: null
                    }      
               }
          }     
     }
}
And now let’s play the game:
Move tiles with WASD keys and try to match two bombs – focus the canvas first. There is still a lot to do with animations, since all events run on their own and the result is a bit messy, and this is what we are going to do in next step, as well as handling swipe movement. At that time, the prototype will be complete and we will only have to add gameplay. Meanwhile, download the source code.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers