Introduction to A* (A-Star) Pathfinding in ActionScript 3 (AS3)

Introduction

For Spellirium we had to develop a system that could determine the most efficient path between a series of nodes on a point. After some diligent research and careful consideration it became clear that the A* search algorithm was the way to go.

Now, I was familiar with the basics behind A* but had never actually implemented it. Thankfully though I had a copy of Keith Peter’s Advanced ActionScript 3.0 Animation which has a very nice chapter all about path finding and A*.

Advanced Actionscript 3.0 Animation

Why read this post? Just buy the book instead. ;)

Before we begin, some prerequisite knowledge is required. In order to follow along with this you should have a knowledge of AS3 Classes and Interfaces. It is certainly possible to implement A* without knowledge of these concepts but in order to build a flexible, reusable solution, we will be using both. Check out our Understanding Classes in AS3 tutorials if you need help getting up to speed.

What is A* path finding?

Dofus Arena

According to the ever-so-useful Wikipedia, A* path finding can be described as:

(A) computer algorithm that (finds an) efficiently traversable path between points, called nodes…. A* uses a best-first search and finds the least-cost path from a given initial node to one goal node (out of one or more possible goals).

Sounds nice, but what does this mean? It means that A* chooses a node (or tile in a tile based world) searches through all the directly connected nodes and calculates a cost to travel to all of them. It then selects the node that has the lowest cost and uses it as the starting node for the next phase of the search. A* continues to do this until it reaches the destination.

To implement the algorithm it is first important to understand a few concepts:

  • Node: This is a point along a path. In a tile-based environment you can think of it as a tile. The reason why we don’t call it a tile is because it can be used for non-tile based worlds. You will provide A* with a starting node and ending node. A* will then calculate all the nodes to get from the start to the end.
  • Parent Node: When a node is tested in the algorithm all of its directly connected nodes (nodes that are traversable in a single step) are assigned that node as their parent. By doing so, we can easily work backwards through all of the parent nodes to create our path after we reach the destination node.
  • Cost: Cost is assigned to each node based on two factors. Nodes with a lower cost are used over nodes with a higher cost. The two parts that make up the cost are: the cost to get to that node from the starting node and the estimated cost to get from that node to the end node. The cost of a node is usually expressed via 3 variables f, g and h.
  • g: This is the cost to get to this node via the starting node. We know this value exactly because we can follow the parent nodes all the way to the start and add up their costs.
  • h: This is the cost to get from this node to the final node. This value is estimated. It is not exact because we don’t know the path that we will take to get to the final node. Therefore we must estimate the cost and we do so using a function called a heuristic.
  • f: This is the total cost of the specific node – it is calculated by adding g + h.
  • Heuristic: This is the function that estimates the cost of getting from a particular node to the end node. The beauty of A* is that it supports a variety of heuristics, therefore you can try out different ones until you get the result you are looking for. Heuristic functions can differ based on their speed, efficiency and a variety of other criteria.
  • Open list: This is a list of all the nodes that have been visited (in an iteration of the search) and have been assigned a cost. The node that has the lowest cost will be used to perform the next iteration of the search.
  • Closed list: This is the list of nodes whose neighbors have been visited.

The Algorithm

Algorithm

Now that is out of the way we can move on to the actual algorithm. Before we actually begin coding, let’s just take a look at how A* actually works.

  1. Provide a starting node and an destination node
  2. Add the starting node to our open list (which should have been empty)
  3. Start our search loop:
    • a. Pick the node currently in the open list with the lowest cost – we will call this node the current node. When we first run the loop this will obviously be our starting node.
    • b. If the current node is the same as the destination then we are done and we can move on to step 5.
    • c. Check every directly connected node. In a tile based world this would be up to 8 tiles. For our implementation we will provide a function that determines all the connected nodes. For each node that is connected:
      • i. If the node is not traversable (you can’t move to it because it Is occupied or for some other reason) or the node is already in the open list or in the closed list we can skip and move on to the next node in the list of connected nodes. Otherwise continue to ii.
      • ii. Calculate the cost of that node.
      • iii. Set the current node as the parentNode.
      • iv. Add the node to the open list.
    • d. Add the current node to the closed list.
  4. Now return to step 3 (loop again) with the newly updated open list. (The loop should keep running until it hits the destination node)
  5. We have found the destination node (hurrah!) Now we create a list of nodes that will be our path list and we will add the destination node to that list.
  6. Add the parent node of the destination node to the path list.
  7. Add the parent of the previous node to the path list, we will continue to do this until we have added our starting node.

