3D Flash Card Flip Tutorial

Card Flip Tutorial

Card Flip Tutorial

One effect I need to use often in my Flash applications is a very simple card flip. The best way to simulate this effect is to use Papervision3D to make a realistic three dimensional illusion. For this demonstration we’re going to create a playing card that will flip to the other side when the user clicks on it.

First, download the Papervision3D package from their website:
http://blog.papervision3d.org/

You will also need the AS3 Tweener package for smooth animation tweens:
http://code.google.com/p/tweener/

This demonstration makes use of several classes from the RAD ActionScript package. This is currently in beta and will be released in the near future.

You can use your own images for the card front and back, or you can download the whole source here:
card_flip_tutorial.zip

Open up Flash and create a new 640×480 document. Import the front and back images – these should ideally be the same size. Create symbols out of each image, and name them ‘CardFront’ and ‘CardBack’ respectively. Right-click on each symbol in the library and check ‘Export for ActionScript’. Have the base class extend ‘flash.display.Sprite’.

CardFront Properties

CardFront Properties

Note: This could just as easily have been done entirely in ActionScript using a Loader, but by using a Library Symbol it illustrates that this can be done with much more than simple images. You can have form controls, video, or even other MovieClips within your flippable symbol.

Next it’s onto the ActionScript. Open up your favorite ActionScript editor and create a new class named ‘PapervisionScene’. This is a generic extension of the Sprite class that will make it easy for us to setup a full Papervision3D scene, complete with camera, viewport, and renderers. Alternatively, you can use the ‘com.rad.papervision3d.PapervisionScene’ class included in the source files and skip ahead to the good part.

[code lang=”as3″ collapse=”true”]
package com.rad.papervision3d {

import flash.display.Sprite;
import flash.events.Event;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.render.QuadrantRenderEngine;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.view.Viewport3D;
import org.papervision3d.render.BasicRenderEngine;

public class PapervisionScene extends Sprite {

private var _scene:Scene3D; // the 3d scene
private var _cameraPivot:DisplayObject3D; // the origin (0, 0, 0)
private var _camera:Camera3D; // the main camera
private var _viewport:Viewport3D; // the viewport
private var _renderer:BasicRenderEngine; // basic renderer (faster)
private var _quadRenderer:QuadrantRenderEngine; // quad renderer (better)
private var _renderAsQuad:Boolean; // flag to determine render method

public function PapervisionScene(viewportWidth:int = 800, viewportHeight:int = 600, autoScaleToStage:Boolean = false) {

// default to quad rendering for accuracy
_renderAsQuad = true;

// create the objects
_scene = new Scene3D();
_cameraPivot = new DisplayObject3D();
_scene.addChild(_cameraPivot);
_camera = new Camera3D();
_viewport = new Viewport3D(viewportWidth, viewportHeight, autoScaleToStage, true, true, true);
_renderer = new BasicRenderEngine();
_quadRenderer = new QuadrantRenderEngine(QuadrantRenderEngine.CORRECT_Z_FILTER);

// add the viewport to the stage
addChild(_viewport);

// render the scene every new frame
addEventListener(Event.ENTER_FRAME, onEnterFrame);

}

// getters and setters
public function get scene():Scene3D { return _scene; }
public function get camera():Camera3D { return _camera; }
public function get viewport():Viewport3D { return _viewport; }
public function get cameraPivot():DisplayObject3D { return _cameraPivot; }

public function get renderAsQuad():Boolean { return _renderAsQuad; }
public function set renderAsQuad(r:Boolean):void { _renderAsQuad = r; }

/*
* add an object to the 3d scene
*/
public function addToScene(o:DisplayObject3D):void {
_scene.addChild(o);
}

/*
* render the scene to the stage
*/
private function onEnterFrame(e:Event):void {
if (_renderAsQuad) {
_quadRenderer.renderScene(_scene, _camera, _viewport);
} else {
_renderer.renderScene(_scene, _camera, _viewport);
}
}

}

}
[/code]

At a glance, we’re adding the following objects to the Sprite by default:

