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.
Page «Previous 1 2 3 4 5 6 7 Last »
#1
04/04/2008 (7:44 pm)
This is pretty cool, the sneakupon, searchcover, and findcover from are all useful.
#2
04/05/2008 (2:30 am)
Thank you for releasing this. There have been other A* resources in the past but they've always been a pain to implement, this one looks easy enough and also looks like a solid base to expand on for developers.
#3
04/05/2008 (9:49 am)
Thanks, Dan! :)

There are some kind of large problems though. Wander doesn't work for me at all, the AIPlayer just stands there spinning at a ridiculous speed. The same thing happens with some commands, at times, the player doesn't do what he's supposed to and spins instead.

It's kind of annoying how the player stops at every node before moving on to the next one, too. :\
#4
04/05/2008 (1:50 pm)
Thanks a lot Dan!

I've just ported your resource to TGEA 1.0.3 but have some trouble with the commands as well (wander works for me though). I'll check that again tomorrow.
#5
04/05/2008 (3:13 pm)
@Maddermadcat: ROFL!!1!!!1one! ... Now that I have that out of my system, what are you passing as an argument to wander?

I actually don't really like the way wander works, so I'm reworking it.
#6
04/05/2008 (4:31 pm)
Yeah, I called it through the console as bob.wander(20); .

Are you planning on smoothing out the AIPlayer's movement anytime in the future?

EDIT: Weird, wander no longer causes him to spin all the time. :P

However, all of the behaviours seem to experience this problem much more frequently than I'd like.
#7
04/06/2008 (5:11 am)
I think

if (SceneObject *obj = dynamic_cast<SceneObject*>(Sim::findObject([b]end.x[/b])))

should be

if (SceneObject *obj = dynamic_cast<SceneObject*>(Sim::findObject([b]argv[2][/b])))

in the ConsoleMethods findPathTo and findCoverFrom.

This is the rendering code I use in TGEA 1.0.3, maybe somebody needs it:

void AStar::render(SceneState* state)
{
    if (!Con::getBoolVariable("$pref::AStar::Render", false))
        return;

    int i;

    for (NMPoint* tmp = list; tmp; tmp = tmp->next) //iterate thru list
    {
	  PrimBuild::begin( GFXLineList, 2 );
		PrimBuild::color3f( 0, 1, 0 );
		PrimBuild::vertex3f(tmp->loc.x, tmp->loc.y, tmp->loc.z);
		PrimBuild::vertex3f(tmp->loc.x, tmp->loc.y, tmp->loc.z-1);
	  PrimBuild::end();

      for (i=0; i<8; i++)
	  {
		if (!tmp->adjs[i])
		  continue;

		PrimBuild::begin( GFXLineList, 2 );
		  PrimBuild::color3f( 0, 0, 1 );
		  PrimBuild::vertex3f(tmp->loc.x, tmp->loc.y, tmp->loc.z);
		  PrimBuild::vertex3f(tmp->adjs[i]->loc.x, tmp->adjs[i]->loc.y, tmp->adjs[i]->loc.z-1);
		PrimBuild::end();
      }
    }
}
#8
04/06/2008 (10:55 am)
@Maddermadcat -> Try slowing down the AI; if your problem is the same as mine, they spin because they overshot the node.

This is awesome! Way to go! The only problem I'm having is that the regular path system no longer works, I could work around it, but I'd rather not.
#9
04/06/2008 (5:10 pm)
Er, Nathan, how?
#10
04/06/2008 (5:36 pm)
(AI id).setMoveSpeed(.5); -- Slows them down to half their regular speed.


