Oxide Interactive

Articles

Wooden Puzzle Animation

April 4th, 2007

We built this pseudo-3D wooden puzzle with some clever layering. First I’ll explain how the effect was achieved, then explain the code behind it.

You need to upgrade your Flash Player.

Source code (1.45 MB) Flash MX 2004

Creating the Graphics

Steps involved in creating up the graphic of the puzzle pieces
  1. We began by drawing the puzzle top view in Flash then skewing it to add some perspective.
  2. Each piece is built up to appear thick and simulate depth.
  3. The pieces are then exported from Flash as images and a wood texture is added in Photoshop to finish the effect.
  4. Every piece is saved as an individual image, cropped closely.
  5. Back in Flash, the images are applied to their corresponding piece as a bitmap fill and adjusted to fit (if needed).

Layering in Flash

The tricky part is layering the pieces so they appear to slot together and create the 3D illusion.

Each piece is split into a base movieclip and separate movieclips for each join. When examining each join, we needed to figure out which parts would move in front of the other parts.

Diagram illustrating how the pieces are split before layering

In both of the examples above, the blue area becomes bottom layer, with orange, yellow then green above it respectively.

Once all the joins from each piece have been layered in the correct order, they need to be labeled. The names we chose are made from 3 characters - a “p” and two digits (eg. p12, p31, p44).

The first digit is the puzzle piece number: from left to right, top to bottom (piece 1 is in the top left, piece 9 is the bottom right). The second digit is a sequential number for the separate movieclips that form a puzzle piece, with each new puzzle piece starting at 1.

Animating

In the FLA

A Piece class is created to hold the behaviour of the puzzle pieces. In the second frame of the FLA, nine Piece objects are created - one for each puzzle piece. The number of movieclip parts that make up each Piece are passed into the new object. Basically, a puzzle piece is made of many (movieclip) parts:

var parts:Array = [3,4,3,4,5,4,3,4,3];
var pieces:Array = [];
for (var n=0; n<9; n++){
  var pieceNum:Number = n + 1;
  var numParts:Number = parts[n];
  pieces[n]=new Piece(pieceNum, numParts);
}
The Constructor

Inside Piece.as, each Piece object contains a _parts array. This array holds references to all the movieclip parts that make up that puzzle piece. It also contains each part’s _y position on the stage, relative to the first part of that piece:

public function Piece(pieceNum:Number, numParts:Number){
  ...
  for (var i=1; i<=numParts; i++) {
    var mc:MovieClip = _root["p" + pieceNum + i];
    mc.onPress = Delegate.create(this, this.onPress);
    ...
    if (i == 1) this._startY = mc._y;
    var y:Number = mc._y - this._startY;
    var part:Object = {mc:mc, y:y};
    this._parts.push(part);
  }
}

Each of the mouse events and the onEnterFrame are assigned using the Delegate class to solve scoping issues.

Still inside the Piece constructor, each piece is told to ignore mouse events:

this.isEnabled = false;

Which calls this setter to loop through all the movieclip parts and disable them:

public function set isEnabled(bool:Boolean):Void {
  for (var i in this._parts) {
    this._parts[i].mc.enabled = bool;
  }
  ...
}

The next action in the constructor is to set each puzzle piece to call a drop method sequentially, starting from the bottom right moving to the top left:

var dropTime = (9 - pieceNum) * 1000;
_global['setTimeout'](this, 'drop', dropTime);

setTimeout should seem familiar - unlike it’s sister function setInterval, it only gets called once after a specified interval. I guess that makes setTimeout the ugly sister…

The final action in the constructor is setting the lift property to raise the piece off the top of the screen - ready to be dropped:

this.lift = random(150) + 600;

The lift setter moves each movieclip part relative to the resting position (positive lift moves the Piece upwards):

public function set lift(num:Number):Void {
  for (var i in this._parts) {
    this._parts[i].mc._y = this._parts[i].y + this._startY - num;
  }
  ...
}
Gravity

When the drop method is invoked, it quite simply sets the acceleration of the Piece to -1:

private function drop():Void {
  this.acc = -1;
}

The setter for the acc attaches (or removes) the onEnterFrame from the first movieclip part in the _parts array. If the acceleration is stopped (set to null), it also enables mouse events on that puzzle piece for the first time:

public function set acc(num:Number):Void {
  if (num == null) {
    this.isEnabled = true;
    delete this._parts[0].mc.onEnterFrame;		
  } else {
    this._parts[0].mc.onEnterFrame = Delegate.create(this, this.onEnterFrame);
  }
  ...
}

The onEnterFrame calls the fall method…

private function onEnterFrame():Void {
  this.fall();
}

…which is where all the animation happens. The first few lines of code move the puzzle piece towards it’s resting position at an accelerated rate (GRAVITY default of 0.3):

private function fall():Void {
  if (this.lift > 0) {
    this.acc -= GRAVITY;
  } else if (this.lift < 0) {
    this.acc += GRAVITY;
  }
  this.lift += this.acc;
  ...

The remaining code in the fall method does two things. Firstly, it snaps the piece to it’s resting position when it gets within the SNAP threshold (default of 1) and removes the onEnterFrame.

Secondly, it bounces the puzzle piece off the surface based on the FIRMNESS modifier (default of 0.3). FIRMNESS of 1 is minimal bounce and any greater than that will make it bounce higher on impact. The surface bounce only effects the piece when it goes below it’s resting point and is moving downwards:

  ...
  if ((Math.abs(this.acc) < SNAP) && (Math.abs(this.lift) < SNAP)){
    this.lift = 0;
    this.acc = null;
  } else if ((this.acc < 0) && (this.lift < 0)) {
    this.acc -= (this.acc * FIRMNESS);
  }
}

That wraps it up. Post a comment if you make use of this technique - I’ll be interested to see how it can evolve.

Tags: ,
Creative Commons License

One Response to “Wooden Puzzle Animation”

  1. Kristiaan Says:

    Hello,
    I would like to use this animation on the front-page of a non-profit organization in Belgium, Europe, that is acting as a focal point for HSP (Highly Sensitive Persons) in the Dutch speaking part. I’m helping them setup a new look and feel, where this wooden puzzle would visually demonstrate the effect a lot of people have getting to know this information.
    For this purpose I would like to know to whom I can attribute this nice work.
    Thank you and kind regards.
    Kristiaan De Paepe

Leave a Reply