We should now have a list of all the nodes that make up the best path from our starting node to our destination node.

The Implementation

Roll up your sleeves

Now that our theory is out of the way, let’s start writing some code. First we will start with our Node class. For this I will actually create an interface for your node:

public interface INode
{
	function get f():Number;
	function get g():Number;
	function get h():Number;
	function get x():Number;
	function get y():Number;
	function get parentNode():INode;
	function get traversable():Boolean;
	function set f(value:Number):void;
	function set g(value:Number):void;
	function set h(value:Number):void;
	function set parentNode(value:INode):void;
	function set traversable(value:Boolean):void;
}

This is a pretty straight-forward interface. It includes the variables for all the properties we discussed above (f,g,h and parentNode ). The other variables are fairly self-explanatory. X and Y obviously indicate the position of the node. We will use these values in our heuristic to estimate the cost of traveling from the node to the destination. The traversable variable is a simple Boolean that indicates if the node can be used in our path finding. If it is not then the algorithm will simply skip it when it is encountered. This is useful in a world where a node might be occupied by a prop (say a rock, tree or wall) or if there is another player/character occupying that space and you don’t want to support multiple characters on a single node. Implementing this in a class should be very easy.

Nissan Pathfinder

Now we will build a class called Pathfinder. We will create the class only using static methods. Hopefully this will make it very flexible. It will not take much to turn this into a standard class but for our purposes static methods will do very well.

