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));
-
}
-
}
-
}



awesome tutorial.
great tutorial !!
I have some questions about Pv3d !! somedays ago I load a external swf and convert swf to MovieLip then I use it as MovieMaterial !! But when I test the Movie !It doesn’t show the swf !! how to let it show !!
sorry for my poor english !!!
@Seb, dreamnight, and andre – Thanks, glad you enjoyed the tutorial. :)
@dreamnight – If you paste your code at pastebin.com and leave the link in a comment perhaps we can help you find out whats going wrong..
Charlie
Hello,
Very nice tutorial!
However, I have a little problem witht the sphere display:
I pan the isView so that the top corner of the grid touch the top of the stage, so that I have a could view the entire grid.
When the sphere got to the far bottom of the grid, it is hidden.
I didn’t manage to make it appears on the last squares of the bottom of the grid.
Since the sphere is managed by papervision I am a little bit confused how to solve the problem:
Should I configure the viewport or the isoScene, or isoView?
Should I play with the z-index?
Sorry I don’t know much about papervision so I don’t know how to handle this problem…
Thanks in advance for any hint or advices!
Regards
Regis
Hello,
thanks for your great work
i have downloaded your code.
but i have some problems when i run the application.
1.the ball does not appear at the correct position.
2.the scrollbar does not show up
my flex builder version is 3.2
sorry for my poor english
Hi,
same problem for me running in flashBuilder 4.
The sphere is not placed at the right position, not scroll neither.
any ideas?
thank you
Bruno
@bruno & @flex3d Have you set your stageAlign and stageScale properties? Also check the dimensions of the movie you are exporting (should be 800 x 600). If neither of those options work I will test it out in FlashBuilder shortly and get an update out.
Charlie
I made a modified version of this on my websites that supports camera panning with the object.
Anyone who is interested can check it out here http://adamzwakk.com/?p=198
adding this metatag ( after the includes ) fixed the issue for me, in an as3 project in flash builder 4:
[SWF(width="800", height="600")]
thanks for the tutorial
Hi, great tutorial.
Qq, what is the purpose of -15 in the onRenderTick method?
Thanks.
Hi,
compliments for this website…it’s a great inspiration.
I noticed that you always use only one class in Papervision…and that’s what I have done until now…but now with a particular project I wanted to split the papervision stuff in a class Main that imported other 3 classes…
but I get a error I cannot solve:
in my Main class extending BasicView I call another class, also extending basic view…I just call:
var inst:MyOtherClass= new MyOtherClass();
myDisplayObject3D.addChild (inst)
this normally would work just fine…but in Papervision (2.0)
and I receive the error:
C:\********\Main.as:46: 1067: Implicit coercion of a value of type com.whatever:MyOtherClass to an unrelated type org.papervision3d.objects:DisplayObject3D.
Any suggestion?
thanks,
Max