[code lang=”as3″]
_scene = new Scene3D();
_cameraPivot = new DisplayObject3D();
_scene.addChild(_cameraPivot);
_camera = new Camera3D();
_viewport = new Viewport3D(viewportWidth, viewportHeight, autoScaleToStage, true, true, true);
_renderer = new BasicRenderEngine();
_quadRenderer = new QuadrantRenderEngine(QuadrantRenderEngine.CORRECT_Z_FILTER);
addChild(_viewport);
[/code]

We also tell the Sprite to render the scene on every frame using our preferred render engine:

[code lang=”as3″]
private function onEnterFrame(e:Event):void {
if (_renderAsQuad) {
_quadRenderer.renderScene(_scene, _camera, _viewport);
} else {
_renderer.renderScene(_scene, _camera, _viewport);
}
}
[/code]

Now we’re going to create the core card flipping class. Create a new class named ‘CardFlipScene’ that again extends Sprite.

[code lang=”as3″ collapse=”true”]
package com.rad.papervision3d {

import caurina.transitions.Tweener;
import flash.display.BitmapData;
import flash.display.Sprite;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.materials.BitmapMaterial;
import org.papervision3d.objects.primitives.Plane;

public class CardFlipScene extends Sprite {

private var _side1_mc:Sprite;
private var _side2_mc:Sprite;
private var _scene:PapervisionScene;
private var _plane1:Plane;
private var _plane2:Plane;
private var _isFlipped:Boolean;
private var _isFlipping:Boolean;
private var _isInteractive:Boolean;

public function CardFlipScene(side1:Sprite, side2:Sprite, interactive:Boolean = false) {

// save the source sprites
_side1_mc = side1;
_side2_mc = side2;

// save interactive flag
_isInteractive = interactive;

// determine the sprite dimensions
var planeWidth:Number = _side1_mc.width;
var planeHeight:Number = _side1_mc.height;

// calculate the scene size
var sceneWidth:Number = planeWidth * 2;
var sceneHeight:Number = planeHeight * 2;

// create the scene
_scene = new PapervisionScene(sceneWidth, sceneHeight);
_scene.camera.zoom = 115;
_scene.x = -sceneWidth / 2;
_scene.y = -sceneHeight / 2;
addChild(_scene);

// capture the first material
var bmp1:BitmapData = new BitmapData(planeWidth, planeHeight, true, 0);
bmp1.draw(_side1_mc);
var material1:BitmapMaterial = new BitmapMaterial(bmp1);
material1.interactive = true;
material1.smooth = true;

// create the front side
_plane1 = new Plane(material1, planeWidth, planeHeight, 10, 10);
_scene.addToScene(_plane1);

// capture the second material
var bmp2:BitmapData = new BitmapData(planeWidth, planeHeight, true, 0);
bmp2.draw(_side2_mc);
var material2:BitmapMaterial = new BitmapMaterial(bmp2);
material2.interactive = true;
material2.smooth = true;

// create the back side
_plane2 = new Plane(material2, planeWidth, planeHeight, 10, 10);
_plane2.rotationY = 90;
_scene.addToScene(_plane2);

// add rollovers to the planes
if (_isInteractive) var ip1:InteractiveObject = new InteractiveObject(_plane1, _scene.viewport);
if (_isInteractive) var ip2:InteractiveObject = new InteractiveObject(_plane2, _scene.viewport);

// add listeners for clicks
_plane1.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onObjectClick);
_plane2.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onObjectClick);

}

/*
* get the current card object (read only)
*/
public function get card():Plane {
if (_isFlipped) return _plane2;
else return _plane1;
}

/*
* flip to the opposite side
*/
public function flip(delay:Number = 0, time:Number = 1, clockwise:Boolean = true):void {
if (_isFlipped) flipToFront(delay, time, clockwise);
else flipToBack(delay, time, clockwise);
}

