2010
05.18

I’ve been working with as3isolib for quite some time now and since I can’t find many tutorials on how to do things, here’s a somewhat simple one to create a terrain with a mario sprite (loaded from an external SWF) to walk around in.

You can view the finished product and the source code here (right click to view source).

First, make sure you import all the classes you will need.

import as3isolib.display.IsoSprite;
 
import as3isolib.display.IsoView;
 
import as3isolib.display.scene.IsoScene;
 
import bit101.AStar;
 
import bit101.Grid;
 
import bit101.Node;
 
import caurina.transitions.*;
 
import com.adobe.viewsource.ViewSource;
 
import eDpLib.events.ProxyEvent;
 
import flash.display.Loader;
 
import flash.display.Sprite;
 
import flash.display.StageAlign;
 
import flash.display.StageScaleMode;
 
import flash.events.*;
 
import flash.net.URLRequest;

This uses an AStar class that I got from Papervision2. (In fact, this is a heavily modified version of that code)

Next we define our class, and create all the variables we will be using.

public class pathfindingtest extends Sprite
{
protected var cellSize:int = 50;//size of cubes/squares
protected var pathGrid:Grid;
protected var path:Array;
protected var isoView:IsoView;
protected var isoScene:IsoScene;
protected var dude:IsoSprite = new IsoSprite;
protected var speed:Number = 0.4;
protected var speed1:Number;
protected var delay:Number;
protected var delayfix:Number = 0;;
protected var speedcheck:Boolean = false;
protected var _numX:Number = 0;
protected var _numY:Number = 0;
protected var targetX:Number;
protected var targetY:Number;
protected var dudeDir:String;
protected var leftRightTempX:Number = 0;
protected var leftRightTempY:Number = 0;
 
private var loaderMario:Loader;
private var loaderGrass:Loader;</blockquote>
For our main function, we want to load the External SWFs we have for both Mario and the terrain. When they complete, we want to create our random terrain.
<blockquote>public function pathfindingtest(){
loaderMario = new Loader();
loaderMario.load(new URLRequest("mario.swf"));
loaderGrass = new Loader();
loaderGrass.contentLoaderInfo.addEventListener(Event.INIT,makeGrid);
loaderGrass.load(new URLRequest("grassSprites.swf"));
}

For our grid, we’re going to make a 10×10 grid with random obstacles every time the page loads. We also want to create an Event Listener on every frame that renders the IsoScene for us and updates Mario on his direction.

protected function makeGrid(e:Event):void
{
addEventListener(Event.ENTER_FRAME, render);
pathGrid = new Grid(10, 10);//makes a 10x10 grid
for(var i:int = 0; i &lt; 15; i++)
{
pathGrid.setWalkable(Math.floor(Math.random() * 8 ) + 2,
Math.floor(Math.random() * 8)+ 2,
false);//randomly selects which tiles will be up or down
}
drawGrid();
}

Now we want to draw the grid, and place all the objects on our scene. We also want to randomize the terrain a bit so there will be small differences in grass and obstacles (4 different grass sprites, and 4 different obstacle sprites). On every grass sprite, we’ll make an event listener that will be our walking function (so we cannot click on an obstacle) Then we want to add it all to the IsoScene and then add the IsoScene to the IsoView so we can see all our sprites.

protected function drawGrid():void
{
isoScene 		= new IsoScene();
isoView 		= new IsoView();
 
isoView.clipContent = false;
 
var grassClass:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Grass") as Class;
var grass2Class:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Grass2") as Class;
var grass3Class:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Grass3") as Class;
var grass4Class:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Grass4") as Class;
 
var blockClass:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Block") as Class;
var block2Class:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Block2") as Class;
var block3Class:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Block3") as Class;
var block4Class:Class = loaderGrass.contentLoaderInfo.applicationDomain.getDefinition("Block4") as Class;
 
for(var i:int = 0; i &lt; pathGrid.numCols; i++)
{
for(var j:int = 0; j &lt; pathGrid.numRows; j++)//go through each column, and then each row
{
var node:Node = pathGrid.getNode(i, j);//grab the current node (or square) of the grid
var ground:IsoSprite = new IsoSprite();
 
if (node.walkable)//if it was determined that the tile is down, it makes the height 0 and adds an event listener
{
var grassPicker:int = Math.random()*4+1;
if(grassPicker == 1){
ground.sprites = [grassClass];
} else if (grassPicker == 2){
ground.sprites = [grass2Class];
} else if (grassPicker == 3){
ground.sprites = [grass3Class];
} else if (grassPicker == 4){
ground.sprites = [grass4Class];
}
 
ground.addEventListener(MouseEvent.CLICK, onGridItemClick);
}
else
{
var blockPicker:int = Math.random()*4+1;
if(grassPicker == 1){
ground.sprites = [blockClass];
} else if (grassPicker == 2){
ground.sprites = [block2Class];
} else if (grassPicker == 3){
ground.sprites = [block3Class];
} else if (grassPicker == 4){
ground.sprites = [block4Class];
}
}
ground.moveTo(i * cellSize, j * cellSize, 0);//places the box
isoScene.addChild(ground);
}
}
 
//Set properties for isoView
isoView.setSize(stage.stageWidth, stage.stageHeight);
isoView.showBorder = false;
dude.isAnimated = true;
dude.autoUpdate = true;
 
isoView.autoUpdate = true;
 
isoScene.addChild(dude);
 
//Add the isoScene to the isoView
isoView.addScene(isoScene);
 
//Add the isoView to the stage
addChild(isoView);
}</blockquote>
Next, we'll create our function that runs when we click on a grass tile. We want it to grab where we clicked, and where Mario is right now.
<blockquote>protected function onGridItemClick(evt:ProxyEvent):void
{
var box:IsoSprite = evt.target as IsoSprite;
 
//Get and set End Nodes (where are we going)
var xpos:int = (box.x)/cellSize;
var ypos:int = Math.floor(box.y / cellSize);//grabs the center of what the player just clicked
pathGrid.setEndNode(xpos,ypos);
 
//Get and set Start Node (where are we now)
xpos = Math.floor(dude.x / cellSize);
ypos = Math.floor(dude.y / cellSize);
 
pathGrid.setStartNode(xpos, ypos);//grabs the position of the dude
 
//Find our path
findPath();
}

So now that we have out start and end points, we need to find the path to get there and plot a waypoint system (it gets a bit complicated here). For every point, we want to tween Mario so he goes through all the points. This works using the Tweener Delay system. We add a delay after every point. That way his start and end point will match up perfectly. I’ve also added a fix here which makes Mario move a bit slower when he goes directly left or right. This is because the regular tween between left and right goes about 125% faster than the rest of the tweens.

protected function findPath():void
{
var astar:AStar = new AStar();
 
if(astar.findPath(pathGrid))//if there is a path between the two nodes...
{
path = astar.path;//make our path using a waypoint system
 
for (var i:int = 1; i &lt; path.length; i++) 				{ 					//trace(path[i].x+','+path[i].y); 					targetX = path[i].x * cellSize; 					targetY = (path[i].y * cellSize);//for every spot on our waypoint, tween it through every point					 					speedcheck = checkTween(targetX, targetY); 					moveDude(targetX, targetY, i); 					//trace(targetX+','+targetY); 					 				} 				speed1=0; 				delay = 0; 				delayfix = 0; 				speedcheck=null; 			} else { 				//if path is impossible, do something here. add a text message maybe? 			} 			 		} 		 		public function moveDude(targetX:Number, targetY:Number, i:int):void{ 				if(i &gt; 1){
speed1 = speed;
}
//trace(speed1);
if(speedcheck == true){
speed = 0.65;
if(speed1 == 0.4){
delay = speed1;//finds out how much delay is needed from the last instruction
Tweener.addTween(dude, {x:targetX, y:targetY, delay:delay+delayfix , time:speed, transition:"linear"} );//tween the dude
Tweener.addTween(isoView, {x:((-targetX+targetY)/2)*2, y:(-targetY-targetX)/2, delay:delay+delayfix , time:speed, transition:"linear" } );//tween the camera
delayfix = delayfix+delay;//adds to the total delay time so the tweens stack on another
} else {
delay = speed;//finds out how much delay is needed from the current instruction
Tweener.addTween(dude, {x:targetX, y:targetY, delay:delay+delayfix , time:speed, transition:"linear"} );//tween the dude
Tweener.addTween(isoView, {x:((-targetX+targetY)/2)*2, y:(-targetY-targetX)/2, delay:delay+delayfix , time:speed, transition:"linear" } );//tween the camera
delayfix = delayfix+delay;//adds to the total delay time so the tweens stack on another
}
} else if(speedcheck == false) {
speed = 0.4;
if(speed1 == 0.65){
delay = speed1;//finds out how much delay is needed from the last instruction
Tweener.addTween(dude, {x:targetX, y:targetY, delay:delay+delayfix , time:speed, transition:"linear"} );//tween the dude
Tweener.addTween(isoView, {x:((-targetX+targetY)/2)*2, y:(-targetY-targetX)/2, delay:delay+delayfix , time:speed, transition:"linear" } ); //tween the camera
delayfix = delayfix+delay;//adds to the total delay time so the tweens stack on another
} else {
delay = speed;//finds out how much delay is needed from the current instruction
Tweener.addTween(dude, {x:targetX, y:targetY, delay:delay+delayfix , time:speed, transition:"linear"} );//tween the dude
Tweener.addTween(isoView, {x:((-targetX+targetY)/2)*2, y:(-targetY-targetX)/2, delay:delay+delayfix , time:speed, transition:"linear" } ); //tween the camera
delayfix = delayfix+delay;//adds to the total delay time so the tweens stack on another
}
 
}
//trace(i+': Speed:'+speed+' Delay:'+delayfix);//This trace would find the number of tweens happening at one time and their speeds and delays
}
 
//CHECKS IF DUDE IS GOING LEFT OR RIGHT(HE MOVES SLIGHTLY FASTER IN BOTH THOSE DIRECTIONS)
public function checkTween(leftrightX:Number, leftrightY:Number):Boolean{
if (leftrightX &lt; leftRightTempX &amp;&amp; leftrightY &gt; leftRightTempY) {
leftRightTempX = leftrightX;
leftRightTempY = leftrightY;
//trace("left");
return true;
}else if (leftrightX &gt; leftRightTempX &amp;&amp; leftrightY &lt; leftRightTempY) {
leftRightTempX = leftrightX;
leftRightTempY = leftrightY;
//trace("right");
return true;
} else {
leftRightTempX = leftrightX;
leftRightTempY = leftrightY;
//trace("regular");
return false;
}
}

Almost done, now all we have to do is have it check which direction Mario is going, and change his sprite accordingly on every frame. We load all his sprites from the external SWF (just like the terrain ones) and add them to Mario accordingly.

public function checkDude():void{
var marioFrontClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioFront") as Class;
var marioBackLeftClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioBackLeft") as Class;
var marioBottomRightClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioBottomRight") as Class;
var marioBackClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioBack") as Class;
var marioLeftClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioLeft") as Class;
var marioRightClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioRight") as Class;
var marioTopRightClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioTopRight") as Class;
var marioBottomLeftClass:Class = loaderMario.contentLoaderInfo.applicationDomain.getDefinition("MarioBottomLeft") as Class;
if(_numX == 0 &amp;&amp; _numY == 0){
_numX = dude.x;
_numY = dude.y;
if(dude.sprites.toString() != marioFrontClass.toString()){
dude.sprites = [marioFrontClass];
}
}else {
if (dude.x &lt; _numX) {
if(dude.y &lt; _numY){ 						if(dude.sprites.toString() != marioBackClass.toString()){ 							dude.sprites = [marioBackClass]; 							dudeDir = "Up"; 						} 					} else if(dude.y &gt; _numY){
if(dude.sprites.toString() != marioLeftClass.toString()){
dude.sprites = [marioLeftClass];
dudeDir = "Left";
}
}else {
if(dude.sprites.toString() != marioBackLeftClass.toString()){
dude.sprites = [marioBackLeftClass];
dudeDir = "UpLeft";
}
}
} else if (dude.x &gt; _numX) {
if(dude.y &lt; _numY){ 						if(dude.sprites.toString() != marioRightClass.toString()){ 							dude.sprites = [marioRightClass]; 							dudeDir = "Right"; 						} 					} else if(dude.y &gt; _numY){
if(dude.sprites.toString() != marioFrontClass.toString()){
dude.sprites = [marioFrontClass];
dudeDir = "Down";
}
}else {
if(dude.sprites.toString() != marioBottomRightClass.toString()){
dude.sprites = [marioBottomRightClass];
dudeDir = "DownRight";
}
}
} else {
if(dude.y &lt; _numY){ 						if(dude.sprites.toString() != marioTopRightClass.toString()){ 							dude.sprites = [marioTopRightClass]; 							dudeDir = "UpRight"; 						} 					} else if(dude.y &gt; _numY){
if(dude.sprites.toString() != marioBottomLeftClass.toString()){
dude.sprites = [marioBottomLeftClass];
dudeDir = "DownLeft";
}
}
}
if(dude.x == _numX &amp;&amp; dude.y == _numY){
dude.sprites = [marioFrontClass];//Mario faces forwards when stopped
}
_numX = dude.x;
_numY = dude.y;
}
}

And last, we’ll make our render function that runs every frame that renders the scene and checks for Mario to change direction.

protected function render(event:Event = null):void
{
//Render the isoScene
isoScene.render();
checkDude();//Checks the sprite for direction change and changes the sprite accordingly
}

After everything, it should look something like this.

Hope this helps anyone get a hold of as3isolib.

29 comments so far

Add Your Comment
  1. Hi, the work is just great. a question: how can we make the character move in 4 directions (adjacency) only, not 8 like in current example.

    I’ve tried to change A* Class’s _heuristic property to be manhattan but it isn’t working & still finding the 8 dimensional paths. Can you guide some on that ?

    • Sorry, I can’t really help you with that one. I haven’t had much success customizing the AStar library either.

      Have you tried asking in the as3isolib Yahoo group? Someone in there could possibly give you some direction.

  2. Recently I have been studying about as3isolib. So far I don’t have a clue about what it can do in the game. Your tutorial is really great. It gives me an idea about how it could be used to construct a basic rpg world and a character.

    • Thank you! I basically wrote this up because I couldn’t find any good examples to work from when I was trying to learn the basics.

      • Yeah, it is so difficult to find any demo game that use as3isolib with a little bit of complexity.

  3. Hello, Adam and RK. If you are still troubled with a diagonal walking of mario, I have done a patch on the A* that disallow diagonal walking.

    public function search():Boolean
    {
    var node:Node = _startNode;
    while(node != _endNode)
    {
    var startX:int = Math.max(0, node.x – 1);
    var endX:int = Math.min(_grid.numCols – 1, node.x + 1);
    var startY:int = Math.max(0, node.y – 1);
    var endY:int = Math.min(_grid.numRows – 1, node.y + 1);

    for(var i:int = startX; i <= endX; i++)
    {
    for(var j:int = startY; j f, which is not accurate.
    // The construction of shortest path tree should be based on real cost instead of estimation.
    if(test.g > g)
    {
    test.f = f;
    test.g = g;
    test.h = h;
    test.parent = node;
    }
    }
    else
    {
    test.f = f;
    test.g = g;
    test.h = h;
    test.parent = node;
    _open.push(test);
    }
    }
    }

    //Empty loop. Comment out. Don’t know why it exists in the code.
    // for(var o:int = 0; o < _open.length; o++)
    // {
    // }
    _closed.push(node);
    if(_open.length == 0)
    {
    trace("no path found");
    return false
    }
    _open.sortOn("f", Array.NUMERIC);
    node = _open.shift() as Node;
    }
    buildPath();
    return true;
    }

