MicroPather is a path finder and A* solver (astar or a-star) written in platform independent C++ that can be easily integrated into existing code. MicroPather focuses on being a path finding engine for video games but is a generic A* solver.
There is plenty of pathing code out there, but most if it seems to focus on teaching somewhat how to write an A* solver, rather than being utility code for pathing. MicroPather is firmly aimed at providing functionality, not at being a tutorial.
MicroPather's primary goal is to be easy to use:
- An easy API
- No requirements on the host program to change its data structures or objects
- No library or 'make' - just add 1 .cpp and 1 .h file to your project.
Enjoy, and thanks for checking out MicroPather!
官方網站可以下載其sdk和demo。
demo1的程式碼如下:
dungeon.cpp
#define USE_PATHER
#include <ctype.h>
#include <stdio.h>
#include <memory.h>
#include <math.h>
#include <vector>
#include <iostream>
#ifdef USE_PATHER
#include "micropather.h"
using namespace micropather;
#endif
const int MAPX = 30;
const int MAPY = 10;
const char gMap[MAPX*MAPY+1] =
//"012345678901234567890123456789"
" | | |"
" | |----+ | +"
"---+ +---DD-+ +--+--+ "
" | +-- +"
" +----+ +---+ "
"---+ + D D | "
" | | +----+ +----+ +--+"
" D | | | "
" | +-------+ +-+ |--+ "
"---+ | +";
class Dungeon
#ifdef USE_PATHER
: public Graph
#endif
{
private:
Dungeon( const Dungeon& );
void operator=( const Dungeon& );
// 尋路的起始點位置
int playerX, playerY;
// 尋路的最終路徑
std::vector<void*> path;
// 門是否開啟
bool doorsOpen;
//
bool showConsidered;
MicroPather* pather;
public:
Dungeon() : playerX( 0 ), playerY( 0 ), doorsOpen( false ), showConsidered( false ), pather( 0 )
{
// this : The "map" that implements the Graph callbacks.
// 20 : How many states should be internally allocated at a time.
// This can be hard to get correct. The higher the value,
// the more memory MicroPather will use.
pather = new MicroPather( this, 20 ); // Use a very small memory block to stress the pather
}
virtual ~Dungeon()
{
delete pather;
}
int X() { return playerX; }
int Y() { return playerY; }
// 校驗和,用於debug
unsigned Checksum() { return pather->Checksum(); }
void ClearPath()
{
#ifdef USE_PATHER
path.resize( 0 );
#endif
}
//
void ToggleTouched()
{
showConsidered = !showConsidered;
// Reset() Should be called whenever the cost between states or
// the connection between states changes.
// Also frees overhead memory used by MicroPather,
// and calling will free excess memory.
pather->Reset();
}
// 開啟或者關閉Door
void ToggleDoor()
{
doorsOpen = !doorsOpen;
#ifdef USE_PATHER
pather->Reset();
#endif
}
// 目標點(nx, ny)是否可到達(" "或者"D")
int Passable( int nx, int ny )
{
if ( nx >= 0 && nx < MAPX
&& ny >= 0 && ny < MAPY )
{
int index = ny * MAPX + nx;
char c = gMap[index];
if ( c == ' ' )
return 1;
else if ( c == 'D' )
return 2;
}
return 0;
}
// 計算起始位置到(nx,ny)的路徑及其代價
int SetPos( int nx, int ny )
{
int result = 0;
if ( Passable( nx, ny ) == 1 )
{
#ifdef USE_PATHER
float totalCost;
if ( showConsidered )
pather->Reset();
/*
int micropather::MicroPather::Solve
( void * startState,
void * endState,
std::vector< void * > * path,
float * totalCost
)
Solve for the path from start to end.
Parameters:
startState: Input, the starting state for the path.
endState: Input, the ending state for the path.
path: Output, a vector of states that define the path. Empty if not found.
totalCost: Output, the cost of the path, if found.
Returns:
Success or failure, expressed as SOLVED, NO_SOLUTION, or START_END_SAME.
*/
result = pather->Solve( XYToNode( playerX, playerY ), XYToNode( nx, ny ), &path, &totalCost );
if ( result == MicroPather::SOLVED )
{
// 將玩家位置設定為(nx, ny)
playerX = nx;
playerY = ny;
}
printf( "Pather returned %d\n", result );
#else
playerX = nx;
playerY = ny;
#endif
}
return result;
}
void Print()
{
char buf[ MAPX + 1 ];
std::vector<void*> stateVec;
if ( showConsidered )
pather->StatesInPool(&stateVec);
printf(" doors %s\n", doorsOpen ? "open" : "closed");
printf(" 0 10 20\n");
printf(" 012345678901234567890123456789\n");
for( int j=0; j<MAPY; ++j )
{
// 按行復制, 並輸出
memcpy(buf, &gMap[MAPX * j], MAPX + 1);
buf[MAPX] = 0;
#ifdef USE_PATHER
unsigned k;
// Wildly inefficient demo code.
for( k=0; k<path.size(); ++k )
{
int x, y;
NodeToXY( path[k], &x, &y );
if ( y == j )
buf[x] = '0' + k % 10;
}
if ( showConsidered )
{
for( k=0; k<stateVec.size(); ++k )
{
int x, y;
NodeToXY( stateVec[k], &x, &y );
if ( y == j )
buf[x] = 'x';
}
}
#endif
// Insert the player
if ( j == playerY )
buf[playerX] = 'i';
printf( "%d%s\n", j % 10, buf );
}
}
#ifdef USE_PATHER
void NodeToXY( void* node, int* x, int* y )
{
int index = (int)node;
*y = index / MAPX;
*x = index - *y * MAPX;
}
void* XYToNode( int x, int y )
{
return (void*) ( y*MAPX + x );
}
// 最小代價
virtual float LeastCostEstimate( void* nodeStart, void* nodeEnd )
{
int xStart, yStart, xEnd, yEnd;
NodeToXY( nodeStart, &xStart, &yStart );
NodeToXY( nodeEnd, &xEnd, &yEnd );
/* Compute the minimum path cost using distance measurement. It is possible
to compute the exact minimum path using the fact that you can move only
on a straight line or on a diagonal, and this will yield a better result.
*/
int dx = xStart - xEnd;
int dy = yStart - yEnd;
return (float) sqrt( (double)(dx*dx) + (double)(dy*dy) );
}
// Return the exact cost from the given state to all its neighboring states.
// This may be called multiple times, or cached by the solver.
virtual void AdjacentCost( void* node, std::vector< StateCost > *neighbors )
{
int x, y;
// 在X,Y軸上8個方向 E SE S SW W NW N NE
const int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 };
const int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };
// X,Y軸上8個方向的代價
const float cost[8] = { 1.0f, 1.41f, 1.0f, 1.41f, 1.0f, 1.41f, 1.0f, 1.41f };
NodeToXY( node, &x, &y );
// 得到與該點相鄰的8個方向的點的座標;計算是否可通過;將代價push到vector中
for( int i=0; i<8; ++i )
{
int nx = x + dx[i];
int ny = y + dy[i];
int pass = Passable( nx, ny );
if ( pass > 0 )
{
if ( pass == 1 || doorsOpen )
{
// Normal floor
StateCost nodeCost =
{
// (nx, ny)點的索引號
XYToNode( nx, ny ),
// node到該點的代價
cost[i]
};
neighbors->push_back( nodeCost );
}
else
{
// 若不可pass則代價為FLT_MAX
StateCost nodeCost = { XYToNode( nx, ny ), FLT_MAX };
neighbors->push_back( nodeCost );
}
}
}
}
virtual void PrintStateInfo( void* node )
{
int x, y;
NodeToXY( node, &x, &y );
printf( "(%d,%d)", x, y );
}
#endif
};
int main( int /*argc*/, const char** /*argv*/ )
{
{
Dungeon test;
const int NUM_TEST = 5;
int tx[NUM_TEST] = { 24, 25, 10, 6, 0 }; // x of test
int ty[NUM_TEST] = { 9, 9, 5, 5, 0 }; // y of test
int door[NUM_TEST] = { 0, 0, 0, 1, 0 }; // toggle door? (before move)
unsigned check[NUM_TEST] = { 139640, 884, 0, 129313, 2914 };
for( int i=0; i<NUM_TEST; ++i )
{
// (25,9)到(10, 5)時, (10, 5)正好被不通路包圍的中心,這個不通路有2扇門,
// 此時2扇門沒開啟,所以(25,9)到(10, 5)不通!
// (10,5)到(6,5)之間正好有一扇門,若門關閉則此路不通,門開啟則可以通
if ( door[i] )
test.ToggleDoor();
int _result = test.SetPos( tx[i], ty[i] );
if ( _result == MicroPather::SOLVED )
{
// Return the "checksum" of the last path returned by Solve().
// Useful for debugging, and a quick way to see if 2 paths are the same.
unsigned checkNum = test.Checksum();
if ( checkNum == check[i] )
printf( "Test %d to (%d,%d) ok\n", i, tx[i], ty[i] );
else
printf( "Test %d to (%d,%d) BAD CHECKSUM\n", i, tx[i], ty[i] );
}
else if (_result == MicroPather::NO_SOLUTION)
{
printf( "Test %d to (%d,%d) no solution\n", i, tx[i], ty[i] );
}
else if (_result == MicroPather::START_END_SAME)
{
printf( "Test %d to (%d,%d) start end same\n", i, tx[i], ty[i] );
}
}
}
Dungeon dungeon;
bool done = false;
char buf[ 256 ];
while ( !done )
{
dungeon.Print();
printf( "\n# # to move, q to quit, r to redraw, d to toggle doors, t for touched\n" );
std::cin.getline( buf, 256 );
if ( *buf )
{
if ( buf[0] == 'q' )
done = true;
else if ( buf[0] == 'd' )
{
dungeon.ToggleDoor();
dungeon.ClearPath();
}
else if ( buf[0] == 't' )
dungeon.ToggleTouched();
else if ( buf[0] == 'r' )
dungeon.ClearPath();
else if ( isdigit( buf[0] ) )
{
int x, y;
sscanf( buf, "%d %d", &x, &y ); // sleazy, I know
dungeon.SetPos( x, y );
}
}
else
dungeon.ClearPath();
}
return 0;
}