It's ActiveTuts+'s first birthday! Let's celebrate by making a musical 3D birthday card using Papervision3D and TweenMax.
Step 1: Files Setup
Start by creating a new folder (this will be your project's root folder) and create a new AS3 FLA inside that folder, called Main.fla. This will contain all the UI we need in order to create the card.
For this tutorial we will need two popular Flash libraries: Papervision3D and TweenMax. Download Papervision (I'm using version 2.1.932) and extract the com and nochump folders from the zip to your project's root folder. Download TweenMax (I'm using version 11), and extract the zip to your project's root folder as well.
Now create an empty AS file; save it as "CardViewport.as" in your project's root folder. This will be our papervision3d viewport containing the card. Create another empty AS file, in the same folder, named "Main.as." This will be your document class. (Not sure how to use a document class? Read this quick introduction.)
Your folder structure should look like this:

Step 2: Stage Setup
Open up Main.fla and edit the stage to make it 500x440 with a 30fps frame rate. Set the document class to Main:

Step 3: Layers Setup
We only need two layers for this tutorial:
- The card container which will be populated by our papervision
- The buttons layer which will trigger an open/close action on the card

Step 4: Viewport Setup
In the viewport layer create a rectangular shape. Size it to the size of the stage and move it to position 0,0. Hit F8 to convert the shape to a MovieClip symbol. Check the box to export for ActionScript and add the class name CardViewport. It will connect to the class we will build later on.

Now click on the viewport movieClip and give it an instance name of "viewport".

Step 5: Viewport Background
Open the viewport movie clip symbol and create whatever background you like. This is what I did: A couple of gradient shapes, one for the sky and one for the ground and the ActiveTuts+ logo centered on the sky.

Step 6: Button Setup
On the button layer create a rectangular shape. Convert it to a movie clip symbol and call it "open_mc"; make sure you give it the same instance name.
Inside open_mc make a textfield. Set it to be dynamic, give it an instance name of label_txt and embed the fonts by clicking "Character Embedding" and selecting the alphabet you want to use.

Make it look however you want. I built mine with a gradient and some diagonal lines running across:

NOTE: I do not own the right to distribute standard 07_66, the font I'm using in this project. Sorry for that.
Step 7: Assets
Photoshop time: We will build our card with four planes (flat rectangles) facing each other. For that we need four different image files to map into those planes.
Our planes will be 700x900 and our four images will therefore also be of that size. The reason for this size is because the planes will be floating on 3d space some distance away from the camera; they need to be big so that we can see them. Name the images "page_1_front.jpg", "page_1_back.jpg", "page_2_front.jpg", "page_2_back.jpg" and put them in the root folder of your project.

Here are my images:

These images are included in the main source zip, but you can also download them directly if you prefer..
Do the same for a sound if you want to add one. Scott Wills from audiojungle was kind enough to put this funky track together for us.
Step 8: CardViewport.as Imports
Open CardViewport.as. I will be using FlashDevelop to code, but you can use Flash if you prefer.
Create a package and import the classes we need:
package { //basic classes import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; //papervision classes import org.papervision3d.objects.primitives.Plane; import org.papervision3d.materials.BitmapFileMaterial; import org.papervision3d.view.BasicView; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.render.QuadrantRenderEngine; //tweening engine import com.greensock.TweenMax;
Step 9: CardViewport.as Private Variables
Set up the private variables we'll need for the CardViewport:
public class CardViewport extends BasicView //BasicView its a class that includes a basic viewport setup for our scene { //Creates 4 planes private var front_cover:Plane; private var front_inside:Plane; private var back_cover:Plane; private var back_inside:Plane; //Creates 4 materials private var front_cover_img:BitmapFileMaterial; private var front_inside_img:BitmapFileMaterial; private var back_cover_img:BitmapFileMaterial; private var back_inside_img:BitmapFileMaterial; //Creates the front page container, that will nest 2 planes private var front_container:DisplayObject3D; //Creates the back page container, that will nest the other 2 planes private var back_container:DisplayObject3D; //Creates a card container that will nest the front container and the back container private var card_container:DisplayObject3D; }
Step 10: CardViewport.as Constructor
Here's the function that will be run when the CardViewport is created:
public function CardViewport () { super(); //to get rid of a few errors about how z-depth is calculated in our scene //we need to change the render type to one that can correct it setRenderType(); //Instantiates the materials and sets its properties setMaterials(); //Instantiates the planes, adds the materials to the planes, adds the planes to the wrappers (containers) setPlanes(); //renders the camera every frame stage.addEventListener(Event.ENTER_FRAME, render); //repositions the planes based on mouse's position stage.addEventListener(MouseEvent.MOUSE_MOVE, positionPlanes); }
Step 11: CardViewport.as Render Type
To get rid of a few errors about how z-depth is calculated in our scene we need to change the render type to one that can correct. The QuadrantRenderEngine accepts one parameter: the correction type. We will be correcting the z filter this time, use this wisely as it can hog your CPU in complex situations.
private function setRenderType() { this.renderer = new QuadrantRenderEngine(QuadrantRenderEngine.CORRECT_Z_FILTER) }
Step 12: CardViewport.as Materials Setup
Even though we don't see the fourth plane (the back of the card) I decided to add it anyway just in case you want to play around with the scene.
We will be using a BitmapFileMaterial, by setting "precise" to true I'm improving the overall quality, however I'm also using more CPU power.
private function setMaterials():void { //Front Cover Page front_cover_img = new BitmapFileMaterial("page_1_front.jpg",true); front_cover_img.doubleSided = false; front_cover_img.fillAlpha = 1.0; //Front Inside Page front_inside_img = new BitmapFileMaterial("page_1_back.jpg",true); front_inside_img.doubleSided = false; front_inside_img.fillAlpha = 1.0; //Back Inside Page back_inside_img = new BitmapFileMaterial("page_2_front.jpg",true); back_inside_img.doubleSided = false; back_inside_img.fillAlpha = 1.0; //Back Cover Page back_cover_img = new BitmapFileMaterial("page_2_back.jpg"); back_cover_img.doubleSided = false; back_cover_img.fillAlpha = 1.0; }
Step 13: CardViewport.as Planes Setup
Here we create the four planes which make up our card, arranging them in a pair of containers so they appear as two pages.
private function setPlanes():void { //Creates the front Plane Cover front_cover = new Plane( front_cover_img, 700, 900, 3, 3 ); front_cover.z = 0; front_cover.x = 350//this offset will be used later so we can open the card //Creates the inside of the front Plane front_inside = new Plane( front_inside_img, 700, 900, 3, 3 ); front_inside.z = 0; front_inside.rotationY = 180;//rotated 180 degrees so we can see it from the inside front_inside.x = 350//its exactly half the width of the plane //Creates a wrapper object so we can rotate the front page from a different axis //that of the pages. It's simpler this way as you don't have to mess up with the creation //coordinates and we can just offset it. front_container = new DisplayObject3D(); front_container.addChild(front_cover); front_container.addChild(front_inside); front_container.x-=350//here we are offsetting it //Repeat the process for the second page back_inside = new Plane( back_inside_img, 700, 900, 3, 3 ); back_inside.z = 0.1; back_inside.x = 350 back_cover = new Plane( back_cover_img, 700, 900, 3, 3 ); back_cover.z = 0.1; back_cover.rotationY = 180; back_cover.x = 350 back_container = new DisplayObject3D(); back_container.addChild(back_inside); back_container.addChild(back_cover); back_container.x -= 350 //And after everything add the front and back containers to the main wrapper and add the wrapper to the scene. card_container = new DisplayObject3D(); card_container.addChild(front_container) card_container.addChild(back_container) scene.addChild( card_container ); }
Step 14: CardViewport.as openCard()
Notice that this is a public function, so we can access it through the Main document class when we click the button:
public function openCard() { //When we open the card we need to remove the mouse listeners from stage because //when we mouse move we rotate stuff, sometimes the containers sometimes the planes themselves, //therefore we cannot rotate both simultaneously as we will end up with animation errors stage.removeEventListener(MouseEvent.MOUSE_MOVE, positionPlanes); //we need to reset the planes' positions inside the containers resetPlanesPosition(); //we rotate the front container 180 degrees and set the x to 0, //because we offset the planes earlier it now rotates accordingly. TweenMax.to(front_container, 1.4, { rotationY:180, x:0,onComplete:function(){ stage.addEventListener(MouseEvent.MOUSE_MOVE, positionContainer); } } ); TweenMax.to(back_container, 1.4, { x:0 } ); //after the rotation we add a new listener to the mouse. this time to reposition the main container. }
Step 15: CardViewport.as closeCard()
Same for the closing of the card:
public function closeCard() { //when we close the card the steps are very similar to when we open. stage.removeEventListener(MouseEvent.MOUSE_MOVE, positionContainer); //difference is where we reset the main container's rotation card_container.rotationY = card_container.rotationX = 0 //we reset the front and back containers' x and rotationY properties TweenMax.to(front_container, 1.4, { rotationY:0, x:-350, onComplete:function(){ stage.addEventListener(MouseEvent.MOUSE_MOVE, positionPlanes); } } ); TweenMax.to(back_container, 1.4, { x: -350 } ); //and after the animation we add the previous mouse move event action to position the planes }
Step 16: CardViewport.as Position Planes
This is called whenever the mouse is moved while the card is closed:
private function positionPlanes(event:Event):void { //it rotates between -25º and 25º depending on the mouse position front_cover.rotationY = back_inside.rotationY = (stage.mouseX / stage.stageWidth * 50)-25 front_cover.rotationX = front_inside.rotationX = back_cover.rotationX = back_inside.rotationX = (stage.mouseY / stage.stageHeight * 50) - 25 //we only need to add 180 to the faces that are supposed to be against us front_inside.rotationY = back_cover.rotationY = ((stage.mouseX / stage.stageWidth * 50) - 25) + 180 }
Step 17: CardViewport.as Position Container
This is called whenever the mouse is moved while the card is open:
private function positionContainer(event:Event):void { //just like the planes. it rotates between -25º and 25º depending on the mouse position card_container.rotationY = (stage.mouseX / stage.stageWidth * 50)-25 card_container.rotationX = (stage.mouseY / stage.stageHeight * 50) - 25 }
Step 18: CardViewport.as Reset Planes
This is called when the card is clicked to open:
private function resetPlanesPosition() { //the function name sais it all, it rotates the planes back to its original created position front_inside.rotationY = back_cover.rotationY = 180 front_cover.rotationY = back_inside.rotationY = front_cover.rotationX = front_inside.rotationX = back_cover.rotationX = back_inside.rotationX =0 }
Step 19: CardViewport.as Render
The last step is obviously rendering out the viewport, after that we move on to the document class.
private function render(event:Event):void { //singleRender is a BasicView function that activates the render in that given time. singleRender(); }
That's the end of the class. Don't forget to add your closing curly braces.
Step 20: CardViewport.as Full Code
We won't change anything else in the CardViewport class, so feel free to get the source from the zip file and compare it with your own.
Step 21: Main.as Imports
This step is fairly simple, we just need to associate the open card and close card actions to the button. Before that, let's import the following classes into our package:
package { import flash.display.MovieClip; import flash.events.Event; import flash.events.MouseEvent; import flash.media.Sound; //remember the birthday.mp3? it will be added here import flash.net.URLRequest;
Step 22: Main.as Constructor
Again, nothing fancy, it extends MovieClip and creates a Sound object:
public class Main extends MovieClip { private var birthday_song:Sound }
Step 23: Main.as Main function
Here's a little tip for you guys: to prevent null errors initialize your commands only after the class been added to the stage. This way you are sure everything you are trying to access is present.
public function Main():void { addEventListener(Event.ADDED_TO_STAGE,init) }
Step 24: Main.as Init function
...and this is the function that will be run when the document class has been added to the stage:
private function init(event:Event):void { removeEventListener(Event.ADDED_TO_STAGE, init) //we don't need this anymore open_mc.label_txt.text = "_!OPEN CARD!_" //Labels the button open_mc.buttonMode = true; //Sets the button to act like a button open_mc.mouseChildren = false; //Disables mouse events on open_mc children open_mc.addEventListener(MouseEvent.CLICK, openCard) //Adds an event listener to the mouse click }
Step 25: Main.as Click to Open Handler
This function will be run when the card is clicked if it is closed:
private function openCard(event:MouseEvent):void { open_mc.removeEventListener(MouseEvent.CLICK, openCard); //removes the open listener viewport.openCard(); //access the viewport to open the card (remember we set it to public earlier?) open_mc.label_txt.text = "_!CLOSE CARD!_" // changes the label open_mc.addEventListener(MouseEvent.CLICK, closeCard) //adds a listener to close the card on mouse click birthday_song = new Sound(new URLRequest("birthday.mp3")); //loads the birthday song birthday_song.play(); //plays the birthday song }
Step 26: Main.as Click to Close Handler
This is basically the same as openCard(), but in reverse. ;)
private function closeCard(event:MouseEvent):void { open_mc.removeEventListener(MouseEvent.CLICK, closeCard) viewport.closeCard(); open_mc.label_txt.text = "_!OPEN CARD!_" open_mc.addEventListener(MouseEvent.CLICK, openCard) }
Step 27: Main.as Finish Off
Finished. No secrets in this document class, it's pretty straight forward. Don't forget to add your closing curly braces.
Conclusion
I would like to see you evolve from this tutorial taking up some of the little tips I gave you, from the ADDED_TO_STAGE to the displayObject3d wrapper, to using a tweening class to rotate and move a 3d object. Take this card and make it your own.
Happy birthday Activetuts+! I hope you liked this tutorial, thanks for reading!
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post