    Inside the double loop where all the adjacent cells are being evaluated, I use "continue" to skill the diagonal cells.

    Hope it helps. 😉

  4. Some code is missing from the previous posting.

    public function search():Boolean
    {
    var node:Node = _startNode;
    while(node != _endNode)
    {
    var startX:int = Math.max(0, node.x – 1);
    var endX:int = Math.min(_grid.numCols – 1, node.x + 1);
    var startY:int = Math.max(0, node.y – 1);
    var endY:int = Math.min(_grid.numRows – 1, node.y + 1);

    for(var i:int = startX; i <= endX; i++)
    {
    for(var j:int = startY; j f, which is not accurate.
    // The construction of shortest path tree should be based on real cost instead of estimation.
    if(test.g > g)
    {
    test.f = f;
    test.g = g;
    test.h = h;
    test.parent = node;
    }
    }
    else
    {
    test.f = f;
    test.g = g;
    test.h = h;
    test.parent = node;
    _open.push(test);
    }
    }
    }

    //Empty loop. Comment out. Don’t know why it exists in the code.
    // for(var o:int = 0; o < _open.length; o++)
    // {
    // }
    _closed.push(node);
    if(_open.length == 0)
    {
    trace("no path found");
    return false
    }
    _open.sortOn("f", Array.NUMERIC);
    node = _open.shift() as Node;
    }
    buildPath();
    return true;
    }

  5. I don’t know why some of code appear to be absent. So I will just tell you where to put the code inside AStar class.

    Inside search method, find the following:
    if(!((node.x == test.x) || (node.y == test.y)))
    {
    cost = _diagCost;
    }

    Replace it with the following:
    if(!((node.x == test.x) || (node.y == test.y)))
    {
    cost = _diagCost;

    //Don’t give diagonal cell any consideration
    continue;
    }

    Right now, mario should only be able to walk straight.

  6. BC, thx for the solution, it work perfectly. Jz a simple continue :)

  7. Good tutorial – now I have an approximate vision on how to rotate main character. Thanks a lot – more game tutorials would really be appreciated)

  8. Do you know how to add IsoSprites to Background or Forground container in AS3IsoLib? In the documentation, I’ve found that you can read IsoView’s BG,FG and ObjectContainer. I suggest that addChild method, adds an object to ObjectContainer only, or am I wrong?

    • I haven’t looked at it in a while, but addChild should be what you want to add the object.

      Just make sure you are adding objects in the right order etc.

      • I asked it at AS3IsoLib yahoo group! He told me that it can be achieved this way:
        “isoView.backgroundContainer.addChild(). Same goes for the foregroundContainer.”

        In case someone needs…

  9. Hi, thanks for this tutorial (the first i found with a character 8 directions management).
    But have you solve the mario’s sprite bug with animations stucking in frame 1 ? I saw you posted questions about it but you didn’t update this ressource. Could be nice to achieve this 😉

  10. I’ve seen that there is a problem with depth sorting if you make something like a “Building” in a block and the guy walks behind the building it looks like if he goes through the roof…i’m trying to fix this..anyone has ideas?

  11. Hey there Adam, I know it’s been a while since you wrote this tutorial but thank you so much for it! I’ve just recently started messing around with AS3IsoLib, however, like Redeemer, I’m also having problems with the sorting of IsoSprites. I’ve tried storing all my IsoSprites into an array and sorting by either y or depth values. But so far, I haven’t been successful. If you can help me with this issue, I would greatly appreciate it. Thank you.

  12. Actually, nevermind. I managed to fix the problem! Redeemer, I solved my sorting problems by calling the “setSize” method in the IsoSprite class. If you specifiy a height for your IsoSprites, it will sort automatically.

    • Thanks for the info!

    • @KahunaCoder

      Thanks so much for sharing !!!

      • Thanks this saved me, but I wasted like one hour, because it didn’t worked (I had an outdated as3isolib version)

  13. Awesome tutorial! One thing, I can’t seem to find the property to change the way the screen/character moves in relation to the view port. I would like the character to just move NOT the whole grid.
    Any ideas on this?
    Thanks,
    ~Nathan

  14. Hi Adam,

    Thanks for the tutorial. It is indeed awesome. Have been through the code. Wanted to know how do I set the cell walkable/not walkable for artwork whose size is bigger than the size of one cell? Or do I cut the artwork as per the size of the cell and connect them individually?

    Look forward to your reply.

    Regards,
    Abhishek

    • Hey there,

      I would cut to the size of the cell and map them that way. At least that’s how tiling in 2D games work.

      • Thanks for the answer Adam. But isnt that painstaking especially in a world where there are plenty of elements to deal with?

        Regards,
        Abhishek

  15. this tutorial is excelent thank you very much i have a question

    how can i get the walkable area not random
    your code:

    pathGrid.setWalkable(Math.floor(Math.random() * 8) + 2,
    Math.floor(Math.random() * 8)+ 2,
    false);//randomly selects which tiles will be how can i draw my own map

    Regards

    Morfo

    • What I would do is make an xml file with your grid number and loop through it to draw it. That way you could switch out maps easily.

  16. thank you for your answer adam,

    i will try to get more informations about how to use xml file because i dont have idea about it.

    is it possible with array ?

  17. Hai, how do i set the character (which is mario), to not spawn by 0, 0 on grid?

    I want to it to be spawn on 1, 0, how do i do that?

  18. Hai, how do i change the character when i got 2 characters on the screen?

    I mean, when i press 1 on the keyboard, the mario character is selected and whenever i click any grid, the mario character walk to there. And when i press 2 on the keyboard, another character is selected and whenever i click any grid, that another character walk to there.

    So basically, if i press 1, ONLY the mario character is selected and can walk. Where as if i press 2, ONLY the another character is selected and can walk.

    How do i do that? Thanks