public class Pathfinder
{
	public static var heuristic:Function = Pathfinder.diagonalHeuristic;

Here, we define the heuristic function we will use to estimate the cost of traveling from a node to the end. In this example we will use the diagonal heuristic. At the end of this article, we will discuss some other heuristics and how to implement them.

Let’s move on to our actual path finding function:

public static function findPath(firstNode:INode, destinationNode:INode, connectedNodeFunction:Function ):Array 	{

Let’s examine the parameters:

  • firstNode: INode – this is the first node in our path
  • destinationNode: INode – The is the node that we are going to be traveling to
  • connectedNodeFunction:Function – This is a function that will return an array of nodes that are connected to a given node.

Since each implementation might be different we abstract it out this way. After we write our class I will use a tile- based example to illustrate how this works, but your method might be different.

Lastly, the function returns an Array. This will contain all the nodes along the path.

Variable Setup

So now that is out of the way let’s do some initial work:

var openNodes:Array = [];
var closedNodes:Array = [];
var currentNode:INode = firstNode;
var testNode:INode;
var connectedNodes:Array;
var travelCost:Number = 1.0;
var g:Number;
var h:Number;
var f:Number;
currentNode.g = 0;
currentNode.h = Pathfinder.heuristic(currentNode, destinationNode, travelCost);
currentNode.f = currentNode.g + currentNode.h;
var l:int = nodes.length;
var i:int;

The first two arrays will hold our open nodes and our closed nodes. Then we have a variable currentNode which will keep track of the node we are currently on. We set this to be our firstNode since that is where we will start.

Next we keep a variable testNode:INode, which will be the node that will be a connected node of our current node.

Next we have g,h and f values we will keep track of for each node.

We then set the initial g value of the currentNode (which is also our first node) to 0 since it does not cost anything to get there since it is already our current location.

Then, we calculate the estimated cost to reach the end using our heuristic, and finally the final cost which is calculated by adding g + h (the starting cost plus the final cost).

Lastly, we have some variables that we will use inside of loops in our A* algorithm.

The Loop

Roller coaster loop

Let’s move on to the actual loop.

while (currentNode != destinationNode) {
	connectedNodes = connectedNodeFunction( currentNode );
	l = connectedNodes.length;
	for (i = 0; i < l; ++i) {
		testNode = connectedNodes[i];
		if (testNode == currentNode || testNode.travesable == false) continue;
		g = currentNode.g  + travelCost;
		h = heuristic( testNode, destinationNode, travelCost);
		f = g + h;
		if ( Pathfinder.isOpen(testNode, openNodes) || Pathfinder.isClosed( testNode, closedNodes) )	{
			if(testNode.f > f)
			{

				testNode.f = f;
				testNode.g = g;
				testNode.h = h;
				testNode.parentNode = currentNode;
			}
		}else {
			testNode.f = f;
			testNode.g = g;
			testNode.h = h;
			testNode.parentNode = currentNode;
			openNodes.push(testNode);
		}
	}
	closedNodes.push( currentNode );
	if (openNodes.length == 0) {
		return null;
	}
	openNodes.sortOn('f', Array.NUMERIC);
	currentNode = openNodes.shift() as INode;
}

Whoa! There’s a lot going on there, so let’s try to break it down piece by piece.

while (currentNode != destinationNode) {

Start our loop. If our current node is the same as our destination then we are done and we can build our path!

connectedNodes = connectedNodeFunction( currentNode );
l = connectedNodes.length;
for (i = 0; i < l; ++i) {

So fetch an array of all of the connected nodes of our current node. Store the length and then loop through all of them.

testNode = connectedNodes[i];
if (testNode == currentNode || testNode.travesable == false) continue;

Test each node out of the connected nodes. If the node is the same as our current node or it is not traversable, move on to the next connected node.

Note: Your connectedNodeFunction might omit non traversable nodes which would make the above line redundant and unnecessary.

g = currentNode.g  + travelCost;
h = heuristic( testNode, destinationNode, travelCost);
f = g + h;

Ok, so now we are calculating the actual cost of our node. First, g:

g

Ali G

In a tile based world, we simply add the cost of the currentNode to the travelCost to determine the cost of the tile. In some tile based environments, a diagonal move sometimes cost more than other moves. In this case you would need to determine if the testNode is diagonal to the currentNode and adjust the travelCost value accordingly.

In our case here at Untold, we had a situation where the cost of each connected node was not the same because the nodes could be spread out across variable distances. So in our case we changed the g calculation to use our heuristic between the currentNode and the testNode.

g  = currentNode.g + Pathfinder.heuristic( currentNode, testNode, travelCost);

Now, h:

h

Horse

This is where we use our heuristic. Our heuristic takes in our testNode and our destination node. We also supply the cost to the heuristic which will return a number. Again, we will go over the heuristic later.

f

f

Finally, we are at f. This is a straightforward calculation to determine the total cost of the node.

if ( Pathfinder.isOpen(testNode, openNodes) || Pathfinder.isClosed( testNode, closedNodes) )	{

Here, we use some simple utility functions to determine if the node is in either our open list or our closed list. If it is, then we should only adjust its f,g and h value if its current cost (f) is greater than the cost we just calculated.

	if(testNode.f > f){
		testNode.f = f;
		testNode.g = g;
		testNode.h = h;
		testNode.parentNode = currentNode;
	}else {
		testNode.f = f;
		testNode.g = g;
		testNode.h = h;
		testNode.parentNode = currentNode;
		openNodes.push(testNode);
	}

If the node is not in either the open list or in the closed list, then we definitely want to assign it the values we just calculated. We also want to add it to the list of open nodes and make sure its parent is set to our currentNode.

We are now done our loop and can add our currentNode to our list of closed nodes.

closedNodes.push( currentNode );
if (openNodes.length == 0) {
	return null;
}
openNodes.sortOn('f', Array.NUMERIC);
currentNode = openNodes.shift() as INode;

If there are no more open nodes, then no path is available between the provided nodes, so we return NULL.

Otherwise, sort our open nodes based on their cost and use the one with the lowest cost as our new currentNode.

If a path is available, the currentNode will eventually be the same as our destination node, and the while loop will exit. At this point, we will be ready to build our path and end our function which we do with a single line:

return Pathfinder.buildPath(destinationNode, firstNode);

Of course this means we need a function called buildPath(). Luckily, it is quite simple and looks like this:

public static function buildPath(destinationNode:INode, startNode:INode):Array {
	var path:Array = [];
	var node:INode = destinationNode;
	path.push(node);
	while (node != startNode) {
		node = node.parentNode;
		path.unshift( node );
	}
	return path;
}

In this function, we simply keep moving through all the parent nodes of the provided nodes and put them at the front of a new array we return to the caller.

The Heuristic

We are almost ready to test out our class with an example but as promised we will first discuss our heuristic.

There is a wide range of heuristics used to calculate an optimum path. The methodology is based on a variety of criteria. Different heuristics return different results and you will need to find the one that is best for your case. Right now, we’ll examine three simple heuristics, but don’t be afraid to go out and find your own.

Now, I was familiar with the basics behind A* but had never actually implemented it. Thankfully though I had a copy of Keith Peter’s Advanced ActionScript 3.0 Animation which has a very nice chapter all about path finding and A*.

Manhattan A* Heuristic

Manhattan: This heuristic is the most simple. It ignores any diagonal movement, so if your world does not allow for diagonal movement it might be a good fit. It simply adds the total number of rows and columns between the two provided nodes.

public static function manhattan(node:INode, destinationNode:INode, cost:Number = 1.0):Number {
	return Math.abs(node.x – destinationNode.x) * cost + Math.abs(node.y + destinationNode.y) * cost;
}

Euclidian A* Heuristic

Euclidian: This heuristic simply draws a straight line between the two provided nodes and returns its length. You might recall the Pythagorean Theorem from math class? Well, here it is in action.

public static function euclidianHeuristic(node:INode, destinationNode:INode, cost:Number = 1.0):Number {
	var dx:Number = node.x - destinationNode.x;
	var dy:Number = node.y - destinationNode.y;
	return Math.sqrt( dx * dx + dy * dy ) * cost;
}

Diagonal A* Heuristic

Diagonal: This heuristic is the most accurate of the three. It is more complex than the previous two, but runs fewer times because of its accuracy. It takes into account a diagonal cost, if it is more expensive than a regular movement.

public static function diagonalHeuristic(node:INode, destinationNode:INode, cost:Number = 1.0, diagonalCost:Number = 1.0):Number {
	var dx:Number = Math.abs(node.x - destinationNode.x);
	var dy:Number = Math.abs(node.y - destinationNode.y);
	var diag:Number = Math.min( dx, dy );
	var straight:Number = dx + dy;
	return diagonalCost * diag + cost * (straight - 2 * diag);
}

Make it Go

Rocket

Now that we are done with our pathfinding system, we can put it to work. Take a look at the example below. You can click on any two tiles in the grid and then we will then draw a path between the two tiles. You can change the heuristic at any point to see the different results you might get. Also highlighted are any tiles that the pathfinder tests. Notice that the Manhattan heuristic tests far more nodes than the other two.

[SWF]http://www.untoldentertainment.com/blog/img/2010_08_13/pathfinding_example.swf, 640, 480[/SWF]

I implement the pathfinding simply by calling the Pathfinder.findPath() function and passing the start node, end node and the function that determines connected nodes. I then pass the returned array to a function called drawPath. For your usage you probably want to do a lot more than draw a path, you probably want to have a character traverse along that path, but this is just meant to show you how to acquire which path to follow.

drawPath( Pathfinder.findPath(_startNode, _endNode, findConnectedNodes) );

The findConnectedNodes method is very straightforward. It simply returns all nodes that are adjacent to a given node. You can check it out here:

public function findConnectedNodes( node:INode ):Array
{
	var n:Node = node as Node;
	var connectedNodes:Array = [];
	var testNode:Node;
	for (var i:int = 0; i < _nodes.length; i++)
	{
		testNode = _nodes[i];
		if (testNode.row < n.row - 1 || testNode.row > n.row + 1) continue;
		if (testNode.col < n.col - 1 || testNode.col > n.col + 1) continue;
		connectedNodes.push( testNode );
	}
	return connectedNodes;
}

Of course, this implementation will not be sufficient if you want different tiles to have different costs based on terrain (quicksand might be more expensive than grass), but if you can grasp the above you should be able to make the adjustments fairly easily.

Hopefully you will now have a solid understanding of A* path finding and you can now use it in your own applications.

Click here to download the source code for this tutorial.

Flash and Actionscript 911

This article is part of our ongoing Flash and Actionscript 911 Series, which is awesome.

34 thoughts on “Introduction to A* (A-Star) Pathfinding in ActionScript 3 (AS3)

  1. jansensan

    always enjoy reading your blog, this post is particularly good, as i am working on a snes-era-like game these days. thanks!

    btw, i do agree with a position of yours, where you said schools shun to teach coding as3 properly. not only in ontario, québec is the same.

    Reply
  2. Pete

    Fantastic article. Really helping me understand a better way to structure things. Thank you. And your image selections are hysterical. LMAO. :D

    Reply
  3. Chris

    Thanks for this it’s been really useful. I am trying to modify it to return vertical and horizontal movement only, I know you mention Manhattan is the way to go for this but on the example above it still goes horizontal. Is there a simple way to restrict diagonal nodes?

    Thanks again

    Reply
    1. Chris

      Doh Figured it out. Manhattan just searches vertical and horizontal.
      If anyone’s interested you can get a vertical/horizontal path by adding

      if( testNode.col == n.col || testNode.row == n.row ) connectedNodes.push( testNode );

      Reply
  4. Reinhart Redel

    Really awesome! That was just exactly what I was looking for. I adjusted the whole thing to automatically trigger obsatcles and make waypoint in their range to be not traversable and got the whole thing running.
    Just a note:
    If there are really many waypoints/nodes the calculation of the path is really CPU extensive, if you use the code mentioned in the comments for calculating “g”. So be careful with that one ;-)

    Cheers!
    Reinhart

    Reply
  5. poco

    Hi!

    Your post spoiled my morning :) I had nearly finished debugging my own AStar implementation when I decided to look up for heuristic and found this one, which is way more clean. It’s very kind of you to post this! Thank you!

    Poco

    Reply
  6. Hoang

    I think many ppl will feel the same: This site is EXCELLENT especially in explaining things.
    I love the way you provide information and of course love the code! (as I am VERY new in these stuffs)
    Love Untold now!
    Keep up posting!

    Reply
  7. sean

    It doesn’t work. A star should find the shortest route, no matter what the heuristic. my guess is that your code closes nodes incorrectly.

    :S

    Reply
    1. DudeGuy

      That’s not entirely correct, A * should find the route with the least cost – most likely all the costs being calculated are actually the same. I think there is a bug in the diagonal heuristic.

      Reply
  8. Kreso

    Thank you dude! I have been playing with this algorithms for few days now and your code gave me a much needed jump start. Much apreciated!!

    BTW, I am only testing Manhattan heuristic and it works, but only after you change the operator ; )

    public static function manhattanHeuristic…….
    Only change is the operator from + to -:
    Math.abs(node.y + destinationNode.y) * cost;
    to
    Math.abs(node.y – destinationNode.y) * cost;

    Reply
  9. Rob

    This is great! It really helps a lot. But one question, I have an issue when there is no traversable path between the start and finish nodes (i.e. if the object is trapped in a box of walls) Is there a way to tell it when to give up or to recognize that no path exists?

    Reply
  10. Noob Game Dev

    Hey Phil, this is a great tutorial, I thank you from bottom of my heart for explanation. I have been trying to integrate this code into my isometric game. I have gone through lot of pain and made it working how ever I have some more weird issues still hunting me. I am right now tring to resolve myself, please respond if I comeup with some noob questions. Once again, great thanks!

    Reply
  11. Noob Game Dev

    I have a suggestion, I have implemented get/set ‘travelCost’ methods into INode, just by multiplying _travelCost with _color inside Node.highlight() gives the travesered node and path nodes much more clear vision with a unique colors

    Reply
  12. Jon Trausti

    Do you need the square root at all in the distance calculation in euclidianHeuristic? If you’re always comparing the same scale then you can skip it. That change would add higher performance.

    Anyway, great tutorial and well written! :)
    -Jon ‘raRaRa’

