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.
#81
02/05/2009 (4:12 pm)
@Deaconsyre:

I think you're misreading the code. That doesn't delete the second, that deletes the first. It's a linked list, so the pt->prev on the second node points to the first node.
#82
02/05/2009 (4:24 pm)
@Jaimi

I know what it's try to do, but for some reason when it deletes the first node the second node disappears anyway. I watched both nodes expanded in VS2005's watch window as it did this. I've started talking with one of my programming teachers and he thought that maybe the destructor for the node was deleting the next node on accident. I looked into this briefly but didn't find anything, kinda short on time.
#83
02/05/2009 (5:07 pm)
And you are looking at pt (the second node) and pt->prev? Unless you've modified the code, there is no destructor - Have you made any modifications to the engine? It's possible some memory is being overwritten.
#84
02/06/2009 (7:14 am)
Does this work on Interiors? I was under the impression that it did. I am able to get it to work on terrains but not interiors. Could anyone let me know how to get it to work on Interiors and hopefully transition between the two?
#85
02/06/2009 (1:15 pm)
It should work on any collidable object. What problem exactly are you having?
#86
02/06/2009 (1:31 pm)
I have just placed an interior on the terrain, the navmesh goes around the interior but not 'inside'. Do I need to create a seperate navmesh for that?
#87
02/06/2009 (6:46 pm)
The raycast to drop navigation points starts at the top of the navmesh bounding box and goes to the bottom, so if the top is intersecting a solid object the nodes will be placed inside it. Try setting the height higher or moving the mesh up or down.
#88
02/06/2009 (11:17 pm)
I am unable to get this working in TGEA 1.71. Code compiles fine, I put the navemesh in my scene. It is drawing all the lines to where the nav nodes are.

When I put in an AIPlayer and call followpathto("playerName"), where playername is the ID of my player, it is unable to path. I traced it to the AStar::findBasicPath function, it always returns -1, even though both the AIPlayer and my player are within the navmesh area.

Also, the wander command causes a crash because on line 1016,
if (mAbs(findDirec(Vpoints[0] - start->loc) - findDirec(Pstart - start->loc)) > 2)

VPoints is out of bounds.
#89
02/07/2009 (7:25 am)
I solved my own problem, it seems the NavMesh was not building correctly because the height was too large (50). I reduced height to 20 and the NavMesh built correctly, and now works.

Next step is to alter on reach destination to somehow be recognized by a way other than the player stopping to move (seamless transitions between path nodes).

====
Edit:
setMoveDestination has a bool called slowdown as a parameter. Altering the functionality so that the AIPlayer does not slow down along the path by removing this functionality or making it false will give better behavior. Better would be to only call it when reaching the final destination node.

Also, there seems to be a quirk that the navMesh doesn't generate when loading a level. I have to fidget with its size a bit for it to work.

I also noticed that setMoveDestination no longer works with this resource. It would be nice to have setMoveDestination for standard following, and only switch to pathfinding when the AIPlayer is in complex situations.

==============
Edit:
To make the NavMesh autobuild on start, you need to uncomment linkMeshes() in AStar::addMesh function.
#90
02/10/2009 (2:08 am)
Thanks Dan, I got the navmesh working inside my interior.
#91
02/10/2009 (7:57 pm)
Also the ::getAIMove() function in aiPlayer.cpp has a bug in it. Luckily it has a quick fix:

if (mFabs(xDiff) < tol && mFabs(yDiff) < tol) {
		 if (path.size() > 1)
		 {
			path.pop_front();
			mMoveDestination = path[1];
		 }

Should be:

if (mFabs(xDiff) < tol && mFabs(yDiff) < tol) {
		 if (path.size() > 2) //<----- change this to a 2!
		 {
			path.pop_front();
			mMoveDestination = path[1];
		 }

As it is, if there are 2 things in the vector, it pops one off, then tries to access the second... which isn't there.

EDIT: For clarity.
#92
02/20/2009 (11:12 am)
I've tried this project and I'm not sure how to actually put the .cpp and .h files into the project. Also, when I try to creat a NavMesh with the name TestNav, nothing happens. When I go into the console to see what happens, it tells me "Tried to get the object for item 254, which is not InspectorData!" and "editor/newObject.cs (0): Unable to instantiate non-conobject class NavMesh." Somebody please help!
#93
02/23/2009 (7:02 pm)
I finally figured out that mem-access violation exception. It turns out that for some reason the list of points is getting set up weird and all the memory for the list is being allocated in a single chunk. What this means then is that the header information for this memory chunk is for all the nodes, and only exists for the first node.

So when you delete the first node you instantly delete the second (and all subsequent) nodes. Thus causing the exception. I did some checks and this appears to be the proper thing to do however, just don't try to delete anything else. Unless theres some memory hiding somewhere that my resource monitor can't see/understand there's no leak. So instead of the big for loop you can just delete list.

I've also confirmed that this memory belongs to the single header by allowing it to check the header's validity on the second node (via debug delete method) and having it return invalid. so for delete paths all you need is
void AStar::deleteAll()
{
	if (!list)
		return;

	while(meshes.size())
	{
		meshes[0]->deleteObject();
		meshes.pop_front();
	}

	delete list;
	list = 0;
	points = 0;
}
#94
02/23/2009 (8:49 pm)
Whoops, I'd forgotten all about that.
#95
03/03/2009 (5:14 pm)
This is a great resource and it is working for me.

I am having a problem though… I am building levels out of ts static objects. I use them for floors and walls. The mesh will build just fine on top of the ts static object floors, but it builds the mesh inside the ts static walls.

I noticed that on other things like interior objects it builds the mesh around them. I added the ts static objects type to the raycast in hopes that it just wasn’t looking for them, but that didn’t help.

Can someone please point me in the right direction to solve this problem?
#96
03/03/2009 (6:02 pm)
Do you have collision boxes set up for the walls?
#97
03/04/2009 (10:33 am)
Thanks for your quick response.

I have talked with my artist and it seems that we do have collision boxes, but it is hard for me to tell... but he says the collision boxes are there. I do know that our player runs into walls and that our projectiles bounce off the walls.

As a side note about the problem, I do notice that some of the blue lines don't show up when they go into the walls. It seems like the main lines between the nodes show up though. I am not sure why some would know not to be there and others don't.

Any help would be appreciated.

Edit:
Sometimes it almost works on the walls but one or two lines will still show and sometimes all the lines will go through the walls.
#98
03/04/2009 (1:10 pm)
Can you take a screenshot of the problem?
#99
03/04/2009 (3:24 pm)
Ok this is the first time I have put images on a forum so I hope this goes ok...

Here is an example room and the nav mesh:
img7.imageshack.us/img7/8820/screenshot00100003pp5.png
This is after I build the paths and it is closer so you can see the blue lines:
img23.imageshack.us/img23/6108/screenshot00100005al6.png
Hopfully you can see that the blue lines go into the walls and the blue block in the middle of the room is a wall also. The floor is solid (no holes under the walls) but I don't think that should matter.

If you need anymore info just let me know.

Thanks
#100
03/04/2009 (6:22 pm)
I would recommend moving the mesh so the nodes are in the centers of the squares rather than the edges. This would make the most sense with the layout of the level, especially the doors.

You could also try setting $pref::AStar::Clearence higher. This is the lateral clearance that the path builder will require to connect two nodes.