/*
* flip to the back
*/
public function flipToBack(delay:Number = 0, time:Number = 1, clockwise:Boolean = true):void {

_isFlipping = true;

// determine the time for half a flip
var halfTime:Number = time / 2;

// setup start and end rotations based on spin direction
var rotBegin1:Number = 0;
var rotEnd1:Number = -90;
var rotBegin2:Number = 90;
var rotEnd2:Number = 0;
if (clockwise) {
rotEnd1 = 90;
rotBegin2 = -90;
}

// snap planes to start rotations
_plane1.rotationY = rotBegin1;
_plane2.rotationY = rotBegin2;

// animate the planes
Tweener.addTween(_plane1, {rotationY:rotEnd1, delay:delay, time:halfTime, transition:"linear"});
Tweener.addTween(_plane2, {rotationY:rotEnd2, delay:(delay + halfTime), time:halfTime, transition:"linear", onComplete:finishFlip});

// save the flip state
_isFlipped = true;

}

/*
* flip to the front
*/
public function flipToFront(delay:Number = 0, time:Number = 1, clockwise:Boolean = true):void {

_isFlipping = true;

// determine the time for half a flip
var halfTime:Number = time / 2;

// setup start and end rotations based on spin direction
var rotBegin1:Number = 90;
var rotEnd1:Number = 0;
var rotBegin2:Number = 0;
var rotEnd2:Number = -90;
if (clockwise) {
rotBegin1 = -90;
rotEnd2 = 90;
}

// snap planes to start rotations
_plane1.rotationY = rotBegin1;
_plane2.rotationY = rotBegin2;

// animate the planes
Tweener.addTween(_plane2, {rotationY:rotEnd2, delay:delay, time:halfTime, transition:"linear"});
Tweener.addTween(_plane1, {rotationY:rotEnd1, delay:(delay + halfTime), time:halfTime, transition:"linear", onComplete:finishFlip});

// save the flip state
_isFlipped = false;

}

/*
* finish the flip motion
*/
private function finishFlip():void {
_isFlipping = false;
}

/*
* dispatch a click event
*/
private function onObjectClick(e:InteractiveScene3DEvent):void {
if (!_isFlipping && _isInteractive) dispatchEvent(new InteractiveScene3DEvent(e.type));
}

}

}
[/code]

First, we add a PapervisionSprite object to the stage:

[code lang=”as3″]
// create the scene
_scene = new PapervisionScene(sceneWidth, sceneHeight);
_scene.camera.zoom = 115;
_scene.x = -sceneWidth / 2;
_scene.y = -sceneHeight / 2;
addChild(_scene);
[/code]

Next, we are adding two Papervision3D Plane objects to the scene, grabbing the BitmapData from the two Sprites passed to the constructor, and mapping the generated BitmapMaterials to the Planes:

[code lang=”as3″]
// capture the first material
var bmp1:BitmapData = new BitmapData(planeWidth, planeHeight, true, 0);
bmp1.draw(_side1_mc);
var material1:BitmapMaterial = new BitmapMaterial(bmp1);
material1.interactive = true;
material1.smooth = true;

// create the front side
_plane1 = new Plane(material1, planeWidth, planeHeight, 10, 10);
_scene.addToScene(_plane1);

// capture the second material
var bmp2:BitmapData = new BitmapData(planeWidth, planeHeight, true, 0);
bmp2.draw(_side2_mc);
var material2:BitmapMaterial = new BitmapMaterial(bmp2);
material2.interactive = true;
material2.smooth = true;

// create the back side
_plane2 = new Plane(material2, planeWidth, planeHeight, 10, 10);
_plane2.rotationY = 90;
_scene.addToScene(_plane2);
[/code]

We also create a flipToBack and flipToFront method to easily tell the scene to flip the card. We are using the Tweener library to smoothly animate the rotation of the Planes over time:

