I want to put pathfinding in my game for the purpose of walking the character around, as well as to give NPC's some intelligence. The players pathfinding will be used for when the player wants to walk somewhere. The NPC pathfinding will be used for 'wandering' npcs. The other type of npc will be stationary. Other times when npc pathfinding will be used is when the npc is chasing or following the player.
There are heaps of online tutorials about pathfinding in games. A really good page on advanced AI and game programming topics is
Amit's Page.
The page I mainly followed to learn pathfinding is this one:
http://www.policyalmanac.org/games/aStarTutorial.htm. It has a fantastic visually represented tutorial on A* (A Star) Pathfinding. I recommend you read it if you're interested in learning.
However, whlist the above article is really informative, the implementation side of things is quite confusing. He provides an example in C, which is useful, but only if you have a firm grip on pointers and stacks. Now I've never done much work with stacks (not since experimenting with ASM years ago) so I had a bit of a hard time understanding his code. I did manage to integrate his code into my own, but it just didnt feel right. So I set out and made a more simplified (less efficient) version.
Rather than using a stack, or linked list for organising my nodes, I used a static array. The downfall of doing this is that each iteration its scanning the entire array, and not just the nodes in question. But for the purpose of an RPG where the map is small and its not real time, its fine.
I struggled for a bit to understand how to get an actual path from the nodes that are evaluated, but eventually worked it out.
Here is my inefficient code:
#include <stdio.h>
#include <stdlib.h>
#include "allegro.h"
BITMAP *map;
#define MAPW 16
#define MAPH 12
struct list_s
{
int x;
int y;
int f;
} path[10000];
struct NODE
{
int walkable;
int onopen;
int onclosed;
int g;
int h;
int f;
int parentx;
int parenty;
} node[MAPW][MAPH];
int count;
void initnodes()
{
int x,y;
for (x=0;x<MAPW;x++)
{
for (y=0;y<MAPH;y++)
{
node[x][y].walkable = getpixel(map,x,y);
node[x][y].onopen = FALSE;
node[x][y].onclosed = FALSE;
node[x][y].g = 0;
node[x][y].h = 0;
node[x][y].f = 0;
node[x][y].parentx = 0;
node[x][y].parenty = 0;
}
}
}
struct list_s *findpath(int startx, int starty, int endx, int endy)
{
int x=0,y=0; // for running through the nodes
int dx,dy; // for the 8 squares adjacent to each node
int cx=startx, cy=starty;
int lowestf=10000; // start with the lowest being the highest
// add starting node to open list
node[startx][starty].onopen = TRUE;
node[startx][starty].onclosed = FALSE;
//////////////////////LOOP BEGINS HERE/////////////////////////
while (cx!=endx || cy!=endy)
{
//look for lowest F cost node on open list - this becomes the current node
lowestf=10000;
for (x=0;x<MAPW;x++)
{
for (y=0;y<MAPH;y++)
{
node[x][y].f = node[x][y].g + node[x][y].h;
if (node[x][y].onopen)
{
rect(screen,(x*40)+1,(y*40)+1,(x*40)+39,(y*40)+39,makecol(0,0,255));
if (node[x][y].f<lowestf)
{
cx = x;
cy = y;
lowestf = node[x][y].f;
}
}
else if (node[x][y].onclosed)
rect(screen,(x*40)+1,(y*40)+1,(x*40)+39,(y*40)+39,makecol(0,255,0));
}
}
// we found it, so now put that node on the closed list
node[cx][cy].onopen = FALSE;
node[cx][cy].onclosed = TRUE;
// for each of the 8 adjacent node
for (dx=-1;dx<=1;dx++)
{
for (dy=-1;dy<=1;dy++)
{
if ((dx!=0) || (dy!=0))
{
if ((cx+dx)<MAPW && (cx+dx>-1) && (cy+dy)<MAPH && (cy+dy)>-1)
{
// if its walkable and not on the closed list
if (node[cx+dx][cy+dy].walkable==0 && node[cx+dx][cy+dy].onclosed==FALSE)
{
//if its not on open list
if (node[cx+dx][cy+dy].onopen==FALSE)
{
//add it to open list
node[cx+dx][cy+dy].onopen = TRUE;
node[cx+dx][cy+dy].onclosed = FALSE;
//make the current node its parent
node[cx+dx][cy+dy].parentx = cx;
node[cx+dx][cy+dy].parenty = cy;
//work out G
if (dx!=0 && dy!=0) node[cx+dx][cy+dy].g = 14; // diagonals cost 14
else node[cx+dx][cy+dy].g = 10; // straights cost 10
//work out H
//MANHATTAN METHOD
node[cx+dx][cy+dy].h =
(abs(endx-(cx+dx))+abs(endy-(cy+dy)))*10;
node[cx+dx][cy+dy].f =
node[cx+dx][cy+dy].g + node[cx+dx][cy+dy].h;
textprintf_ex(screen,font,((cx+dx)*40)+2,((cy+dy)*40)+2,
makecol(255,255,255),0,"%d",node[cx+dx][cy+dy].g);
textprintf_ex(screen,font,((cx+dx)*40)+2,((cy+dy)*40)+12,
makecol(255,255,255),0,"%d",node[cx+dx][cy+dy].h);
textprintf_ex(screen,font,((cx+dx)*40)+2,((cy+dy)*40)+22,
makecol(255,255,255),0,"%d",node[cx+dx][cy+dy].f);
}
//otherwise it is on the open list
else if (node[cx+dx][cy+dy].onclosed==FALSE
&& node[cx+dx][cy+dy].onopen==TRUE)
{
if (dx==0 || dy==0) // if its not a diagonal
{
if (node[cx+dx][cy+dy].g==14) //and it was previously
{
node[cx+dx][cy+dy].g = 10; // straight score 10
//change its parent because its a shorter distance
node[cx+dx][cy+dy].parentx = cx;
node[cx+dx][cy+dy].parenty = cy;
//recalc H
node[cx+dx][cy+dy].h =
(abs(endx-(cx+dx))+abs(endy-(cy+dy)))*10;
//recalc F
node[cx+dx][cy+dy].f =
node[cx+dx][cy+dy].g + node[cx+dx][cy+dy].h;
}
}
}//end else
}// end if walkable and not on closed list
}
}
}
}//end for each 8 adjacent node
}//end while
//follow all the parents back to the start
count=0;
cx = endx;
cy = endy;
while (cx!=startx || cy!=starty)
{
path[count].x = node[cx][cy].parentx;
path[count].y = node[cx][cy].parenty;
path[count].f = node[cx][cy].f;
cx = path[count].x;
cy = path[count].y;
count++;
if (count>100) break;
}
return path; //we're done, return a pointer to the final path;
}//end function
int init()
{
//INITIALISE ALLEGRO
printf("Starting Engine\nallegro_init()... ");
if (allegro_init())
{
allegro_message("Cannot initalize Allegro.\n");
return 1;
}
else printf("Success!\n");
//INITIALISE KEYBOARD
printf("install_keyboard()... ");
if (install_keyboard())
{
allegro_message("Keyboard error.\n");
return 1;
}
else printf("Success!\n");
//INSTALL TIMER
printf("install_timer()... ");
if (install_timer())
{
allegro_message("Cannot start timers.\n");
return 1;
}
else printf("Success!\n");
//START MOUSE
printf("install_mouse()... ");
if (install_mouse()==-1)
{
allegro_message("Cannot start mouse.\n");
return 1;
}
else printf("Success!\n");
//CHANGE GRAPHICS MODE
set_color_depth(8);
if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0)!=0)
{
printf("Cannot start graphics mode\n%s\n", allegro_error);
return 1;
}
//START SOUND DRIVERS
if (install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL) != 0)
{
printf("Error initialising sound system\n%s\n", allegro_error);
return 1; //CHANGE BACK TO 1 after DEBUG
}
//everything is okay, so return!
return 0;
}
int main(int argc, char* argv[])
{
srandom(time(0));
int i,x,y;
//run the initialisation
if (init())
{
allegro_exit();
return 1;
}
map = load_bitmap("map.pcx",NULL);
initnodes();
for (x=0;x<640;x+=40)
{
for (y=0;y<480;y+=40)
{
rect(screen,x,y,x+40,y+40,makecol(255,255,255));
if (node[x/40][y/40].walkable)
rectfill(screen,x,y,x+40,y+40,makecol(255,255,255));
}
}
struct list_s *thepath = findpath(0,0,15,11);
for (i=0;i<count-1;i++)
{
rect(screen,(thepath[i].x*40)+1,(thepath[i].y*40)+1,
(thepath[i].x*40)+39,(thepath[i].y*40)+39,makecol(255,0,0));
line(screen,(thepath[i].x*40)+20,(thepath[i].y*40)+20,
(thepath[i+1].x*40)+20,(thepath[i+1].y*40)+20,makecol(255,0,0));
}
readkey();
if (set_gfx_mode(GFX_TEXT, 0, 0, 0, 0)!=0)
{
printf("Cannot start graphics mode\n%s\n", allegro_error);
return 1;
}
allegro_exit();
return 0;
}
END_OF_MAIN()
I know I used MAPW and MAPH, but it only works at the ones I specified because I got lazy when testing it. It does work, and like I said its not the most efficient, but definitely easy to understand. I hope someone might get some use out of it, and let me know if you implement my method in your code!
Here is what the output looks like: