Game Development Community

dev|Pro Game Development Curriculum

Flexible A* Pathfinding System

by Dan Keller · 04/08/2008 (2:15 pm) · 203 comments

1.8.1 version here!

Download

Note: The TGEA version in the download is newer than the TGE version. Most people are using TGEA now, anyways. If you want the newer version in TGE, you can port it yourself, it shouldn't be too bad. If there is some interest in it and someone does a port, just send it to me and I'll put it up here.

Installation - TGEA

Add aStar.h, aStar.cpp, aStarIO.cpp, and aStarMesh.cpp to your project (in T3D). Replace or merge aiPlayer.cpp and .h with the files in in the zip.

Add #include "T3D/aStar.h" on top of sceneState.cpp

Add the commented part in SceneState::renderCurrentImages():
PROFILE_START(RenderInstManagerRender);
   gRenderInstManager.render();
   PROFILE_END();
//aStar
#ifdef AI_RENDER
   AStar::Get()->render(this);
#endif
//aStar
   gRenderInstManager.clear();

   mInteriorList.clear();


Add this in game/tools/missionEditor/scripts/editors/creator.ed.cs line 146:
%Mission_Item[6] = "NavMesh";

Add the following function at the end of game/tools/missionEditor/gui/objectBuilderGui.ed.gui:
function ObjectBuilderGui::buildNavMesh(%this)
{
	%this.className = "NavMesh";
	%this.process();
}

In [game]/scriptsAndAssets/server/scripts/game.cs, at the end of startGame(), add
readPaths();

At the end of endGame(), add
deletePaths();

before resetMission();

For an example, add aiDemo.mis.


Installation - TGE

Add the aStar.cc and aStar.h files to your project. Replace or merge aiPlayer.cc and .h with the files in in the zip.

In sceneState.cc, after the includes, add
#include "game/aStar.h"

in renderCurrentImages(), around line 481, add the commented code
for (i = 0; i < mTranslucentEndImages.size(); i++)
      renderImage(this, mTranslucentEndImages[i]);
//aStar
#ifdef AI_RENDER
   gAStar.render(this);
#endif
//aStar
   glMatrixMode(GL_MODELVIEW);
   glPopMatrix();

Save and compile.

In creator/editor/EditorGui.cs, at line 1306, change
%Mission_Item[3] = "Trigger";
   %Mission_Item[4] = "PhysicalZone";
   %Mission_Item[5] = "Camera";

to

%Mission_Item[3] = "NavMesh";
   %Mission_Item[4] = "Trigger";
   %Mission_Item[5] = "PhysicalZone";
   %Mission_Item[6] = "Camera";

In creator/editor/objectBuilderGui.gui, at line 643, add
function ObjectBuilderGui::buildNavMesh(%this)
{
	%this.className = "NavMesh";
	%this.process();
}

In [game]/server/scripts/game.cs, at the end of startGame(), add
readPaths();

At the end of endGame(), add
deletePaths();

before resetMission();

For an example, add aiDemo.mis.

Use

To add navigation points, open the mission editor creator. NavMesh objects are under Mission Objects / Mission. The properties are:
Interval - distance between points
XSize - number of points in x direction
YSize - number of points in y direction
Height - how far up or down the object will look to place points

There are two global preferences you can change. One is $Pref::AStar::Render. It controls whether or not the navigation mesh is rendered. Turn it on when editing the mesh, turn it off otherwise. The other is $Pref::AStar::Clearence. This controls the minimum horizontal clearance between two nodes when building paths. If you change this after creating points, you need to call buildPaths for it to effect those points.

There are three functions for path caching. Do not call any of these while the mission editor is running, it may get all messed up. They are
- deletePaths Deletes all NavMesh objects and all nodes.
- writePaths Writes all nodes to a file (called [mission name].as) that is loaded automatically on mission startup. This is the only function you need to call manually; call it once you're done editing the nav mesh.
- readPaths Reads nodes from file. Returns true on success, false on faliure.

Also there is
- buildPaths Manually rebuilds navigation information, for example if an object has been moved in the way of some nodes.

There is also a compiler switch, AI_RENDER, at the top of aStar.h. Comment this out when you distribute the game to remove all the nav mesh rendering code.

