如何在C ++中使用A *追溯路径?

时间:2017-02-27 01:27:45

标签: c++ sfml path-finding a-star

我一直试图实施A *几周,所以敌人可以在我的游戏中追逐一个玩家,我无法让它发挥作用。整个周末我一直在努力,我甚至最终抓住了大部分内容并重新编写它。我可以绘制从起始位置到目标的路径,但我无法追溯它,就像实际写下路径一样。我正在使用Vector2f(有序浮动对)和SFML中的Sprite,但所有代码都非常简单,所以你不需要理解它。

编辑:问题出在Node.cameFrom上。出于某种原因,除了墙壁之外,它不会有任何问题。

这是Node.h

#ifndef NODE_H
#define NODE_H
#include <SFML/Graphics.hpp>

using namespace sf;

class Node {
    public:
        Vector2f pos;
        // Distance traveled already to reach node
        int level;
        // Level + estimated dist to goal
        int priority;

        Node *cameFrom;

        Node(Vector2f npos, int lv, Vector2f dest, Node *cf=nullptr);

        bool operator == (const Node &nhs) const {
            return nhs.priority == priority;
        }

};

#endif // NODE_H

Node.cpp

#include "Node.h"
#include <SFML/Graphics.hpp>
#include <math.h>
#include <iostream>

using namespace std;
using namespace sf;

int estimatedDist(Vector2f pos, Vector2f dest) {
    return abs(dest.x - pos.x) + abs(dest.y - pos.y);
}

Node::Node(Vector2f npos, int lv, Vector2f dest, Node *cf) {
    cameFrom = cf;
    level = lv;
    pos = npos;
    priority = level + estimatedDist(pos, dest);
}

Enemy.cpp pathfind functions

bool occupies(Vector2f pos, vector<Wall> walls) {
    for (unsigned w = 0; w < walls.size(); w++) {
        if (walls.at(w).collisionBox.getGlobalBounds().contains(pos.x * 32, pos.y * 32)) {
            return true;
        }
    }
    return false;
}

bool nFind(Node n, vector<Node> nodes) {
    for (unsigned i = 0; i < nodes.size(); i++) {
        if (nodes.at(i).pos == n.pos) {
            return true;
        }
    }
    return false;
}