    Reply
  13. Jordan Springett

    Thanks for the great tutorial its been very helpful. What I would like to know is what exactly is the purpose of the closed list?
    Cheers
    - Jordan

    Reply
  14. Andreas R

    Just a heads up. The findConnectedNodes function is very slow on large maps. It went from 400ms to 40ms by only checking the closest nodes instead of looping through all of them.

    Reply
  15. Pingback: The LoneStranger Network » LS-MAN v1 Postmortem

  16. Pingback: Kompliziert? Per Mausklick Objekt dort bewegen lassen. - Flashforum

  17. Joe

    This is a really great explanation of things. It really helped me understand a lot! Thank you.
    One question, if I were to use this with hexagons.. I can’t seem to get it to function right. It will move left to right fine, but going down it skips a hexagon then continues. I tried to make it read six directions in the findConnections function, but it doesn’t seem to be working right. Any help would be great!!

    Reply
  18. AirBear

    I like it, but in trying to implement it myself, this line is throwing errors:
    if ( Pathfinder.isOpen(testNode, openNodes) || Pathfinder.isClosed( testNode, closedNodes) ) {

    What are the .isOpen and .isClosed in reference to?

    Reply
      1. AirBear

        Ah of course! Ok, I was thinking it was something specific to Actionscript variables. Perhaps relating to multithreading… I was overthinking it and you are correct! Sorry, Actionscript is not a language I know and I have been using the code as reference to implementing my own graph in Unity3d…

        Reply
  19. Pingback: Creating Isometric Worlds: A Primer for Game Developers, Continued | Gamedevtuts+

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>