Learn how Phaser manages draggable objects by making the HTML5 engine behind Rush Hour game

Read all posts about "" game
Do you know Rush Hour game? From Wikipedia: Rush Hour is a sliding block puzzle invented by Nob Yoshigahara in the 1970s. It was first sold in the United States in 1996. It is now being manufactured by ThinkFun (formerly Binary Arts). The goal of the game is to get the red car out of a six-by-six grid full of automobiles by moving the other vehicles out of its way. However, the cars and trucks (set up before play according to a puzzle card) obstruct the path which makes the puzzle harder. This is the perfect game to create to show you the features Phaser puts at our disposal when it’s time to drag objects. You can define a sprite to be draggable, lock it on an axis, make it snap to a grid when you drag it or when you release it, define a bounding box which the sprite cannot cross while dragging, and there are events to trigger functions when you start and stop dragging. This way, using only draggable sprites, I made this prototype of the first level: Each rectangle is a car or a truck, drag them around to see how they move, snap and react as if there was some kind of collision system. But there isn’t. You are just dragging sprites with some constraints. Here is the source code, with all required comments:
// the game itself
var game;

// two variables to represent "horizontal" and "vertical" cars. Better using HORIZONTAL and VERTICAL rather than 0 and 1
var HORIZONTAL = 0;
var VERTICAL = 1;

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

// game board, it's a 6x6 array, initially all its items are set to zero = empty
var levelArray = [
     [0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0]
];

// these are the cars to place on the board.
// each car is an object with the following properties:
// row: car upper row
// col: car leftmost column
// dir: car direction, can be HORIZONTAL or VERTICAL
// spr: name of the image to assign to car sprite
var carsArray = [
     {
          row: 0,
          col: 0,
          dir: HORIZONTAL,
          len: 2,
          spr: "car"
     },
     {
          row: 1,
          col: 0,
          dir: VERTICAL,
          len: 3,
          spr: "truck"
     },
     {
          row: 4,
          col: 0,
          dir: VERTICAL,
          len: 2,
          spr: "car"
     },
     {
          row: 2,
          col: 1,
          dir: HORIZONTAL,
          len: 2,
          spr: "car"
     },
     {
          row: 1,
          col: 3,
          dir: VERTICAL,
          len: 3,
          spr: "truck"
     },
     {
          row: 5,
          col: 2,
          dir: HORIZONTAL,
          len: 3,
          spr: "truck"
     },
     {
          row: 0,
          col: 5,
          dir: VERTICAL,
          len: 3,
          spr: "truck"
     },
     {
          row: 4,
          col: 4,
          dir: HORIZONTAL,
          len: 2,
          spr: "car"
     }
]

// some car colors to be randomly assigned to cars
var carColors = ["0xff0000", "0x00ff00", "0x0000ff", "0xffff00", "0x00ffff", "0xff00ff"];

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

// "PlayGame" state
var playGame = function(game){}
playGame.prototype = {
     // when the state preloads...
	preload: function(){
          // preloading graphic assets
          game.load.image("field", "field.png");
          game.load.image("car", "car.png"); 
          game.load.image("truck", "truck.png");    	
	},
     // when the state starts...
	create: function(){
          // adding the sprite representing the game field     
          game.add.sprite(0, 0, "field");
          // now it's time to add all cars
          for(var i = 0; i < carsArray.length; i++){
               // to keep the code clear, I assign carsArray[i] to a variable simply called "car"
               var car = carsArray[i];
               // looping through car length
               for(var j = 0; j < car.len; j++){
                    // if the car is horizontal
                    if(car.dir == HORIZONTAL){
                         // setting levelArray items overlapped by the car to 1 (not empty);
                         levelArray[car.row][car.col + j] = 1;
                    }
                    // if the car is vertical... (I know I could have used "else" but being a tutorial it looks better this way)
                    if(car.dir ==  VERTICAL){
                         // setting levelArray items overlapped by the car to 1 (not empty);
                         levelArray[car.row + j][car.col] = 1;     
                    }     
               }
               // adding the sprite representing the car
               // notice car direction (car.dir) is also involved in the placement.
               var carSprite = game.add.sprite(tileSize * car.col + tileSize * car.dir, tileSize * car.row, car.spr);
               // car sprite will be rotated by 90 degrees if the car is VERTICAL and by 0 degrees if the car is HORIZONTAL
               carSprite.angle = 90 * car.dir;
               // Assigning to car sprite some custom data, adding them as an object. We'll store car position, direction and length
               carSprite.data = {
                    row: car.row,
                    col: car.col,
                    dir: car.dir,
                    len: car.len
               }
               // assigning a random color to the car
               carSprite.tint = carColors[game.rnd.between(0, carColors.length - 1)];
               // the car has input enabled
               carSprite.inputEnabled = true;
               // the car can be dragged
               carSprite.input.enableDrag();
               // the car will snap to a tileSize * tileSize grid but only when it's released
               carSprite.input.enableSnap(tileSize, tileSize, false, true);
               // when the car starts to be dragged, call startDrag funcion
               carSprite.events.onDragStart.add(startDrag);
               // when the car stops to be dragged, call stopDrag function
               carSprite.events.onDragStop.add(stopDrag);
               // if car direction is VERTICAL then prevent the sprite to be dragged horizontally
               if(car.dir == VERTICAL){
                    carSprite.input.allowHorizontalDrag = false;
               }
               // if car direction is HORIZONTAL then prevent the sprite to be dragged vertically
               if(car.dir == HORIZONTAL){
                    carSprite.input.allowVerticalDrag = false;     
               }                   
          }
	}
}