Ok, I fixed my problem. In order to get the regular AIDatablock::OnReachDestination callback, you need to make this change:
bool AIPlayer::getAIMove(Move *movePtr)
{
...
...
if (mFabs(xDiff) < gAStar.clearence && mFabs(yDiff) < gAStar.clearence) {
         if (path.size())
         {
			if(mMoveDestination != path[1]) {
				mMoveDestination = path[1];
				path.pop_front();
			}
			else {
				stopMove();
				throwCallback("onReachDestination");
			}
         }
         else
         {
            stopMove();
            throwCallback("onReachDestination");
         }
      }
      else {
...
...
}
#11
04/06/2008 (7:52 pm)
instead of

if (path.size() != 0.0f)

you could probably just say

if (path.size())

because the first one is comparing an int to a float.

EDIT:

@Stefan: Thanks. I'm about to switch to tgea, so that was very helpful.

@Maddermadcat: You could also try setting $Pref::AStar::Clearance higher
#12
04/06/2008 (8:20 pm)
To smooth out the paths, add the bold code

if (mMoveState == ModeMove) {
      F32 xDiff = mMoveDestination.x - location.x;
      F32 yDiff = mMoveDestination.y - location.y;
[b]
      F32 tol;

      if (path.size() > 2 && !gServerContainer.castRay(location, path[2], STATIC_COLLISION_MASK, &rInfo))
      {
         tol = mDot(path[0]-path[1], path[1]-path[2])+1.05f;
         tol = tol > 1 ? tol*gAStar.clearence : gAStar.clearence;
      }
      else
         tol = gAStar.clearence;
[/b]
      // Check if we should mMove, or if we are 'close enough'
      // AStar clearence is used to give us enough room to get around corners
      if (mFabs(xDiff) < [b]tol[/b] && mFabs(yDiff) < [b]tol[/b]) {
#13
04/06/2008 (9:22 pm)
Has anyone tried using the a* in the new 1.7 TGEA system?

I am trying it now. If get it I will let you know but any help would be great.

Thank you
#14
04/07/2008 (12:50 am)
@Nathan, just to be sure. The code you posted is to fix the regular path system?

@Dan, maybe you want to add the TGEA port to the zip file as well :) I could make a complete list with the needed changes. Thanks for the smoothing code, I'll try it later.

@Eugene I guess the PrimBuilder does still exist in 1.7 and the code I posted above should work (you'll need to include primBuild.h, remove dgl.h and such though).
#15
04/07/2008 (4:05 am)
@Stefan -> No, it just fixes the "onReachDestination" callback. If you want regular path following,you'll have to do something like this:
function AIDatablock::onReachDestination(%datablock, %this)
{
   %count = %this.Path.getCount();
   
   // The number of times to go around the path
   %times = %this.Path.circleCount;
   
   if (%this.nodeCount >= %count * %times).
   {                     
      // Gone around the path!
   }
   else {
      %this.nodeCount++;
      %this.nextNode();
      // Go to the next node
   }
}

function AIPlayer::nextNode(%this) {
   // Move to the given path node index
   %this.oldNode = %this.currentNode;
   %this.currentNode++;
   
   if (%this.currentNode > %this.Path.getCount() - 1)
      %this.currentNode = 0;
   %node = %this.Path.getObject(%this.currentNode);
   %this.findPathTo(%node.getPosition());
}
@Dan Keller -> Under 'F32 tol;', you need to have 'RayInfo rInfo;', otherwise it won't compile.

I can't guaranty that I'll get this done, but I'll look into compiling and saving the navNet for faster load times. Also, I think that there's a memory leak somewhere, with 15000 nodes (only 63 second load time increase!) I'm unable to exit the program. Take out the nodes, or put in less, and it quits, otherwise I have to force quit it.
#16
04/07/2008 (3:04 pm)
Found the exiting problem. After each navMesh was deleted, the program wanted to build the navNet again. With over 16000 nodes (as of last count), that could be a problem! The fix isn't all that great, and if you're deleting meshes in the editor you'll have to type buildPaths() in the console, but here it is:

Comment out the linkMesh() at the bottom of AStar::removeMesh (NavMesh *mesh), and it'll quit.


Saving the navNet comming soon! I would have it by now, but Torque just doesn't want to accept the standered serialization method...
#17
04/07/2008 (5:55 pm)
I just bought tgea so i'll have a port up soon.
#18
04/09/2008 (7:29 pm)
Any word on getting this working for TGEA 1.7?
#19
04/09/2008 (11:09 pm)
fireVein I'm checking a port to 1.7 later today.
#20
04/10/2008 (7:28 am)
Looks like it'll work in 1.7. The path meshes aren't rendered properly but that's only a question of time :)
Page «Previous 1 2 3 4 5 6 7 Last »