As suspected, gizmodo planted a bug.
This week's example / tutorial is how to mix Papervision with as3isolib and A* (pronounced A Star) for a nice 3D isometric pathfinding experience. The version of A* that I am using comes straight out of Keith Peters book AdvancedEd ActionScript 3.0 Animation. Anyone looking to cover advanced animation topics in Flash should buy a copy of this book. It is excellent.
This tutorial will not cover how to create the A* classes but rather show you how to blend 3 things together; Papervision, as3isolib, and A* (the Keith Peters version). If you're just getting started using as3IsoLib take a look at these great tutorials and download the code: http://code.google.com/p/as3isolib/w/list
We will cover a few of the things that you need to successfully merge the pathfinding and 3D experience.
First item is our camera setting. We are setting ortho to true because this actually gives us a true isometric camera feel.
-
camera.ortho = true;
Inside of our makeGrid function we are setting up the groundwork for our pathfinding and isometric world. This grid has no information about the cellsize, just which parts of it are walkable and not walkable.
-
protected function makeGrid():void
-
{
-
pathGrid = new Grid(10, 10);
-
for(var i:int = 0; i <20; i++)
-
{
-
pathGrid.setWalkable(Math.floor(Math.random() * 8) + 2,
-
Math.floor(Math.random() * 8)+ 2,
-
false);
-
}
-
drawGrid();
-
}
Next we move on to drawGrid() which begins to connect the pathfinding and as3isolib
One of the great thing about as3isolib which is similar to papervision is that it has native objects that you can skin, such as boxes and polygons.
Here inside of drawGrid we run through the columns and rows that we setup in our makeGrid function. From the information in these loops we can get back the node information of the grid and check if an item is walkable or not walkable. If it is walkable we create a standard size box with a height of 0, if it is not walkable we create that same standard box but with a height of 40 so we can start to define our available paths. If the box is walkable we also want to set a mouseEvent so that we can select it. Finally we add these items to our isoScene.
-
for(var i:int = 0; i <pathGrid.numCols; i++)
-
{
-
for(var j:int = 0; j <pathGrid.numRows; j++)
-
{
-
var node:Node = pathGrid.getNode(i, j);
-
var box:IsoBox = new IsoBox();
-
-
if (node.walkable)
-
{
-
box.setSize(cellSize, cellSize, 0);
-
box.addEventListener(MouseEvent.CLICK, onGridItemClick);
-
}
-
else
-
{
-
box.setSize(cellSize, cellSize, 40);
-
}
-
-
box.moveTo(i * cellSize, j * cellSize, 0);
-
isoScene.addChild(box);
-
}
-
}
Another item within the drawGrid function is our playerHelper. We are only using this playerHelper so that we can get it's coordinates to follow along in papervision. We set him to the same size as our box items.
-
playerHelper.setSize(cellSize, cellSize, 10);
The next important step is setting up a way to have our viewport inside of our isoScene. We first create a nice wrapper for our viewport which is a native IsoSprite object. We then associate our viewport as one of the sprites inside of our isoSprite.
-
isoSprite = new IsoSprite();
-
isoSprite.sprites = [viewport];
-
isoScene.addChild(isoSprite);
Notice that we add our sprites kind of like we add filters for a movieclip. We are passing it an array.
-
//isoSprite.sprites = [viewport,moreStuff] - pass in an array of objects
Now lets see what happens when a grid item is clicked. First we gather the information about where we want to go. We gather that by getting the item that was clicked and getting its x & y positions divided by the cellSize. Then we give pathGrid.setEndNode those coordinates. Next we gather the information about where our playerHelper currently is divided by the cellSize and pass that information to pathGrid.setStartNode.
-
protected function onGridItemClick(evt:ProxyEvent):void
-
{
-
var box:IsoBox = evt.target as IsoBox;
-
-
//Get and set End Nodes (where are we going)
-
var xpos:int = (box.x)/cellSize
-
var ypos:int = Math.floor(box.y / cellSize)
-
pathGrid.setEndNode(xpos,ypos );
-
-
//Get and set Start Node (where are we now)
-
xpos = Math.floor(playerHelper.x / cellSize);
-
ypos = Math.floor(playerHelper.y / cellSize);
-
pathGrid.setStartNode(xpos, ypos);
-
-
//Find our path
-
findPath();
-
}
Finally we call findPath(). This is the fun part. First we create a new instance of AStar and gather the path information that we gathered when we ran the onGridItemClick function.
We can now run through that loop and get each x and y portion of that loop. We simply add a tween with a delay that is the same time as our speed. This will allow you to see each move and when it is complete it will start it's next move.
-
protected function findPath():void
-
{
-
var astar:AStar = new AStar();
-
var speed:Number = .3;
-
if(astar.findPath(pathGrid))
-
{
-
path = astar.path;
-
}
-
-
for (var i:int = 0; i <path.length; i++)
-
{
-
var targetX:Number = path[i].x * cellSize;
-
var targetY:Number = path[i].y * cellSize;
-
-
Tweener.addTween(playerHelper, { x:targetX, y:targetY, delay:speed * i , time:speed, transition:"linear" } );
-
}
-
}
We now just need to look at how to sort the papervision with the as3isolib objects
Inside of our onRenderTick function which is an override from BasicView we use this to render our isoScene which is part of as3isolib and change the x,y coordinates of our sphere. We are basically following around the screenX and screenY of our playerHelper. The next and very important part of this is the depth sorting. We are pulling the depth at all times from the playerHelper object and assigning our isoSprite (which has our viewport in it) to that depth. Now we can watch as our 3D objects find their pretty little paths and sort without issue.
-
override protected function onRenderTick(event:Event = null):void
-
{
-
super.onRenderTick(event);
-
isoScene.render();
-
sphere.x = playerHelper.screenX;
-
sphere.y = -playerHelper.screenY - 15;
-
isoScene.setChildIndex(isoSprite, isoScene.getChildIndex(playerHelper));
-
}
Here is the full code:
-
package
-
{
-
import as3isolib.display.IsoSprite;
-
import as3isolib.display.IsoView;
-
import as3isolib.display.primitive.IsoBox;
-
import as3isolib.display.primitive.IsoPrimitive;
-
import as3isolib.display.scene.IsoGrid;
-
import as3isolib.display.scene.IsoScene;
-
import bit101.AStar;
-
import bit101.Grid;
-
import bit101.Node;
-
import caurina.transitions.Tweener;
-
import flash.display.StageAlign;
-
import flash.display.StageScaleMode;
-
import flash.events.Event;
-
-
import eDpLib.events.ProxyEvent;
-
-
import flash.display.Sprite;
-
import flash.events.MouseEvent;
-
-
import org.papervision3d.materials.WireframeMaterial;
-
import org.papervision3d.objects.primitives.Sphere;
-
import org.papervision3d.view.BasicView;
-
-
public class Main extends BasicView
-
{
-
protected var cellSize:int = 50;
-
protected var pathGrid:Grid;
-
protected var playerHelper:IsoPrimitive;
-
protected var path:Array;
-
protected var isoSprite:IsoSprite;
-
protected var isoView:IsoView;
-
protected var isoScene:IsoScene;
-
protected var sphere:Sphere;
-
-
public function Main()
-
{
-
super(800, 600, false);
-
stage.align = StageAlign.TOP_LEFT;
-
stage.scaleMode = StageScaleMode.NO_SCALE;
-
-
create3D();
-
makeGrid();
-
startRendering();
-
}
-
-
protected function create3D():void
-
{
-
sphere = new Sphere(new WireframeMaterial(),20);
-
scene.addChild(sphere);
-
camera.ortho = true;
-
}
-
-
protected function makeGrid():void
-
{
-
pathGrid = new Grid(10, 10);
-
for(var i:int = 0; i <20; i++)
-
{
-
pathGrid.setWalkable(Math.floor(Math.random() * 8) + 2,
-
Math.floor(Math.random() * 8)+ 2,
-
false);
-
}
-
drawGrid();
-
}
-
-
protected function drawGrid():void
-
{
-
isoScene = new IsoScene();
-
playerHelper = new IsoPrimitive();
-
isoSprite = new IsoSprite();
-
isoView = new IsoView();
-
-
for(var i:int = 0; i <pathGrid.numCols; i++)
-
{
-
for(var j:int = 0; j <pathGrid.numRows; j++)
-
{
-
var node:Node = pathGrid.getNode(i, j);
-
var box:IsoBox = new IsoBox();
-
-
if (node.walkable)
-
{
-
box.setSize(cellSize, cellSize, 0);
-
box.addEventListener(MouseEvent.CLICK, onGridItemClick);
-
}
-
else
-
{
-
box.setSize(cellSize, cellSize, 40);
-
}
-
-
box.moveTo(i * cellSize, j * cellSize, 0);
-
isoScene.addChild(box);
-
}
-
}
-
-
//Set properties for player helper
-
playerHelper.setSize(cellSize, cellSize, 10);
-
-
//Set properties for isoView
-
isoView.setSize(stage.stageWidth, stage.stageHeight);
-
-
//Set proper position for viewport
-
viewport.x = -stage.stageWidth / 2;
-
viewport.y = -stage.stageHeight / 2;
-
-
//Add viewport to the isoSprite
-
isoSprite.sprites = [viewport];
-
-
//Add the isoSprite and playerHelper to the isoScene
-
isoScene.addChild(isoSprite);
-
isoScene.addChild(playerHelper);
-
-
//Add the isoScene to the isoView
-
isoView.addScene(isoScene);
-
-
//Add the isoView to the stage
-
addChild(isoView);
-
}
-
-
protected function onGridItemClick(evt:ProxyEvent):void
-
{
-
var box:IsoBox = evt.target as IsoBox;
-
-
//Get and set End Nodes (where are we going)
-
var xpos:int = (box.x)/cellSize
-
var ypos:int = Math.floor(box.y / cellSize)
-
pathGrid.setEndNode(xpos,ypos );
-
-
//Get and set Start Node (where are we now)
-
xpos = Math.floor(playerHelper.x / cellSize);
-
ypos = Math.floor(playerHelper.y / cellSize);
-
pathGrid.setStartNode(xpos, ypos);
-
-
//Find our path
-
findPath();
-
}
-
-
protected function findPath():void
-
{
-
var astar:AStar = new AStar();
-
var speed:Number = .3;
-
if(astar.findPath(pathGrid))
-
{
-
path = astar.path;
-
}
-
-
for (var i:int = 0; i <path.length; i++)
-
{
-
var targetX:Number = path[i].x * cellSize;
-
var targetY:Number = path[i].y * cellSize;
-
-
Tweener.addTween(playerHelper, { x:targetX, y:targetY, delay:speed * i , time:speed, transition:"linear" } );
-
}
-
}
-
-
override protected function onRenderTick(event:Event = null):void
-
{
-
super.onRenderTick(event);
-
-
//Render the isoScene
-
isoScene.render();
-
-
//Place our 3D object at the correct location (following our playerHelper)
-
sphere.x = playerHelper.screenX;
-
sphere.y = -playerHelper.screenY - 15;
-
-
/*
-
-
Set the depth of our isoSprite which holds our viewport to the proper depth.
-
-
NOTE: as3isolib objects - the depth sorting is done automatically,
-
we just need to tap into that
-
-
*/
-
-
isoScene.setChildIndex(isoSprite, isoScene.getChildIndex(playerHelper));
-
}
-
}
-
}
As a simple addition to yesterday's coverflow post, I wanted to show the same example but with loading images via XML. For simplicity sake the XML is loaded and parsed all in the main file.
The structure for our XML is very simple:
-
<?xml version="1.0" encoding="utf-8" ?>
-
<data>
-
<image><![CDATA[images/image1.jpg]]></image>
-
<image><![CDATA[images/image2.jpg]]></image>
-
<image><![CDATA[images/image3.jpg]]></image>
-
<image><![CDATA[images/image5.jpg]]></image>
-
<image><![CDATA[images/image4.jpg]]></image>
-
<image><![CDATA[images/image6.jpg]]></image>
-
<image><![CDATA[images/image7.jpg]]></image>
-
</data>
We simply load our XML file with BulkLoader
-
protected function loadXML():void
-
{
-
bulkInstance = new BulkLoader("bulkInstance");
-
bulkInstance.add(xmlPath);
-
bulkInstance.addEventListener(BulkProgressEvent.COMPLETE, onXMLReady);
-
bulkInstance.start();
-
}
Parse the results, now adding the images to the BulkLoader:
-
protected function onXMLReady(evt:BulkProgressEvent):void
-
{
-
bulkInstance.removeEventListener(BulkProgressEvent.COMPLETE, onXMLReady);
-
bulkInstance.addEventListener(BulkProgressEvent.COMPLETE, onImagesReady);
-
-
var xml:XML = bulkInstance.getXML(xmlPath);
-
var xmlList:XMLList = xml.image;
-
-
for (var i:int = 0; i <xmlList.length(); i++)
-
{
-
var imagePath:String = String(xmlList[i])
-
bulkInstance.add(imagePath);
-
-
//Add path to array for later access
-
images.push(imagePath);
-
}
-
-
//Set our number of items based on how many images we load
-
numItems = images.length;
-
}
When our images are ready we can continue the process of setting up our coverflow. But now using the images we just loaded for our materials.
-
var mat:BitmapMaterial = new BitmapMaterial(bulkInstance.getBitmapData(images[i]));
It's that simple. Be sure to let us know if you find this useful and are able to use it in a project.
Here is the full code:
-
package
-
{
-
import br.com.stimuli.loading.BulkLoader;
-
import br.com.stimuli.loading.BulkProgressEvent;
-
import flash.display.Sprite;
-
import flash.events.MouseEvent;
-
import flash.filters.GlowFilter;
-
import gs.easing.Quint;
-
import gs.TweenLite;
-
import org.papervision3d.events.InteractiveScene3DEvent;
-
import org.papervision3d.materials.BitmapMaterial;
-
import org.papervision3d.objects.DisplayObject3D;
-
import org.papervision3d.objects.primitives.Plane;
-
import org.papervision3d.view.BasicView;
-
-
/**
-
* ...
-
* @author Charlie Schulze, charlie[at]woveninteractive[dot]com
-
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
*/
-
-
public class Main extends BasicView
-
{
-
protected var planes:Array = [];
-
protected var images:Array = [];
-
protected var numItems:Number;
-
protected var currentItem:Number = 3;
-
protected var angle:Number = 25;
-
protected var rightBtn:Sprite;
-
protected var leftBtn:Sprite;
-
protected var xmlPath:String = "xml/imageXML.xml";
-
protected var bulkInstance:BulkLoader;
-
-
public function Main():void
-
{
-
//Make sure that your scene is set to interactive
-
super(640, 480, true, true);
-
loadXML();
-
}
-
-
//First load our XML
-
protected function loadXML():void
-
{
-
bulkInstance = new BulkLoader("bulkInstance");
-
bulkInstance.add(xmlPath);
-
bulkInstance.addEventListener(BulkProgressEvent.COMPLETE, onXMLReady);
-
bulkInstance.start();
-
}
-
-
//When our xml is ready parse and load our images
-
protected function onXMLReady(evt:BulkProgressEvent):void
-
{
-
bulkInstance.removeEventListener(BulkProgressEvent.COMPLETE, onXMLReady);
-
bulkInstance.addEventListener(BulkProgressEvent.COMPLETE, onImagesReady);
-
-
var xml:XML = bulkInstance.getXML(xmlPath);
-
var xmlList:XMLList = xml.image;
-
-
for (var i:int = 0; i <xmlList.length(); i++)
-
{
-
var imagePath:String = String(xmlList[i])
-
bulkInstance.add(imagePath);
-
-
//Add path to array for later access
-
images.push(imagePath);
-
}
-
-
//Set our number of items based on how many images we load
-
numItems = images.length;
-
}
-
-
//Images are finished loading we can now create our papervision coverflow
-
protected function onImagesReady(evt:BulkProgressEvent):void
-
{
-
init();
-
}
-
-
protected function init():void
-
{
-
createChildren();
-
createNavigation();
-
animate();
-
startRendering();
-
}
-
protected function createChildren():void
-
{
-
for (var i:int = 0; i <numItems; i++)
-
{
-
//Grab our bitmapData from the bulkLoader using our array of image paths as our key
-
var mat:BitmapMaterial = new BitmapMaterial(bulkInstance.getBitmapData(images[i]));
-
mat.interactive = true;
-
mat.smooth = true;
-
var plane:Plane = new Plane(mat, 280, 351, 4, 4);
-
-
planes.push(plane);
-
-
//Click straight to any plane
-
plane.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, onPlaneClick);
-
-
//Set an id to target current item
-
plane.id = i;
-
-
//Add plane to the scene
-
scene.addChild(plane);
-
}
-
-
camera.zoom = 50;
-
}
-
-
protected function onPlaneClick(evt:InteractiveScene3DEvent):void
-
{
-
currentItem = evt.target.id;
-
animate();
-
}
-
-
//Animate the coverflow left / right based off of currentItems
-
protected function animate():void
-
{
-
for (var i:int = 0; i <planes.length; i++)
-
{
-
var plane:Plane = planes[i];
-
-
//Each if statement will adjust these numbers as needed
-
var planeX:Number = 0;
-
var planeZ:Number = -50;
-
var planeRotationY:Number = 0
-
-
//Place & Animate Center Item
-
if (i == currentItem)
-
{
-
planeZ = -300
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
-
//Place & Animate Right Items
-
if(i> currentItem)
-
{
-
planeX = (i - currentItem + 1) * 120;
-
planeRotationY = angle + 10 * (i - currentItem);
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
-
//Place & Animate Left Items
-
if (i <currentItem)
-
{
-
planeX = (currentItem - i + 1) * -120;
-
planeRotationY = -angle - 10 * (currentItem - i);
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
}
-
}
-
-
/*
-
* Everything below this point is just for creating the buttons and
-
* setting button events for controlling the coverflow.
-
*/
-
-
protected function createNavigation():void
-
{
-
//Create / Add Buttons
-
rightBtn = createButton();
-
leftBtn = createButton();
-
-
addChild(leftBtn);
-
addChild(rightBtn);
-
-
//Add button listeners
-
rightBtn.buttonMode = true;
-
leftBtn.buttonMode = true;
-
rightBtn.addEventListener(MouseEvent.CLICK, buttonClick);
-
leftBtn.addEventListener(MouseEvent.CLICK, buttonClick);
-
-
//Place buttons on stage
-
rightBtn.x = stage.stageWidth - 20;
-
leftBtn.x = 20;
-
rightBtn.y = stage.stageHeight / 2;
-
leftBtn.y = (stage.stageHeight / 2) + 20;
-
leftBtn.rotation = 180;
-
}
-
-
//Button actions
-
protected function buttonClick(evt:MouseEvent):void
-
{
-
switch (evt.target)
-
{
-
case rightBtn:
-
currentItem ++
-
break;
-
-
case leftBtn:
-
currentItem --;
-
break;
-
}
-
-
//Don't allow any number lower than 0 or past max so there
-
//is always a center item
-
-
if (currentItem <0)
-
{
-
currentItem = 0;
-
}
-
if (currentItem> (planes.length - 1))
-
{
-
currentItem = planes.length - 1;
-
}
-
-
//Call animation
-
animate();
-
}
-
-
//Creates a simple arrow shape / returns the sprite
-
protected function createButton():Sprite
-
{
-
var btn:Sprite = new Sprite();
-
-
btn.graphics.beginFill(0x333333);
-
btn.graphics.moveTo(0, 0);
-
btn.graphics.lineTo(0, 20);
-
btn.graphics.lineTo(10, 10);
-
btn.graphics.lineTo(0, 0);
-
btn.graphics.endFill();
-
btn.filters = [new GlowFilter(0xFFFFFF,1,10,10,3)]
-
return btn;
-
}
-
}
-
}
There are several great AS3 / Papervision Coverflows out there but today I set out to create one in it's simplest form. There are no bells and whistles, just a stripped down coverflow with it's core functionality. It's up to you to add an XML feed, Flickr feed, or setup your images in an array and load them the way you want to. This is only meant to be a clean jumping off point.
In the download I have included 2 versions. One with left / right buttons and one without. Both versions you can select the items in the coverflow to navigate.
The heart of this coverflow app is a lot like some of the others. We need to calculate the x and rotation of the center item, left items, and right items are, then animate accordingly. This is the same way that John Dyer animates his coverflow.
-
for (var i:int = 0; i <planes.length; i++)
-
{
-
var plane:Plane = planes[i];
-
-
//Each if statement will adjust these numbers as needed
-
var planeX:Number = 0;
-
var planeZ:Number = 20;
-
var planeRotationY:Number = 0
-
-
//Place & Animate Center Item
-
if (i == currentItem)
-
{
-
planeZ = -300
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
-
//Place & Animate Right Items
-
if(i> currentItem)
-
{
-
planeX = (i - currentItem + 1) * 120;
-
planeRotationY = angle + 10 * (i - currentItem);
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
-
//Place & Animate Left Items
-
if (i <currentItem)
-
{
-
planeX = (currentItem - i + 1) * -120;
-
planeRotationY = -angle - 10 * (currentItem - i);
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
}
For this simple version of coverflow you have two ways of navigating to items.
Selecting a plane to jump right to that item
-
plane.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, onPlaneClick);
-
-
protected function onPlaneClick(evt:InteractiveScene3DEvent):void
-
{
-
currentItem = evt.target.id;
-
animate();
-
}
Or using left / right buttons to navigate one item at a time:
-
rightBtn.addEventListener(MouseEvent.CLICK, buttonClick);
-
leftBtn.addEventListener(MouseEvent.CLICK, buttonClick);
-
-
protected function buttonClick(evt:MouseEvent):void
-
{
-
switch (evt.target)
-
{
-
case rightBtn:
-
currentItem ++
-
break;
-
-
case leftBtn:
-
currentItem --;
-
break;
-
}
-
-
//Don't allow any number lower than 0 or past max so there
-
//is always a center item
-
-
if (currentItem <0)
-
{
-
currentItem = 0;
-
}
-
if (currentItem> (planes.length - 1))
-
{
-
currentItem = planes.length - 1;
-
}
-
-
//Call animation
-
animate();
-
}
On to the full source code. This version includes the left / right buttons.
-
package
-
{
-
import flash.display.DisplayObject;
-
import flash.display.Sprite;
-
import flash.events.Event;
-
import flash.events.MouseEvent;
-
import flash.filters.GlowFilter;
-
import gs.easing.Quint;
-
import gs.TweenLite;
-
import org.papervision3d.events.InteractiveScene3DEvent;
-
import org.papervision3d.materials.BitmapFileMaterial;
-
import org.papervision3d.objects.DisplayObject3D;
-
import org.papervision3d.objects.primitives.Plane;
-
import org.papervision3d.view.BasicView;
-
-
/**
-
* ...
-
* @author Charlie Schulze, charlie[at]woveninteractive[dot]com
-
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
*/
-
-
public class Main extends BasicView
-
{
-
protected var planes:Array = [];
-
protected var numItems:Number = 7;
-
protected var currentItem:Number = 3;
-
protected var angle:Number = 25;
-
-
protected var mat:BitmapFileMaterial;
-
protected var rightBtn:Sprite;
-
protected var leftBtn:Sprite;
-
-
public function Main():void
-
{
-
//Make sure that your scene is set to interactive
-
super(640, 480, true, true);
-
-
init();
-
}
-
protected function init():void
-
{
-
createChildren();
-
createNavigation();
-
animate();
-
startRendering();
-
}
-
protected function createChildren():void
-
{
-
//Create Material
-
mat = new BitmapFileMaterial("images/iPhone-back2.png");
-
mat.smooth = true;
-
mat.interactive = true;
-
for (var i:int = 0; i <numItems; i++)
-
{
-
var plane:Plane = new Plane(mat, 177, 334, 4, 4);
-
planes.push(plane);
-
-
//Click straight to any plane
-
plane.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, onPlaneClick);
-
-
//Set an id to target current item
-
plane.id = i;
-
-
//Add plane to the scene
-
scene.addChild(plane);
-
}
-
-
camera.zoom = 60;
-
}
-
-
protected function onPlaneClick(evt:InteractiveScene3DEvent):void
-
{
-
currentItem = evt.target.id;
-
animate();
-
}
-
-
//Animate the coverflow left / right based off of currentItems
-
protected function animate():void
-
{
-
for (var i:int = 0; i <planes.length; i++)
-
{
-
var plane:Plane = planes[i];
-
-
//Each if statement will adjust these numbers as needed
-
var planeX:Number = 0;
-
var planeZ:Number = -50;
-
var planeRotationY:Number = 0
-
-
//Place & Animate Center Item
-
if (i == currentItem)
-
{
-
planeZ = -300
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
-
//Place & Animate Right Items
-
if(i> currentItem)
-
{
-
planeX = (i - currentItem + 1) * 120;
-
planeRotationY = angle + 10 * (i - currentItem);
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
-
//Place & Animate Left Items
-
if (i <currentItem)
-
{
-
planeX = (currentItem - i + 1) * -120;
-
planeRotationY = -angle - 10 * (currentItem - i);
-
-
TweenLite.to(plane, 1, { rotationY:planeRotationY,x:planeX,z:planeZ, ease:Quint.easeInOut } );
-
}
-
}
-
}
-
-
/*
-
* Everything below this point is just for creating the buttons and
-
* setting button events for controlling the coverflow.
-
*/
-
-
protected function createNavigation():void
-
{
-
//Create / Add Buttons
-
rightBtn = createButton();
-
leftBtn = createButton();
-
-
addChild(leftBtn);
-
addChild(rightBtn);
-
-
//Add button listeners
-
rightBtn.buttonMode = true;
-
leftBtn.buttonMode = true;
-
rightBtn.addEventListener(MouseEvent.CLICK, buttonClick);
-
leftBtn.addEventListener(MouseEvent.CLICK, buttonClick);
-
-
//Place buttons on stage
-
rightBtn.x = stage.stageWidth - 20;
-
leftBtn.x = 20;
-
rightBtn.y = stage.stageHeight / 2;
-
leftBtn.y = (stage.stageHeight / 2) + 20;
-
leftBtn.rotation = 180;
-
}
-
-
//Button actions
-
protected function buttonClick(evt:MouseEvent):void
-
{
-
switch (evt.target)
-
{
-
case rightBtn:
-
currentItem ++
-
break;
-
-
case leftBtn:
-
currentItem --;
-
break;
-
}
-
-
//Don't allow any number lower than 0 or past max so there
-
//is always a center item
-
-
if (currentItem <0)
-
{
-
currentItem = 0;
-
}
-
if (currentItem> (planes.length - 1))
-
{
-
currentItem = planes.length - 1;
-
}
-
-
//Call animation
-
animate();
-
}
-
-
//Creates a simple arrow shape / returns the sprite
-
protected function createButton():Sprite
-
{
-
var btn:Sprite = new Sprite();
-
-
btn.graphics.beginFill(0x333333);
-
btn.graphics.moveTo(0, 0);
-
btn.graphics.lineTo(0, 20);
-
btn.graphics.lineTo(10, 10);
-
btn.graphics.lineTo(0, 0);
-
btn.graphics.endFill();
-
btn.filters = [new GlowFilter(0xFFFFFF,1,10,10,3)]
-
return btn;
-
}
-
}
-
}
That's it. This should provide a good base for you to build out your own unique coverflow.
In early 2008 I wrote a blog article on how to explode an image and rebuild it again. Recently this effect has been used in some amazing ways. Site's like Audi have taken this concept much further with the rows of these tiny boxes swimming in formation.
Today I decided to re-create that simple effect using the latest papervision code and provide a much needed update.
Creating this effect is actually fairly simple. You take an image, use some code to break it into smaller bitmaps which then are used as the materials for your planes. Then you just place the planes in a grid to re-create your image.
Here is a sample of the code that get's our image blocks to be used for our materials:
-
//Creates a 30 x 18 bitmapData block
-
var bitmap_data:BitmapData = new BitmapData(cellWidth, cellHeight, true, 0x00FFFF);
-
-
//Create a new Matrix - A matrix is a rectangular array / table
-
//of numbers with any number of rows / columns.
-
var matrix:Matrix = new Matrix();
-
-
//Get's our desired x / y location information where we will
-
//pull a block of our image
-
matrix.translate( -cellWidth * cellX, -cellHeight * cellY);
-
-
//Write the data to our bitmap data object with our source and
-
//matrix / block info
-
bitmap_data.draw(imageMC, matrix);
Once you've created your grid you just need to store the locations of all your x,y,z,rotation etc so that when you break apart the image you can easily piece it back together.
-
planeVO.origX = pl.x;
-
planeVO.origY = pl.y;
-
planeVO.origZ = pl.z;
-
planeVO.origRotationY = pl.rotationY;
-
planeVO.origRotationX = pl.rotationX;
-
planeVO.origRotationZ = pl.rotationZ;
-
planeVO.planeRef = pl;
PlaneVO is nothing more than a simple value object. A value object gives us a nice strongly typed object to store the variables we want to access later.
PlaneVO.as
-
package
-
{
-
import org.papervision3d.objects.primitives.Plane;
-
/**
-
* ...
-
* @author Charlie Schulze, Woven Interactive, LLC
-
*/
-
public class PlaneVO
-
{
-
public var origX:Number;
-
public var origY:Number
-
public var origZ:Number
-
public var origRotationY:Number
-
public var origRotationX:Number
-
public var origRotationZ:Number
-
public var planeRef:Plane;
-
}
-
-
}
Now on to the full code. I have left comments throughout to explain what is happening. Try changing some of the math or swapping the image with your own. Once you mess with it for a little while you'll find it's easy to create some amazing effects.
-
package
-
{
-
import br.com.stimuli.loading.BulkLoader;
-
import br.com.stimuli.loading.BulkProgressEvent;
-
import com.foomonger.utils.Later;
-
import flash.display.Bitmap;
-
import flash.display.BitmapData;
-
import flash.display.DisplayObject;
-
import flash.display.MovieClip;
-
import flash.geom.Matrix;
-
import gs.easing.Quint;
-
import gs.TweenMax;
-
import org.papervision3d.materials.MovieMaterial;
-
import org.papervision3d.objects.DisplayObject3D;
-
import org.papervision3d.objects.primitives.Plane;
-
import org.papervision3d.view.BasicView;
-
-
/**
-
* ...
-
* @author Charlie Schulze, charlie[at]woveninteractive[dot]com
-
*/
-
-
public class Main extends BasicView
-
{
-
protected var bulkInstance:BulkLoader;
-
protected var imageMC:MovieClip;
-
protected var imageString:String
-
protected var planeVOs:Array = [];
-
protected var planesContainer:DisplayObject3D;
-
-
public function Main():void
-
{
-
super();
-
-
//Set the image we want to load
-
imageString = "images/king.gif";
-
-
//Load our image
-
loadImage();
-
}
-
protected function loadImage():void
-
{
-
//BulkLoader is complete overkill for this but thought it would be a
-
//nice extra for everyone to see in action. Extremely useful set of classes.
-
-
//Create our unique bulkInstance / available anywhere in our app by
-
//that name "bulkImageLoader"
-
bulkInstance = new BulkLoader("bulkImageLoader");
-
-
//Add our image
-
bulkInstance.add(imageString);
-
-
//Add our listeners
-
bulkInstance.addEventListener(BulkProgressEvent.COMPLETE, onImageLoaded);
-
-
//Start Loading
-
bulkInstance.start();
-
}
-
protected function onImageLoaded(evt:BulkProgressEvent):void
-
{
-
init();
-
}
-
protected function init():void
-
{
-
createChildren();
-
explode();
-
startRendering();
-
}
-
-
protected function createChildren():void
-
{
-
//Create the clip we will be getting our bitmap data from
-
imageMC = new MovieClip();
-
-
//add our bitmap
-
imageMC.addChild(bulkInstance.getBitmap(imageString));
-
-
//Optional - Create a container to hold our planes
-
planesContainer = new DisplayObject3D();
-
-
//Array to store our value objects
-
planeVOs = [];
-
-
for (var i:int = 0; i <65; i++)
-
{
-
/**
-
* Here is how we get our loop / colunm / cellWidth / cellHeight numbers
-
*
-
* 5 columns * 13 rows = 65 (number of loops)
-
* 5 columns divided by the width (150) of our image = 30 pixels
-
* 13 rows divided by the height (234) of our image = 18 pixels
-
*/
-
-
var columns:uint = 5;
-
var cellWidth:int = 30;
-
var cellHeight:int = 18;
-
var cellX:int = (i % columns)
-
var cellY:int = Math.floor(i / columns);
-
-
//Creates a 30 x 18 bitmapData block
-
var bitmap_data:BitmapData = new BitmapData(cellWidth, cellHeight, true, 0x00FFFF);
-
-
//Create a new Matrix - A matrix is a rectangular array / table
-
//of numbers with any number of rows / columns.
-
var matrix:Matrix = new Matrix();
-
-
//Get's our desired x / y location information where we will
-
//pull a block of our image
-
matrix.translate( -cellWidth * cellX, -cellHeight * cellY);
-
-
//Write the data to our bitmap data object with our source and
-
//matrix / block info
-
bitmap_data.draw(imageMC, matrix);
-
-
//Create a bitmap with our bitmapData and add it to a movieclip
-
var bitmap:Bitmap = new Bitmap(bitmap_data);
-
var myMovie:MovieClip = new MovieClip();
-
-
myMovie.addChild(bitmap);
-
-
//Use the movieclip with our bitmap inside as our movieMaterial
-
//needs to be cast as a DisplayObject to work
-
var movieMat:MovieMaterial = new MovieMaterial(myMovie as DisplayObject, true);
-
movieMat.smooth = true;
-
-
//Create a plane with our moviemat the size of our cellWidth/Height
-
var pl:Plane = new Plane(movieMat, cellWidth, cellHeight, 0, 0);
-
-
//Create a value object to store our origianl infomation
-
//for when we animate we can then rebuild easily
-
var planeVO:PlaneVO = new PlaneVO();
-
-
//We want to place the planes to re-create our original image
-
pl.x = ((cellX * cellWidth) + cellWidth) -(imageMC.width / 2);
-
pl.y = -((cellY * cellHeight) + cellHeight) +(imageMC.height / 2);
-
-
//Set the original properties of our value object
-
planeVO.origX = pl.x;
-
planeVO.origY = pl.y;
-
planeVO.origZ = pl.z;
-
planeVO.origRotationY = pl.rotationY;
-
planeVO.origRotationX = pl.rotationX;
-
planeVO.origRotationZ = pl.rotationZ;
-
planeVO.planeRef = pl;
-
-
//Add our planeVO to our array
-
planeVOs.push(planeVO)
-
-
//Add planes to our container
-
planesContainer.addChild(pl);
-
}
-
-
//Add our container to our scene
-
scene.addChild(planesContainer);
-
-
camera.zoom = 100;
-
-
//rotate our container for an skewed effect
-
planesContainer.rotationY = 30;
-
planesContainer.rotationX = 30;
-
}
-
-
protected function explode():void
-
{
-
//Create a for loop of our plane objects set random numbers to explode our image
-
-
for (var i:int = 0; i <planeVOs.length; i++)
-
{
-
var planeVO:PlaneVO = planeVOs[i];
-
var plane:Plane = planeVO.planeRef;
-
-
var ranNumber:Number = Math.round(Math.random() * 200 - 200);
-
var delayAmount:Number = i * .05;
-
var shortDelayAmount:Number = delayAmount * .6;
-
-
var randomX:Number = (Math.random() * 4000) - 6000;
-
var randomY:Number = (Math.random() * 8000) - 1000;
-
var randomZ:Number = (Math.random() * 1000) + 5000;
-
-
TweenMax.to(plane, 2, { x:planeVO.origX + randomX, delay:shortDelayAmount,
-
z:randomZ, y:150 + planeVO.origY + randomY,
-
rotationY:180,ease:Quint.easeInOut} );
-
}
-
-
//In 3 seconds rebuild
-
Later.call(this, rebuild, 3000, true);
-
}
-
-
protected function rebuild():void
-
{
-
//Rebuild our image with the locations we stored in our VO
-
-
for (var i:int = 0; i <planeVOs.length; i++)
-
{
-
var planeVO:PlaneVO = planeVOs[i];
-
var plane:Plane = planeVO.planeRef;
-
var delayAmount:Number = i * .005;
-
TweenMax.to(plane, 1.8, { x:planeVO.origX, delay:delayAmount,
-
z:planeVO.origZ, y:planeVO.origY,
-
rotationY:planeVO.origRotationY,ease:Quint.easeInOut} );
-
}
-
-
//In 5 seconds explode the image again
-
Later.call(this, explode, 5000, true);
-
}
-
}
-
}