There are five functions that can be called on the AIPlayer object. They will return 0 on success, -1 on failure, and 1 if the bot is already where you want it to go. They are
- findPathTo This takes either a point or an object as an argument, and simply finds a way to the point.
- findCoverFrom This also takes a point or object, and finds a place hidden from it.
- searchCover This takes no arguments, and makes the bot go to the nearest place it can't see.
- sneakUpOn This takes either a point and a vector or an object as an argument. It finds a path to the point or the object that can't be seen by it.
- wander This takes a distance as an argument and wanders around for this distance.
#41
04/20/2008 (4:29 pm)
It built, I'm going to test it tomorrow morning. I'd rather not lock up my computer for an hour while it builds, I could do that in the morning.

Edit -> Works briliantly, it turned an hour load time back down to it's original 20 seconds! This is by far the best aStar resource on GarageGames!
#42
04/24/2008 (7:59 am)
I need help with this as I am fairly new at TGE. Any clues about this error? I get it when I try to create a NavMesh:

www.headpile.com/stuff/og.jpg

edit: disreagard.
#43
05/01/2008 (1:35 pm)
if (path.size() > 2)
      {
         tol = mDot(path[0]-path[1], path[1]-path[2])+1.05f;
         tol = tol > 1 ? tol*gAStar.clearence : gAStar.clearence;
      }

What exactly is the above supposed to do Dan? It's giving me very large values (around 60 in my tests) which means the bot tries to run for a point way down the path which is not the correct one. Great resource BTW!

I'm also getting sporadic write problems, where all the data is messed up upon being read back. I can't see anything in the code that would do that however.
#44
05/02/2008 (5:10 am)
I thought you might be interested to see what I'm doing with your resource.. the plan is to implement separate A*Star paths for vehicles and pedestrians. I also have human-controlled vehicles/pedestrians drop the path points rather than manually doing it via the NavMeshes in the mission editor (that's a lot less work for me, especially as I have some really large wilderness maps). The more they play against the AI, the better it will get hehe.

www.dark-wind.com/images/pathfinding.jpg
I'm also going to strore vehicle speeds on each path point and use this to help them decide what speed is safe plus to use it as a part of a cost-surface calculation in order to get an optimum route.
#45
05/05/2008 (12:26 pm)
Instead of separating vehicle and pedestrian navMeshes, I would do something where you can set the meshes to be in different navNets. From there you could assign the AI to a different net (ex: %bot.setNavNet(%navNetId);) or have it be called from the find path function (%bot.findPathTo(%destination, %navNetId);). That would allow more flexibility in your nets, hence, 'Flexible A* Pathfinding System'!

Cool work though, I like the pic!

Edit -> If you have a large wilderness, won't there be a point where it takes to long to build the navMesh, and get to the point where it delays the gameplay?

Edit2 -> Unless you got a system where it only compiles the new node, and the nodes that it would be connected to, then apply the changes to the navNet.
#46
05/07/2008 (7:31 pm)
@Sam: Good catch. Replace that code with:
if (path.size() > 2 && !gServerContainer.castRay(location, path[2], STATIC_COLLISION_MASK, &rInfo))
      {
         Point3F a(path[0]-path[1]);
         Point3F b(path[1]-path[2]);
         a.normalize();
         b.normalize();
         tol = mDot(a, b)+1.05f;
         tol = tol > 1 ? tol*gAStar.clearence : gAStar.clearence;
      }

EDIT: Ok, I put that change into the download.
#47
05/07/2008 (7:35 pm)
@Nathan: Compiling only the new node would be very easy to do. You could write a function using only the inner loop from linkMeshes, and run it for the new node and all its adjacent points.
#48
05/12/2008 (9:47 am)
Nathan: I'm actually planning to do my mesh recalcs offline anyway, since I'm having sporadic problems where they sometimes calculate screwy. I'm setting up the live game servers to write the unprocessed info when they close down, and I will manually download the files every so often, process them at home, and re-upload the processed files to the live servers.

(My game runs in a highly 'instanced' way, so each game server typically runs for between 30 minutes and 2 hours before closing).
#49
06/04/2008 (4:47 pm)
Dan, do you think there might be something untoward happening in the code used by findBasicPath() and the other functions that do the actual a*star magic?