void Enemy::pathFind(Vector2f dest, vector<Wall> walls) {
    char fullMap[32][22];
    vector<Node> openSet;
    vector<Node> closedSet;
    int xStart, yStart;
    for (unsigned y = 0; y < 22; y++) {
        for (unsigned x = 0; x < 32; x++) {
            if (sprite.getGlobalBounds().top >= y * 32 && sprite.getGlobalBounds().top <= (y + 1) * 32) {
                if (sprite.getGlobalBounds().left >= x * 32 && sprite.getGlobalBounds().left <= (x + 1) * 32) {
                    xStart = x;
                    yStart = y;
                }
            } if (occupies(Vector2f(x, y), walls)) {
                fullMap[x][y] = '2';
            } else {
                fullMap[x][y] = ' ';
            }
        }
    }
    fullMap[int(dest.x)][int(dest.y)] = 'D';
    Node *current = new Node(Vector2f(xStart, yStart), 0, dest);
    fullMap[int(current->pos.x)][int(current->pos.y)] = '2';
    openSet.push_back(*current);

    while (openSet.size() > 0) {
        sort(openSet.begin(), openSet.end(), sortByPriority());
        *current = openSet.front();

        if (current->pos == dest) {
            cout << "We gots it ";
            for (unsigned y = 0; y < 22; y++) {
                for (unsigned x = 0; x < 32; x++) {
                    if (occupies(Vector2f(x, y), walls)) {
                        fullMap[x][y] = '2';
                    } else {
                        fullMap[x][y] = ' ';
                    }
                }
            }
            while (current->cameFrom) {
                fullMap[int(current->pos.x)][int(current->pos.y)] = 'P';
                current = current->cameFrom;
                for (unsigned y = 0; y < 22; y++) {
                    for (unsigned x = 0; x < 32; x++) {
                        cout << fullMap[x][y];
                    }
                    cout << endl;
                }
                cout << endl;
            } for (unsigned y = 0; y < 22; y++) {
                for (unsigned x = 0; x < 32; x++) {
                    cout << fullMap[x][y];
                }
                cout << endl;
            }
            cout << endl;
            return;
        }

        openSet.erase(remove(openSet.begin(), openSet.end(), *current), openSet.end());
        closedSet.push_back(*current);
        fullMap[int(current->pos.x)][int(current->pos.y)] = '2';

        vector<Node> neighbors;

        neighbors.push_back(Node(Vector2f(current->pos.x - 1, current->pos.y - 1), current->level + 1, dest));
        neighbors.push_back(Node(Vector2f(current->pos.x, current->pos.y - 1), current->level + 1, dest));
        neighbors.push_back(Node(Vector2f(current->pos.x + 1, current->pos.y - 1), current->level + 1, dest));
        neighbors.push_back(Node(Vector2f(current->pos.x + 1, current->pos.y), current->level + 1, dest));
        neighbors.push_back(Node(Vector2f(current->pos.x + 1, current->pos.y + 1), current->level + 1, dest));
        neighbors.push_back(Node(Vector2f(current->pos.x, current->pos.y + 1), current->level + 1, dest));
        neighbors.push_back(Node(Vector2f(current->pos.x - 1, current->pos.y + 1), current->level + 1, dest));
        neighbors.push_back(Node(Vector2f(current->pos.x - 1, current->pos.y), current->level + 1, dest));

        for (unsigned i = 0; i < neighbors.size(); i++) {
            if (nFind(neighbors.at(i), closedSet) ||
                neighbors.at(i).pos.x > 22 ||
                neighbors.at(i).pos.y > 32 ||
                neighbors.at(i).pos.x < 0 ||
                neighbors.at(i).pos.y < 0 ||
                occupies(neighbors.at(i).pos, walls)) {

                continue;
            } if (!nFind(neighbors.at(i), openSet)) {
                openSet.push_back(neighbors.at(i));
            }
            neighbors.at(i).cameFrom = current;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

MCVE将有助于尝试我们(见zett42评论)。

因此,通过快速浏览,我可以给你一些指导,在调试过程中可以查看,但没有明确的答案。

这些行看起来非常可疑:

Node *current = new Node(Vector2f(xStart, yStart), 0, dest);
// ^ no delete in source, will leak memory

*current = openSet.front();
// will overwrite the heap memory with copy constructor
// but the pointer will remain the same
// so all of your nodes will always have "cameFrom"
// pointing to this same memory.

总的来说,这段代码看起来有点复杂。你有固定方块32x22瓷砖的游戏吗?为什么“墙”会传染呢?

我只维护单个全局平铺图作为关卡(但是A *搜索不应该损坏它,而是创建它自己的搜索副本,或者更确切地说是具有到达成本的新地图,这可能是很多简化代码。

xStart, yStart可以直接计算,不需要在每个循环中迭代它:

xStart = int(sprite.getGlobalBounds().left)>>5;  // left/32
yStart = int(sprite.getGlobalBounds().top)>>5;   // top/32

bool operator == (const Node &nhs) const看起来不健康,但它甚至没有在任何地方使用过。

要查看邻居是否在墙上,您不需要使用O(N)occupies,只需测试== '2'的地图? (我的意思是,如果代码是按照这种方式设计的,那么如果您在代码中立即更改代码,我就不会验证它是否会按预期工作)。

总体而言,我不喜欢这些代码,如果您专注于要处理的数据和方式,并且停止在几个列表中来回移动对象,则可以将其简化为更短的版本。对于A * IIRC,您应该需要使用insert_at的单个排序队列来保持它与地图字段的排序,以标记哪些方格已经处理过。

那些Vector2f位置是否重要,例如:

...
P#D
...

如果玩家“P”位于方形的下半部分(“#”是墙,“D”是目的地),A *应该找到底部路径,还是只需要“平铺”精度,上方路径就是还好吗?

我不清楚你的问题,你是否使用子瓦片精度,如果没有,那么你可以放弃大部分Vector2f的东西,只在瓷砖坐标中工作。

使用子平铺精度你可能仍然会掉落大部分,但如果实际上平铺的大小为“32”,而播放器仅为“3”宽,那么他可以将平铺用作某种“区域” “并通过不同的线条移动它,避免在上面的例子中去到中间瓦片的完整中心,节省距离......然后你需要以某种方式计算那些子瓦片位置以获得至少大致准确的”最短“路径。

当我在一个游戏上工作时,我们已经链接了节点列表(经典数学图)而不是瓦片,每个节点都有它的“区域半径”,并且在找到最短的节点到节点路径之后,另一个重复算法几乎没有循环从节点位置移动到半径范围内的某个阴影节点位置,但更接近其他两个阴影节点。在达到最大迭代次数后,或阴影位置没有太大变化(通常最多需要3-5次迭代),它停止“平滑”路径并返回它。这样士兵几乎是直线穿越沙漠,而实际上航点节点就像稀疏的网格,面积半径为20米,所以士兵实际上只有2-3个节点,远离节点中心开始/结束几乎走节点图中的zig-zag。

答案 1 :(得分:0)

对于每个图块,您需要它的成本(到达那里的成本加上启发式),以及从中到达它的相邻图块的标识。

该算法有一个&#34;气球&#34;在起点周围的点数,首先分析最佳点。因此,如果路径很简单,则气球非常细长。如果它是复杂的,气球是圆的,许多路径被废弃,因为被墙壁和已经访问过的瓷砖包围。