// function to be called when a car is dragged. "s" is the reference of the car itself
function startDrag(s){
     // declaring some variables here because I am using them 
     var i;
     var from;
     var to;
     // if the car is horizontal...
     if(s.data.dir == HORIZONTAL){
          // from is the leftmost column occupied by the car
          from = s.data.col;
          // to is the rightmost column occupied by the car
          to = s.data.col + s.data.len - 1;
          // now we are going from the leftmost column backward until column zero, the first column
          for(i = s.data.col - 1; i >= 0; i --){
               // if it's an empty spot, then we update "from" position
               if(levelArray[s.data.row][i] == 0){
                    from = i;
               }
               // otherwise we exit the loop
               else{
                    break;
               }
          }
          // now we are going from the rightmost column forward until column five, the last column
          for(i = s.data.col + s.data.len; i < 6; i ++){
               // if it's an empty spot, then we update "to" position
               if(levelArray[s.data.row][i] == 0){
                    to = i;
               }
               // otherwise we exit the loop
               else{
                    break;
               }
          }
          // at this time, we assign the car a bounding box which will limit its movements. Think about it as a fence,
          // the car cannot cross the fence
          s.input.boundsRect = new Phaser.Rectangle(from * tileSize, s.y, (to - from + 1) * tileSize, tileSize);
     }
     // the same thing applies to verical cars, just remember this time they are rotated by 90 degrees
     if(s.data.dir == VERTICAL){
          from = s.data.row;
          to = s.data.row + s.data.len - 1;
          for(i = s.data.row - 1; i >= 0; i --){
               if(levelArray[i][s.data.col] == 0){
                    from = i;
               }
               else{
                    break;
               }
          }
          for(i = s.data.row + s.data.len; i < 6; i ++){
               if(levelArray[i][s.data.col] == 0){
                    to = i;
               }
               else{
                    break;
               }
          }
          s.input.boundsRect = new Phaser.Rectangle(s.x, from * tileSize, s.x + s.data.len * tileSize, (to - from + 2 - s.data.len) * tileSize);
     }
}

// function to be called when a car is not dragged anymore. "s" is the reference of the car itself
function stopDrag(s){
     // here we just update levelArray items according to the car we moved.
     // first, we set to zero all items where the car was initially placed
     for(var i = 0; i < s.data.len; i ++){
          if(s.data.dir == HORIZONTAL){
               levelArray[s.data.row][s.data.col + i] = 0;
          }
          if(s.data.dir == VERTICAL){
               levelArray[s.data.row + i][s.data.col] = 0;    
          }
     }
     // then we set to 1 all items where the car is placed now
     if(s.data.dir == HORIZONTAL){
          s.data.col = s.x / tileSize;
          for(i = 0; i < s.data.len; i++){
               levelArray[s.data.row][s.data.col + i] = 1;     
          }
     }
     if(s.data.dir == VERTICAL){
          s.data.row = s.y / tileSize;
          for(i = 0; i < s.data.len; i++){
               levelArray[s.data.row + i][s.data.col] = 1;     
          } 
     }
}
Have fun designing your own levels, and 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