Create a 3D Sliding Puzzle Game in Flex with ActionScript 3.0 and Away3D
by 15 April, 2009 12:30 pm36
Flash Platform is a great tool to create games, either for internet or mobile content. With the introduction of Flash 3D Engines, the ability and success of creating flash games is even bigger. In this tutorial we are going to build up a simple sliding puzzle. Puzzle images are dynamically loaded and sliced so you can easily use your own images if you like. Learn, try and leave a comment showing your results.
Requirements
Prerequisites
- You should be familiar with OOP concepts and AS3
- Basic knowledge about Away3D (primitives, materials, cameras…)
The code (SlidingPuzzle.as)
So let’s see how it is done. First we’ll take a closer look at the main class (SlidingPuzzle.as). To get things working we need to import the proper classes.
package { import away3d.cameras.HoverCamera3D; import away3d.containers.View3D; import com.techlabs.puzzle.ControlPanel; import com.techlabs.puzzle.PuzzleBoard; import com.techlabs.puzzle.events.PuzzleEvent; import com.techlabs.puzzle.helpers.ImageSlicer; import flash.display.Bitmap; import flash.display.Loader; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageQuality; import flash.display.StageScaleMode; import flash.events.Event; import flash.net.URLRequest;
Next we initialize the variables.
public static var subdivisions:int = 4;
Subdivisions determine how the puzzle board and the image itself are divided into smaller pieces. The default value results in 16 pieces.
public static var padding:int = 2;
Padding defines the gap between puzzle pieces.This gives our game a bit more realistic feel.
public static var size:int = 500;
Size determines the physical dimensions of the puzzle board. The loaded image doesn’t have to be 500x500px but make sure it is not too small either. Bigger is better :).
Slicing the image
The rest of the main class (SlidingPuzzle.as) deals with setting up the view and initializing the needed objects but let’s take a look what happens inside imageLoadComplete() handler.
private function imageLoadComplete(e:Event):void { _image = _loader.content as Bitmap; _puzzleImages = _slicer.sliceImage(_image); _gameBoard.createPuzzle(_puzzleImages); _view.scene.addChild(_gameBoard); _controlPanel.setPreview(_image); }
When the image is loaded we invoke ImageSlicer class’s instance method sliceImage() and pass it the loaded image as a parameter. The sliceImage() function will then slice it accordingly (subdivisions) and return at two dimensional array containing the bitmaps.
public function sliceImage(image:Bitmap):Array { var imgW:Number = image.width; var imgH:Number = image.height; var pieceW:Number = imgW / SlidingPuzzle.cols; var pieceH:Number = imgH / SlidingPuzzle.rows; var imageArray:Array = new Array(); var rect:Rectangle; var temp:Bitmap; var tempdata:BitmapData; for(var y:int = 0; y < SlidingPuzzle.rows; y++) { imageArray[y] = new Array(); for(var x:int = 0; x < SlidingPuzzle.cols; x++) { tempdata = new BitmapData(pieceW, pieceH, true, 0x00000000); rect = new Rectangle(x * pieceW, y * pieceH, pieceW, pieceH); tempdata.copyPixels(image.bitmapData, rect, new Point(0, 0)); temp = new Bitmap(tempdata); imageArray[y][x] = temp; } } return imageArray; }
This two dimensional array is used for creating the board. createPuzzle() function takes the array as a parameter and iterates through it. Notice that the last piece is ignored beacause we need that space to be able to move other pieces. So here is what happens in the for loops.
- If we have reached the last piece let’s break out the loop
- New PuzzlePiece is created
- We add some eventhandlers to be able to interact with the piece
- We position the piece
When the loop is finnished the gameboard is centered in the view.
public function createPuzzle(images:Array):void { clearBoard(); var pieceCount:int = 0; var lastPiece:int = SlidingPuzzle.subdivisions * SlidingPuzzle.subdivisions; for(var yp:int = 0; yp < SlidingPuzzle.subdivisions; yp++) { for(var xp:int = 0; xp < SlidingPuzzle.subdivisions; xp++) { if (++pieceCount == lastPiece) break; var image:Bitmap = images[yp][xp]; var piece:PuzzlePiece = new PuzzlePiece(image, {width:_pieceWidth, height:_pieceHeight, segmentsH:3, segmentsW:3}); piece.addEventListener(PuzzleEvent.CLICK, clickHandler); piece.addEventListener(PuzzleEvent.MOVE, moveHandler); piece.addEventListener(PuzzleEvent.READY, moveHandler); piece.x = xp * _pieceWidth + xp * _padding; piece.z = -(yp * _pieceHeight + yp * _padding); addChild(piece); _pieces.push(piece); } } centerBoard(); }
Interaction
Well it just wouldn’t be a game without interaction. When user clicks a piece we first check if it is moving already. If it’s not moving we check its neighbours. If one of it’s neighbours is the empty place we move the piece there. checkNeighbours() function returns the direction where to move the piece or -1 if a valid move is not possible.
private function clickHandler(e:PuzzleEvent):void { if (_moving) return ; var direction:int = checkNeighbours(e.piece); if (direction > 0) { e.piece.move(direction); } } private function checkNeighbours(piece:PuzzlePiece):int { var empty:Boolean; // LEFT if (piece.x > 0) { empty = isEmptySpace(piece.x - _pieceWidth - _padding, piece.z); if (empty) return PuzzlePiece.LEFT; } // RIGHT if (piece.x < _boardWidth - _pieceWidth - _padding) { empty = isEmptySpace(piece.x + _pieceWidth + _padding, piece.z); if (empty) return PuzzlePiece.RIGHT; } // DOWN if (piece.z < 0) { empty = isEmptySpace(piece.x, piece.z + _pieceHeight + _padding); if (empty) return PuzzlePiece.UP; } // UP if (piece.z > -_boardHeight + _pieceHeight + _padding) { empty = isEmptySpace(piece.x, piece.z - _pieceHeight - _padding); if (empty) return PuzzlePiece.DOWN; } return -1; } private function isEmptySpace(xp:int, zp:int):Boolean { for each(var p:PuzzlePiece in _pieces) { if (p.x == xp) { if (p.z == zp) { return false; } } } return true; }
And “Game Over”, we have finished our puzzle game. Now it’s time to you test and create your own puzzle games. Leave a comment with your experiments. We are always seeking for the results of our tuts.