【步兵 c++】教科书般的A*寻路算法

【步兵 c++】教科书般的A*寻路算法 by EOS.

好久之前写的了,拿出来稍微整理了一下,看上去一份教科书上的一样标准=。=
没人没有任何技巧性的东西,感觉好悲哀。唯一的亮度就是,功能写好后,
接口封装的还是比较漂亮的~

好了,500来行的代码,看起来,应该不会很困难,废话不多说,
还是老样子,原理自行百度,直接上代码=、=

哦,不对,先上张图。给你点动力~
【步兵 c++】教科书般的A*寻路算法_第1张图片
这是不走斜线版的=。=,蓄力已满,开始看代码吧

头文件

#pragma once
#include 

typedef unsigned char TileType;

typedef struct AStarPoint
{
    int i,j;
}AStarPoint;

typedef struct AStarNode
{//节点结构体
    int f,g,h;
    int row;  //该节点所在行
    int col;  //该节点所在列
    AStarPoint direct;//parent节点要移动的方向就能到达本节点
    struct AStarNode * parent;
}AStarNode;

typedef struct AStarList
{//OPEN CLOSED 表结构体
    AStarNode * npoint;
    struct AStarList * next;
}AStarList;

#define MAXROW  100
#define MAXCOL  100

class AStar
{
public:
    AStar();
    ~AStar();

    static AStar* getInstance();
    static void destroy();

    static std::list run(TileType inMap[MAXROW][MAXCOL], int inRow, int inCol, int StartX, int StartY, int EndX, int EndY);
    void PrintMap();//测试用

    AStarNode* getNodeFromOpen();
    void pushOpen(AStarNode * suc);
    void pushClosed(AStarNode * suc);
    int getH(int row, int col);
    int getRowPosition(int y);
    int getColPosition(int x);
    AStarNode* checkOpen(int row, int col);
    bool isInClose(int row, int col);
    void creatNextLapNode(AStarNode *bestNode, int row, int col,int G_OFFSET);

    bool isCanMove(int col, int row);
    void seachSeccessionNode(AStarNode *bestNode);
    std::list findPath(TileType inMap[MAXROW][MAXCOL], int inRow, int inCol, int StartX, int StartY, int EndX, int EndY);
private:
    int map[MAXROW][MAXCOL];
    int rows;
    int cols;
    int destinationRow;
    int destinationCol;
};

源文件

#include "AStar.h"

AStar::AStar()
{
}

AStar::~AStar()
{
}

static AStar* m_only_AStar = nullptr;
AStar* AStar::getInstance()
{
    if (!m_only_AStar)
    {
        m_only_AStar = new AStar;
    }

    return m_only_AStar;
}

void AStar::destroy()
{
    if (m_only_AStar)
    {
        delete m_only_AStar;
        m_only_AStar = nullptr;
    }
}


#define NULL    0
#define G_OFFSET1 10    //每个图块G值的增加值
#define G_OFFSET2 14
#define TileSize  1     //图块大小
#define Tile_Lock 0     
#define Tile_Open 1     
#define Tile_Start 2    
#define Tile_End  3     
#define Tile_Path 4     

static AStarList* Open = NULL;
static AStarList* Closed = NULL;

void ClearList(AStarList* list)
{
    AStarList* temp = list->next;
    AStarNode *p_node;
    if (temp == NULL)
        return;
    while (temp != NULL)
    {
        AStarList* head = temp;
        temp = temp->next;
        p_node = head->npoint;
        delete p_node;
        delete head;
        list->next = temp;
    }
}

void ClearOpenData()
{//清空Open表
    ClearList(Open);
}

void ClearClosedData()
{//清空Close表
    ClearList(Closed);
}

std::list AStar::run(TileType inMap[MAXROW][MAXCOL], int inRow, int inCol, int StartX, int StartY, int EndX, int EndY)
{//主接口
    auto astar = AStar::getInstance();

    Open = new AStarList;
    Open->next = nullptr;
    Closed = new AStarList;
    Closed->next = nullptr;

    auto ret = astar->findPath(inMap, inRow, inCol, StartX, StartY, EndX, EndY);

    ClearOpenData();
    ClearClosedData();
    delete(Open);
    delete(Closed);

    return ret;
}


void AStar::PrintMap()
{//输出地图
    int i,j;
    for (i=0;ifor (j=0;jif (map[i][j]==Tile_Open){
                printf("□");
            }
            else if (map[i][j]==Tile_Lock){
                printf("■");
            }
            else if (map[i][j]==Tile_Start){
                printf("☆");
            }
            else if (map[i][j]==Tile_End){
                printf("★");
            }
            else if (map[i][j]==Tile_Path){
                printf("●");
            }
        }
        printf("\n");
    }
}