[code lang=”as3″]
/*
* flip to the back
*/
public function flipToBack(delay:Number = 0, time:Number = 1, clockwise:Boolean = true):void {

_isFlipping = true;

// determine the time for half a flip
var halfTime:Number = time / 2;

// setup start and end rotations based on spin direction
var rotBegin1:Number = 0;
var rotEnd1:Number = -90;
var rotBegin2:Number = 90;
var rotEnd2:Number = 0;
if (clockwise) {
rotEnd1 = 90;
rotBegin2 = -90;
}

// snap planes to start rotations
_plane1.rotationY = rotBegin1;
_plane2.rotationY = rotBegin2;

// animate the planes
Tweener.addTween(_plane1, {rotationY:rotEnd1, delay:delay, time:halfTime, transition:"linear"});
Tweener.addTween(_plane2, {rotationY:rotEnd2, delay:(delay + halfTime), time:halfTime, transition:"linear", onComplete:finishFlip});

// save the flip state
_isFlipped = true;

}
[/code]

You may have noticed that the CardFlipScene class uses an InteractiveObject class to add interactivity to the card:

[code lang=”as3″]
// add rollovers to the planes
if (_isInteractive) var ip1:InteractiveObject = new InteractiveObject(_plane1, _scene.viewport);
if (_isInteractive) var ip2:InteractiveObject = new InteractiveObject(_plane2, _scene.viewport);
[/code]

The InteractiveObject class simply adds event listeners to change the cursor when the mouse is over the object passed to the constructor:

[code lang=”as3″ collapse=”true”]
package com.rad.papervision3d {

import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.view.Viewport3D;

public class InteractiveObject {

private var _viewport:Viewport3D;

public function InteractiveObject(object:DisplayObject3D, viewport:Viewport3D) {
_viewport = viewport;
object.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, onObjectOver);
object.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, onObjectOut);
}

private function onObjectOver(e:InteractiveScene3DEvent):void {
_viewport.buttonMode = true;
}
private function onObjectOut(e:InteractiveScene3DEvent):void {
_viewport.buttonMode = false;
}

}

}
[/code]

We now have a very simple way to add a flippable card to our Movies. Go back to your Flash project, create a new MovieClip symbol in your library named ‘CardFlip’, and set it to ‘Export for ActionScript’ in frame 1. I have it defined as the ‘com.craigphares.tutorials.cardflip.CardFlip’ class which we will write next. It should extend ‘flash.display.Sprite’ as well.

Our entire main ActionScript class is as follows:

[code lang=”as3″ collapse=”true”]
package com.craigphares.tutorials.cardflip {

import caurina.transitions.Tweener;
import com.rad.papervision3d.CardFlipScene;
import flash.display.Sprite;
import org.papervision3d.events.InteractiveScene3DEvent;

public class CardFlip extends Sprite {

private var _scene:CardFlipScene;

public function CardFlip() {

// get the front and back sprites
var front:Sprite = new CardFront();
var back:Sprite = new CardBack();

// create the scene
_scene = new CardFlipScene(front, back, true);
_scene.x = 320;
_scene.y = 240;
addChild(_scene);

// listen for clicks
_scene.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onObjectClick);

// scale up the card when loading
_scene.alpha = 0;
_scene.scaleX = _scene.scaleY = 0;
Tweener.addTween(_scene, {alpha:1, scaleX:1, scaleY:1, delay:1, time:1, transition:"easeOutBack"});

}

/*
* flip the card when clicked
*/
private function onObjectClick(e:InteractiveScene3DEvent):void {
// flip the card in half a second with no delay
_scene.flip(0, 0.5);
}

}

}
[/code]

First, we add the CardFlipScene to the stage and add an event listener for clicks:

[code lang=”as3″]
// create the scene
_scene = new CardFlipScene(front, back, true);
_scene.x = 320;
_scene.y = 240;
addChild(_scene);

// listen for clicks
_scene.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onObjectClick);
[/code]

The onObjectClick event handler just calls our utility function flip(), which is a combination of flipToBack() and flipToFront():

[code lang=”as3″]
/*
* flip the card when clicked
*/
private function onObjectClick(e:InteractiveScene3DEvent):void {
// flip the card in half a second with no delay
_scene.flip(0, 0.5);
}
[/code]

Hopefully, you can find a use for this card flip tutorial, and add some interest to your Flash applications. Feel free to contact me with questions and comments.

* The background photograph used in this demo is courtesy of leafar..