Article
Build A Video Jigsaw Puzzle in Flash
Page: 1 2
Flash allows us to use one movie clip as a mask for another. The masked clip will be visible only in places where the masking clip has some visible content. In this application, we use an empty movie clip as a mask, and then attach a puzzle piece to the mask. When the movie is published, only the portion of the image that overlaps the puzzle piece will be visible.

Note that we do not use the puzzle piece itself as a mask, but instead attach a copy of the puzzle piece to an initially empty mask. We'll see in a moment that this approach will give us a convenient way to join puzzle pieces together.
//create a mask, and attach the puzzle piece to the mask
createEmptyMovieClip("mask", 2);
mask.attachMovie("piece" + index, "piece" + index, index);
maskPiece = mask["piece" + index]; // reference to the mask piece
maskPiece.number = index; // the piece knows its own number
setMask(mask);
Now we need to place the masking puzzle piece in its proper position. For this, we refer to the original puzzle piece on the Stage. We set the mask piece to have the same _x and _y values as the original piece, then remove the original piece.
this.maskPiece._x = templatePiece._x;
this.maskPiece._y = templatePiece._y;
templatePiece.swapDepths(99);
templatePiece.removeMovieClip();
Movie clips created in the authoring tool, like our original puzzle pieces, are placed at negative depths. The method removeMovieClip() can only be used on movie clips at positive depths. So we need to send each puzzle piece to a positive depth, using swapDepths(), before we can remove it.
Finally, we add our new puzzle piece to the master list of puzzle pieces, update the maxDepth variable, and assign functions to fire when the puzzle piece is pressed or released.
pieces.push(this);
maxDepth = Math.max(maxDepth, this.getDepth());
this.onPress = pressFunction;
this.onRelease = this.onReleaseOutside = releaseFunction;
}
Make the Pieces Movable
It's all very well to have a bunch of puzzle pieces, but without some way to move them around, it won't be much fun!
Here is the function that fires when a puzzle piece is pressed. This function does four things. First, it moves the puzzle piece we've selected to a depth above all the other movie clips, to prevent the piece sliding under other pieces as it moves. Then it emits a click sound, by calling the start() method of the sound object downClick. Then, it calls the startDrag() method of the puzzle piece, causing it to follow the mouse. Finally, it cancels the onEnterFrame function. This will cancel the piece's desire to move to a random spot on the Stage, in case the user clicks on it during the randomization process.
private function pressFunction() {
if(!draggable) return; // do nothing if puzzle not yet active
this.swapDepths(++maxDepth); // bring piece above other pieces
downClick.start(); // emit click sound
this.startDrag(); // start dragging the piece
delete this.onEnterFrame; // cancel randomization
}
Take Action When a Piece is Released
When a puzzle piece is released, we have to do several things. Of course, we need to stop dragging, and emit a click sound so the user can tell something happened.
private function releaseFunction() {
if(!draggable) return;
upClick.start(); // emit a lower-pitched click
this.stopDrag(); // stop dragging
But we also need to check whether the piece is adjacent to any of its neighboring pieces. If so, these pieces will snap together, and effectively become a single piece.
Because we've set up all the pieces with the same reference point, it is easy to check whether two pieces are in the correct relative position. We need only check whether the differences between their _x and _y coordinates are sufficiently close to zero.
We need to set some tolerance around how close to their proper alignment pieces must be in order to be counted as correctly aligned. I've used a relatively large value of 16 pixels in this example, but larger or smaller values may work better for other puzzles.
Again, we use a loop that runs through all the elements of the pieces array.
for(var j in pieces) {
otherPiece = pieces[j];
Following the programming principle of putting shorter actions first, we skip over a piece if any one of several conditions occurs:
We will not join a piece to itself::
if(otherPiece == this) continue;
If the puzzle pieces are not touching, we will not join them:
if( !this.mask.hitTest(otherPiece .mask) ) continue;
And if the pieces are out of alignment by more than the tolerance, in either the x or the y direction, we will not join them:
if( Math.abs(this._x - otherPiece._x) > tol ||
Math.abs(this._y - otherPiece._y) > tol ) continue;
If we make it through all these tests, we know that the piece we've just released is aligned with another piece, and should be joined to it. In fact, we take the puzzle piece (or pieces, if a piece is already the result of previous joining) from the aligned movie clip, and add it to the mask layer of the released clip.
for(var m in otherPiece.mask) {
maskPiece = otherPiece.mask[m];
var n = this.mask.attachMovie("piece" + maskPiece.number,
"piece" + maskPiece.number, maskPiece.number);
n.number = maskPiece.number;
n._x = maskPiece._x;
n._y = maskPiece._y;
}
Then we delete the other piece from the array of puzzle pieces, and remove it from the stage:
pieces.splice(parseInt(j), 1);
otherPiece.removeMovieClip();
}
Finally, we check to see whether the puzzle has been completed. Here's the full release function:
private function releaseFunction() {
if(!draggable) return;
upClick.start(); // emit a lower-pitched click
this.stopDrag(); // stop dragging
// check for connection to neighboring pieces
var otherPiece : MovieClip;
var maskPiece : MovieClip;
for(var j in pieces) {
otherPiece = pieces[j];
if(otherPiece == this) continue;
if( !this.mask.hitTest(otherPiece.mask) ) continue;
if( Math.abs(this._x - otherPiece._x) > tol ||
Math.abs(this._y - otherPiece._y) > tol ) continue;
for(var m in otherPiece.mask) {
maskPiece = otherPiece.mask[m];
var n = this.mask.attachMovie("piece" + maskPiece.number,
"piece" + maskPiece.number, maskPiece.number);
n.number = maskPiece.number;
n._x = maskPiece._x;
n._y = maskPiece._y;
}
pieces.splice(parseInt(j), 1);
otherPiece.removeMovieClip();
}
_root.checkForCompletion();
}
Respond When the Puzzle is Completed
Because we merge any aligned puzzle pieces into a single piece, it is very easy to check whether the puzzle is finished: we just check whether the array of pieces has been reduced to a single element! If more than one element remains, we'll continue with the puzzle. Otherwise, we'll reward the user for successfully completing the puzzle. In this example, we remove the one remaining puzzle piece, and show a version of the movie that includes sound.
The sound-enhanced version of the movie has a stop() command on frame 1, so that we can preload it while the user is working on the puzzle, without having it launch immediately upon loading. So when the puzzle is done, we issue a gotoAndPlay(2) command to start the movie rolling.
function checkForCompletion() {
if(pieces.length > 1) return;
clearInterval(timerInt); // stop the timer
// show the movie with sound
p._alpha = 100;
p.holder.gotoAndPlay(2);
pieces[0].removeMovieClip();
}
Further Developments
On completion, we stop the timer, to record how long the user took to complete the puzzle. On a commercial Website, one could offer the user various rewards for having completed the puzzle. A free sample, or a lottery entry to win a prize, could be made available to those who complete the puzzle in less than one minute, for instance. Having accomplished a task requiring some effort, your visitor will likely feel that they have earned whatever prize you offer, and be more willing to give their contact information than they would for a free offer. And the user who misses the time cutoff may return to your site to try again.
A video jigsaw puzzle can be an enticing -- even addictive -- entertainment. See what you can do with it!