AStarNode* AStar::getNodeFromOpen()
{//选取Open表上f值最小的节点,返回该节点地址
    AStarList* temp = Open->next,
             * min = Open->next,
             * minp = Open;
    AStarNode * minx;
    if(temp == NULL)
        return NULL;

    while(temp->next != NULL)
    {
        if( (temp->next->npoint->f) < (min->npoint->f))
        {
            min = temp->next;//min表示f最小的链表节
            minp = temp;//指向min的指针,为的是在后面的释放中用到
        }
        temp = temp->next;//temp指向下一个链表节
    }
    minx = min->npoint;
    temp = minp->next;
    minp->next = minp->next->next;
    delete temp;
    return minx;
}

void AStar::pushOpen(AStarNode * suc )
{//把节点放入Open表中
    AStarList * temp;
    temp = new AStarList;
    temp->npoint = suc;

    temp->next = Open->next;
    Open->next = temp;
}

void AStar::pushClosed(AStarNode * suc )
{//把节点放入Close表中
    AStarList * temp;
    temp = new AStarList;
    temp->npoint = suc;
    temp->next = Closed->next;
    Closed->next = temp;
}

int AStar::getH(int row, int col)
{//得到该图块的H值
    int x;
    x=abs(destinationRow - row) + abs(destinationCol - col)+1;
    return x*10;
}

int AStar::getRowPosition(int y)
{//得到该位置所在地图行
    return (y/TileSize);
}

int AStar::getColPosition(int x)
{//得到该位置所在地图列
    return (x/TileSize);
}

bool AStar::isCanMove(int col, int row)
{//检测该图块是否可通行
    if(col < 0 || col >= cols)
        return false;
    if(row < 0 || row >= rows)
        return false;
    if(map[row][col]==Tile_Lock)
        return false;

    return true;
}

AStarNode* AStar::checkOpen(int row, int col)
{//校验Open表
    AStarList* temp = Open->next;
    if ( temp == NULL )
        return NULL;
    while (temp != NULL) 
    {
        if ( (temp->npoint->row==row) && (temp->npoint->col == col)  )
        {
            return temp->npoint;
        }  
        else
        {
            temp = temp->next;
        }
    }
    return NULL;
}

bool AStar::isInClose(int row, int col)
{//是否存在于Close表
    AStarList* temp = Closed->next;
    if ( temp == NULL )
        return false;
    while (temp != NULL) 
    {
        if ( (temp->npoint->row==row) && (temp->npoint->col == col)  )
        {
            return true;
        }  
        else
        {
            temp = temp->next;
        }
    }
    return false;
}

void AStar::creatNextLapNode(AStarNode *bestNode, int row, int col,int G_OFFSET)
{//创建下一圈的node
    int g = bestNode->g + G_OFFSET;
    if(!isInClose(row, col))
    {
        AStarNode *oldNode = NULL;
        if((oldNode = checkOpen(row, col)) != NULL)
        {
            if(oldNode->g > g)
            {
                oldNode->direct.i = bestNode->col;
                oldNode->direct.j = bestNode->row;
                oldNode->parent = bestNode;
                oldNode->g = g;
                oldNode->f = g + oldNode->h;
            }
        }
        else
        {
            AStarNode *node = new AStarNode;
            node->parent = bestNode;
            node->g = g;
            node->h = getH(row,col);
            node->f = node->g + node->h;
            node->row = row;
            node->col = col;
            node->direct.i = bestNode->col;
            node->direct.j = bestNode->row;

            pushOpen( node );
        }
    }
}

void AStar::seachSeccessionNode(AStarNode *bestNode)
{//根据传入的节点生成子节点
    int row, col;
    //上部节点
    if(isCanMove(row = bestNode->row - 1, col = bestNode->col))
    {
        creatNextLapNode(bestNode, row, col,G_OFFSET1);
    }
    //下部节点
    if(isCanMove(row = bestNode->row + 1, col = bestNode->col))
    {
        creatNextLapNode(bestNode, row, col,G_OFFSET1);
    }
    //左部节点
    if(isCanMove(row = bestNode->row, col = bestNode->col-1))
    {
        creatNextLapNode(bestNode, row, col,G_OFFSET1);
    }
    //右部节点
    if(isCanMove(row = bestNode->row, col = bestNode->col + 1))
    {
        creatNextLapNode(bestNode, row, col,G_OFFSET1);
    }
    ////左上角
    //if(isCanMove(row = bestNode->row-1, col = bestNode->col - 1))
    //{
    //  creatNextLapNode(bestNode, row, col,G_OFFSET2);
    //}
    ////右上角
    //if(isCanMove(row = bestNode->row+1, col = bestNode->col - 1))
    //{
    //  creatNextLapNode(bestNode, row, col,G_OFFSET2);
    //}
    ////左下角
    //if(isCanMove(row = bestNode->row-1, col = bestNode->col + 1))
    //{
    //  creatNextLapNode(bestNode, row, col,G_OFFSET2);
    //}
    ////右下角
    //if(isCanMove(row = bestNode->row+1, col = bestNode->col + 1))
    //{
    //  creatNextLapNode(bestNode, row, col,G_OFFSET2);
    //}

    pushClosed(bestNode);
}