I spent about the last 2 weeks gathering node data from my players, without any trouble. Then I deployed the actual pathfinding today. It turned out to crash all over the place, never actually in the pathfinding code itself, but many different places in the engine. In one instance, I had a bunch on console variables get messed up too. It's like the wrong data is being written to or something? Is this possible?
#50
06/04/2008 (5:22 pm)
Hmm I think I found it actually. I removed your nav mesh objects altogether, and *ahem* forgot to resize openList when new points were dynamically added. Therefore
dMemset(openList, 0, sizeof(AStarPoint)*points);
was causing mayhem..
#51
06/20/2008 (9:04 am)
Stefan's variant of the code fits nicely into 1.7.1. devoid of hassle.

Excellent work, Dan.
#52
07/10/2008 (3:18 pm)
Very good resource, worked the first time on 1.4.2. One thing I noticed is that setMoveDestination() no longer works. You can call it without a console error, but the AI Player won't move. The reason I need this is because I need my AI Players to be able to step in and out of cover quickly without calculating a path. Is there any way to bring back setMoveDestination without removing the Astar pathfinding?
#53
07/10/2008 (6:25 pm)
Also, how would you get the position of the very last node in a path? Instead of the both slowing down for each node (you can see the bot moving and slowing down repeatedly), how could we make him slow down only for the last node on the path?
#54
07/13/2008 (1:22 pm)
You can try adding a bool that is set in setMoveDestination that temporarily disables paths.

For the second one, try adding something like
...
         if (path.size())
         {
            mMoveDestination = path[1];
            path.pop_front();
            [b]if (path.size())
               mMoveSlowdown = false;
            else
               mMoveSlowdown = true;[/b]
         }
         else
         {
            stopMove();
            throwCallback("onReachDestination");
         }
...
#55
07/21/2008 (10:35 am)
Also, has the annoying spinning been fixed by anyone yet?
#56
07/29/2008 (10:32 am)
Spinning is caused by the bot missing the node. Either slow down your bots or increase $Pref::AStar::Clearence to fix it.
#57
08/07/2008 (1:31 pm)
Dan, do you have any ideas about how to improve the search efficiency? Some of my wilderness maps now have 5 or 6 MB of astar data.

I guess the key thing is not to have any bot try to route to anywhere that's too far away, since that's when the search really starts to open out.

Some sort of binning system perhaps??
#58
08/11/2008 (6:55 pm)
I'm having some issues with this resource under TGEA+AFX 1.7.1. (I'm using Stefan Grevan's TGEA code.)

If I use findPathTo() to send a bot to "0 -100 0", he runs to roughly "0 -91 0". findPathTo("0 -150 0") then causes him to turn around and run to a point near 0,0,0. This "run to 0,0,0" behavior seems to happen a lot, but I haven't found a pattern to it.

I'm using two navMeshes with an interval of 8 in a large interior with a few obstacles; is this resolution too coarse?

Edit: With a little tweaking of the mesh parameters, my bots are now successfully navigating with findPathTo(). Apparently, my clearance pref and interval were set a bit too high.

Also, I can't get wander() to work at all. The bot changes facing, but doesn't move, even though the function returns a 0 (success). I take it from comments above that this remains an unresolved issue?

And on a related note, I'm not that familiar with A*'s quirks... is it better to have several small navMeshes, or one large navMesh?

Awesome resource so far, I'm impressed with what it can do!
#59
08/12/2008 (9:02 am)
Ah, here's another hiccup... I send a bot to "0 0 0". It runs to the side and stops at "27.4 -2.5 0.04". I issue the command again, and I get a "1" return code -- it thinks it's at the destination (which is 27 units away).

My mesh has an interval of 2. "Clearence" is 0.4. Any ideas what's gone wrong?
#60
08/16/2008 (9:31 pm)
Ok, so I'm having an issue with wander. I put in Nathan Kent's fix for OnReachDestination, and now wander doesn't work at all. I'm guessing that somewhere path[0] is reset to path[1], but I'm not really a programmer, and don't know much C++ at all, so I can't figure out where the problem is.

Anyone else run into this and find a solution?