Talking about Sokoban game, Game development, HTML5, Javascript and Phaser.
About one year ago I made a quick 80 levels Sokoban game with PuzzleScript.
I also ported it to iOS (download it! It’s free!) where it was played with some people who found it one of the most difficult free game ever.
Now I am showing you how to make an HTML5 Sokoban game using Phaser.
First, let’s have a look at tht game we are going to create starting from this sprite sheet:
From left to right, respectively from 0 to 6, we have the floor, the wall, the spot where to drop a crate, the crate, the player, the crate over the spot where to drop a crate, and the player over the spot where to drop a crate
You control the player by swiping in the direction you want it to move. Have a try using the mouse:
If you have a mobile device, play it from this link
As usual, making games with Phaser is always fun an quick, as you can see from the fully commented source code:
window.onload = function() { // game definition, 320x320 var game = new Phaser.Game(320,320,Phaser.CANVAS,"",{preload:onPreload, create:onCreate}); // constants with game elements var EMPTY = 0; var WALL = 1; var SPOT = 2; var CRATE = 3; var PLAYER = 4; // according to these values, the crate on the spot = CRATE+SPOT = 5 and the player on the spot = PLAYER+SPOT = 6 // sokoban level, using hardcoded values rather than constants to save time, shame on me :) var level = [[1,1,1,1,1,1,1,1],[1,0,0,1,1,1,1,1],[1,0,0,1,1,1,1,1],[1,0,0,0,0,0,0,1],[1,1,4,2,1,3,0,1],[1,0,0,0,1,0,0,1],[1,0,0,0,1,1,1,1],[1,1,1,1,1,1,1,1]]; // array which will contain all crates var crates = []; // size of a tile, in pixels var tileSize = 40; // the player! Yeah! var player; // variables used to detect and manage swipes var startX; var startY; var endX; var endY; // first function to be called, when the game preloads I am loading the sprite sheet with all game tiles function onPreload() { game.load.spritesheet("tiles","tiles.png",40,40); } // function to scale up the game to full screen function goFullScreen(){ game.scale.pageAlignHorizontally = true; game.scale.pageAlignVertically = true; game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; game.scale.setScreenSize(true); } // function to be called when the game has been created function onCreate() { // going full screen with the function defined at line 32 goFullScreen(); // adding two groups to the game. The fist group is called fixedGroup, it will contain // all non-moveable elements (everything but crates and player). // Then we add movingGroup which will contain moveable elements (crates and player) var fixedGroup = game.add.group(); var movingGroup = game.add.group(); // variable used for tile creation var tile // looping trough all level rows for(var i=0;i<level.length;i++){ // creation of 2nd dimension of crates array crates[i]= []; // looping through all level columns for(var j=0;j<level[i].length;j++){ // by default, there are no crates at current level position, so we set to null its // array entry crates[i][j] = null; // what do we have at row j, col i? switch(level[j][i]){ case PLAYER: case PLAYER+SPOT: // player creation player = game.add.sprite(40*i,40*j,"tiles"); // assigning the player the proper frame player.frame = level[j][i]; // creation of two custom attributes to store player x and y position player.posX = i; player.posY = j; // adding the player to movingGroup movingGroup.add(player); // since the player is on the floor, I am also creating the floor tile tile = game.add.sprite(40*i,40*j,"tiles"); tile.frame = level[j][i]-PLAYER; // floor does not move so I am adding it to fixedGroup fixedGroup.add(tile); break; case CRATE: case CRATE+SPOT: // crate creation, both as a sprite and as a crates array item crates[j][i] = game.add.sprite(40*i,40*j,"tiles"); // assigning the crate the proper frame crates[j][i].frame = level[j][i]; // adding the crate to movingGroup movingGroup.add(crates[j][i]); // since the create is on the floow, I am also creating the floor tile tile = game.add.sprite(40*i,40*j,"tiles"); tile.frame = level[j][i]-CRATE; // floor does not move so I am adding it to fixedGroup fixedGroup.add(tile); break; default: // creation of a simple tile tile = game.add.sprite(40*i,40*j,"tiles"); tile.frame = level[j][i]; fixedGroup.add(tile); } } } // once the level has been created, we wait for the player to touch or click, then we call // beginSwipe function game.input.onDown.add(beginSwipe, this); } // when the player begins to swipe we only save mouse/finger coordinates, remove the touch/click // input listener and add a new listener to be fired when the mouse/finger has been released, // then we call endSwipe function function beginSwipe(){ startX = game.input.worldX; startY = game.input.worldY; game.input.onDown.remove(beginSwipe); game.input.onUp.add(endSwipe); } // function to be called when the player releases the mouse/finger function endSwipe(){ // saving mouse/finger coordinates endX = game.input.worldX; endY = game.input.worldY; // determining x and y distance travelled by mouse/finger from the start // of the swipe until the end var distX = startX-endX; var distY = startY-endY; // in order to have an horizontal swipe, we need that x distance is at least twice the y distance // and the amount of horizontal distance is at least 10 pixels if(Math.abs(distX)>Math.abs(distY)*2 && Math.abs(distX)>10){ // moving left, calling move function with horizontal and vertical tiles to move as arguments if(distX>0){ move(-1,0); } // moving right, calling move function with horizontal and vertical tiles to move as arguments else{ move(1,0); } } // in order to have a vertical swipe, we need that y distance is at least twice the x distance // and the amount of vertical distance is at least 10 pixels if(Math.abs(distY)>Math.abs(distX)*2 && Math.abs(distY)>10){ // moving up, calling move function with horizontal and vertical tiles to move as arguments if(distY>0){ move(0,-1); } // moving down, calling move function with horizontal and vertical tiles to move as arguments else{ move(0,1); } } // stop listening for the player to release finger/mouse, let's start listening for the player to click/touch game.input.onDown.add(beginSwipe); game.input.onUp.remove(endSwipe); } // function to move the player function move(deltaX,deltaY){ // if destination tile is walkable... if(isWalkable(player.posX+deltaX,player.posY+deltaY)){ // ...then move the player and exit the function movePlayer(deltaX,deltaY); return; } // if the destination tile is a crate... if(isCrate(player.posX+deltaX,player.posY+deltaY)){ // ...if after the create there's a walkable tils... if(isWalkable(player.posX+2*deltaX,player.posY+2*deltaY)){ // move the crate moveCrate(deltaX,deltaY); // move the player movePlayer(deltaX,deltaY); } } } // a tile is walkable when it's an empty tile or a spot tile function isWalkable(posX,posY){ return level[posY][posX] == EMPTY || level[posY][posX] == SPOT; } // a tile is a crate when it's a... guess what? crate, or it's a crate on its spot function isCrate(posX,posY){ return level[posY][posX] == CRATE || level[posY][posX] == CRATE+SPOT; } // function to move the player function movePlayer(deltaX,deltaY){ // moving with a 1/10s tween var playerTween =game.add.tween(player); playerTween.to({ x:player.x+deltaX*tileSize, y:player.y + deltaY*tileSize }, 100, Phaser.Easing.Linear.None,true); // updating player old position in level array level[player.posY][player.posX]-=PLAYER; // updating player custom posX and posY attributes player.posX+=deltaX; player.posY+=deltaY; // updating player new position in level array level[player.posY][player.posX]+=PLAYER; // changing player frame accordingly player.frame = level[player.posY][player.posX]; } // function to move the crate function moveCrate(deltaX,deltaY){ // moving with a 1/10s tween var crateTween =game.add.tween(crates[player.posY+deltaY][player.posX+deltaX]); crateTween.to({ x:crates[player.posY+deltaY][player.posX+deltaX].x+deltaX*tileSize, y:crates[player.posY+deltaY][player.posX+deltaX].y+deltaY*tileSize, }, 100, Phaser.Easing.Linear.None,true); // updating crates array crates[player.posY+2*deltaY][player.posX+2*deltaX]=crates[player.posY+deltaY][player.posX+deltaX]; crates[player.posY+deltaY][player.posX+deltaX]=null; // updating crate old position in level array level[player.posY+deltaY][player.posX+deltaX]-=CRATE; // updating crate new position in level array level[player.posY+2*deltaY][player.posX+2*deltaX]+=CRATE; // changing crate frame accordingly crates[player.posY+2*deltaY][player.posX+2*deltaX].frame=level[player.posY+2*deltaY][player.posX+2*deltaX]; } }
Can you solve the level? I also can show you how to turn this in a complete game if I receive good feedback. Meanwhile, download the source code of the entire project.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.