std::list AStar::findPath(TileType inMap[MAXROW][MAXCOL], int inRow, int inCol, int StartX, int StartY, int EndX, int EndY)
{//主函数
    rows = inRow;
    cols = inCol;
    for (int i = 0; i < rows; i++)
    {
        for (int x = 0; x < cols; x++)
        {
            map[i][x] = inMap[i][x];
            if (i == StartX && x == StartY)
            {
                map[i][x] = Tile_Start;
            }
            if (i == EndX && x == EndY)
            {
                map[i][x] = Tile_End;
            }
        }
    }
    PrintMap();

    AStarNode *startNode = new AStarNode;
    AStarNode *bestNode  = NULL;
    destinationRow = getRowPosition(EndY);
    destinationCol = getColPosition(EndX);

    startNode->parent= NULL;
    startNode->row = getRowPosition(StartY);
    startNode->col = getColPosition(StartX);
    startNode->g = 0;
    startNode->h = getH( startNode->row, startNode->col );
    startNode->f = startNode->g + startNode->h;
    startNode->direct.i = startNode->col;
    startNode->direct.j = startNode->row;
    pushOpen(startNode);

    std::list ret;
    AStarPoint point;
    while(true)
    {
        bestNode = getNodeFromOpen(); //从OPEN表中取出f值最小的节点
        if(bestNode == NULL)//未找到路径
        {
            break;
        }
        else if(bestNode->row == destinationRow && bestNode->col == destinationCol)
        {
            map[bestNode->col][bestNode->row]=Tile_Path;
            point.i = bestNode->col;
            point.j = bestNode->row;
            ret.push_front(point);//实际运行去掉,不然会到达最后一个格子再折返
            while(!(bestNode->direct.i==StartX&&bestNode->direct.j==StartY))
            {
                map[bestNode->direct.i][bestNode->direct.j]=Tile_Path;
                bestNode = bestNode->parent;

                point.i = bestNode->col;
                point.j = bestNode->row;
                ret.push_front(point);
            }
            break;
        }

        seachSeccessionNode(bestNode);
    }
    PrintMap();

    return ret;
}

关于代码

因为是频繁的在头部插入,所以链表是首选。
当然也可以用vector再反转,不过浪费了时间。
队列的话,不能遍历,处理一些问题比较尴尬,比如判断是不是在一条直线上。

TileType 主要是为了节省空间,因为本来状态就不多255绝对够用了。
其实可以用char* 类型的,但是操作起来比较麻烦,主要是懒得改了=。=

TileSize 是用来标记是否一个碰撞块包含多个图块,比如一个包含9个,就用3。
那么9个包含1个就需要在外部另行处理了,比如取图的时候除3。

其他的基本就是教科书了= =!

代码可以直接用,但是还是希望能看懂,最好自己重新实现一边,
比如data 用char*类型。不浪费一丝空间=。=

结合游戏使用

const static int GridSize = 60;

class GameMap : public cocos2d::Node
{
public:
    GameMap();
    ~GameMap();
    //根据名字去寻找图块 比如 输入map 去找 map_0_0, map_0_1, map_0_2(.png)
    static GameMap* create(const std::string& name, int r, int l);
    bool init(const std::string& name, int r, int l);
    //根据两个坐标点寻路
    std::list getPath(cocos2d::Point ps, cocos2d::Point pe);
    //根据返回的路线,去获取坐标,到达一个前往下一个
    cocos2d::Point getGridPos(AStarPoint p);

private:
    int row;
    int col;
    int width;
    int height;
    TileType data[MAXROW][MAXCOL];
};

一些补充

在实际使用中,可能会遇到明明两个点是可以直线行走的,但是用A*会走折线,感觉好白痴的样子,角色是被我们赋予生命的,怎么可以这么傻,所以这时候就需要矢量碰撞来检测。
简单方法是获取到出发点到目的点的矢量,然后对这个矢量做切成N段(或者从起始点每次移动段长)
判断上面每个点的是否都没有与LockTile碰撞,如果是就说明可以走直线~


总结

还是要多动手才是,自己的东西用着才放心,可能没有别人的高效,但是出bug了,
最起码能很快解决,至于效率上的问题,可以慢慢随着经验增长再弥补上来。
看别人的代码,可以增长各种姿势,前提是要能变成自己的东西=。=

不然全是徒劳~(当然以解决问题为目标,而不是强化自己为目标除外)

See Again~
之前
真爱无价,欢迎打赏~
赞赏码

你可能感兴趣的:(C++)