C#XNA A *寻路敌人被困在墙壁对面

时间:2017-03-03 18:50:56

标签: c# algorithm xna nodes path-finding

我的游戏中有一个由节点组成的2D网格。我有跟随玩家使用A *寻路算法的敌人(使用H的对角距离启发式作为对角线移动)。

寻路工作几乎一直都在工作,但是当一个玩家和一个敌人正好在墙的两侧(对角线,垂直或水平方向)时,敌人会卡住并停止移动。

从下面的屏幕截图中,您可以看到此场景中找到的路径,由于某种原因,路径相反方向的节点也被添加到路径中:

下面是我的F,G和H计算的代码(在我的节点类中):

// Calculates distance cost from current node to an adjacent node.
public void CalculateG(Node nodeTarget)
{
    // If the node is diagonal to the current node, its cost of movement is higher.
    if (TileX != nodeTarget.TileX && TileY != nodeTarget.TileY)
    {
        intG = 14;
    }
    else intG = 10;
}

// Calculates H cost using the diagonal shortcut method.
public void CalculateH(Node nodeTarget)
{
    int intXDiff = Math.Abs(TileX - nodeTarget.TileX);
    int intYDiff = Math.Abs(TileY - nodeTarget.TileY);

    if (intXDiff > intYDiff)
        intH = 14 * intYDiff + 10 * (intXDiff - intYDiff);
    else intH = 14 * intXDiff + 10 * (intYDiff - intXDiff);
}

public void CalculateF()
{
    intF = intG + intH; // F = G + H
}

我的寻路类的代码如下所示:

public class Path
{
    private List<Node> PathOfNodes; // Stores the path of nodes to follow to the destination.

    public List<Node> NodePath
    {
        get { return PathOfNodes; }
    }

    // Constructor takes the starting node, destination and the grid to generate the path.
    public Path(Node Start, Node Destination, GridLayer grid)
    {
        List<Node> openNodes = new List<Node>(); // Creates a list of possible nodes for the path.
        List<Node> closedNodes = new List<Node>(); // Creates a list of nodes confirmed for the path.

        openNodes.Add(Start); //  Step 1: Adds the current node to the possibilities list.

        // Loops while the destination is not on the closed list and while the open nodes list is not empty.
        while (!closedNodes.Contains(Destination) && openNodes.Any())
        {
            // Sorts the open list according to f scores.
            openNodes.Sort((node, otherNode) => node.F.CompareTo(otherNode.F));
            Node nodeCurrent = openNodes[0]; // The node with the lowest F score is set as the current node.

            openNodes.Remove(nodeCurrent);
            closedNodes.Add(nodeCurrent); // The current node is moved to the closed list.

            // Creates a list containing all the nodes adjacent to the current node.
            List<Node> adjacentNodes = AddAdjacentNodes(grid, nodeCurrent);

            CheckAdjacentNodes(adjacentNodes, ref closedNodes, ref openNodes, nodeCurrent, Destination);
        }
        EstablishPath(closedNodes);
    }

    // Adds all the adjacent nodes from above the current node turning clockwise.
    public List<Node> AddAdjacentNodes(GridLayer grid, Node nodeCurrent)
    {
        int intCol = nodeCurrent.TileX / nodeCurrent.TileWidth;
        int intRow = nodeCurrent.TileY / nodeCurrent.TileHeight; // Gets the current node's indices.

        List<Node> adjacentNodes = new List<Node>(); // Stores the nodes adjacent to the current node.
        // The if statements check whether the node is within the grid before adding the node.
        if (intRow - 1 >= 0)
            adjacentNodes.Add(grid.Nodes[intCol, intRow - 1]); // Above
        if ((intCol + 1 < 21 && intRow - 1 >= 0) && (grid.Nodes[intCol + 1, intRow].Traversable) && (grid.Nodes[intCol, intRow - 1].Traversable))
            adjacentNodes.Add(grid.Nodes[intCol + 1, intRow - 1]); // Diagonally Right Up
        if (intCol + 1 < 21)
            adjacentNodes.Add(grid.Nodes[intCol + 1, intRow]); // Right
        if (intCol + 1 < 21 && intRow + 1 < 12 && (grid.Nodes[intCol + 1, intRow].Traversable) && (grid.Nodes[intCol, intRow + 1].Traversable))
            adjacentNodes.Add(grid.Nodes[intCol + 1, intRow + 1]); // Diagonally Right Down
        if (intRow + 1 < 12)
            adjacentNodes.Add(grid.Nodes[intCol, intRow + 1]); // Below
        if (intCol - 1 >= 0 && intRow + 1 < 12 && (grid.Nodes[intCol - 1, intRow].Traversable) && (grid.Nodes[intCol, intRow + 1].Traversable))
            adjacentNodes.Add(grid.Nodes[intCol - 1, intRow + 1]); // Diagonally Left Down
        if (intCol - 1 >= 0)
            adjacentNodes.Add(grid.Nodes[intCol - 1, intRow]); // Left
        if (intCol - 1 >= 0 && intRow - 1 >= 0 && (grid.Nodes[intCol - 1, intRow].Traversable) && (grid.Nodes[intCol, intRow - 1].Traversable))
            adjacentNodes.Add(grid.Nodes[intCol - 1, intRow - 1]); // Diagonally Left Up

        return adjacentNodes;
    }

    // Checks the adjacent node list for nodes to be added to the open list/closed list.
    private void CheckAdjacentNodes(List<Node> adjacentNodes, ref List<Node> closedNodes, ref List<Node> openNodes, Node nodeCurrent, Node destinationNode)
    {
        foreach (Node node in adjacentNodes)
        { // Checks each node to see if it is traversable and not already on the closed list.
            if (node.Traversable && !closedNodes.Contains(node))
            {
                // If the node is not on the open list, add it, set its parent as the current node and calculate its F, G, and H values.
                if (!openNodes.Contains(node))
                {
                    openNodes.Add(node);
                    node.Parent = nodeCurrent;
                    node.CalculateG(nodeCurrent);
                    node.CalculateH(destinationNode);
                    node.CalculateF();
                }
                else // If the node was already on the open list...
                {
                    // If its G cost of the node is lower than its parent + its own...
                    if (node.G < node.G + node.Parent.G)
                    {
                        // Make the node's parent the current node and recalculate its values.
                        node.Parent = nodeCurrent;
                        node.CalculateG(nodeCurrent.Parent);
                        node.CalculateF();
                    }
                }
            }
        }
    }

    private void EstablishPath(List<Node> closedNodes)
    {
        PathOfNodes = new List<Node>(); // Stores the path for the entity to follow to its target.

        for (int intNodeIndex = closedNodes.Count - 1; (intNodeIndex > 1); intNodeIndex--)
        {
            // Starting from the top of the closed list, add each node's parent to the path
            // until the starting node is reached (index is 0).
            PathOfNodes.Add(closedNodes[intNodeIndex].Parent);
        }
        PathOfNodes.Remove(null); // Remove the final null node (as the final node has no parent).
    }
}

我认为节点的G分数与其自身及其父级的G分数之间存在比较问题。我不确定在比较后我是否使用正确的相邻节点重新计算G分数。如果有人能说得更清楚,那将会非常有帮助。我跟着的文章是: https://www.gamedev.net/resources/_/technical/artificial-intelligence/a-pathfinding-for-beginners-r2003

但是,我认为我没有正确地将这一步实施到我的代码中:

  

如果它已经在开放列表中,请使用G cost作为度量来检查该方块的路径是否更好。较低的G成本意味着这是一条更好的路径。如果是这样,请将方块的父级更改为当前方块,然后重新计算方块的G和F分数。

请告知我此问题所需的任何其他信息。 提前谢谢。

编辑:我没有为与墙碰撞的敌人设置任何碰撞检测。该路径之后是敌人移动到节点路径列表中的最后一个节点。

编辑:我的G计算错误,分数未累计。

在正确累积G分数后,它现在在所有4个方向上找到路径(启发式设置为0)。我猜这意味着所有关闭列表节点都被添加到我的最终路径中,这意味着我的建立路径方法存在问题。

红色数字显示节点的f分数,蓝色数字显示节点父节点的f分数(因此您可以判断哪个节点是其父节点)。 After correctly accumulating the G scores, it's now finding paths in all 4 directions (with the heuristic set to 0).

编辑:我已经解决了这个问题。我很快就会提交一个答案,但基本上,我的建立路径方法没有做到它应该做的事情。它应该从封闭列表的末尾开始,而不是从封闭列表中添加所有内容,而是从最后的父级链开始,直到添加起始节点。

2 个答案:

答案 0 :(得分:0)

我确实在你的代码中看到了一些缺陷。这可能不是问题,但我们希望它是。

当您检查节点是否优于公开列表中的其他节点时,您正在做一些奇怪的事情。 if语句永远不会触发,因为某些东西永远不会比自身小+正整数。你甚至还没有设置节点的G-cost。因此,您也无法检查它。这段代码可能会导致错误(除非G有标准值)。

但是我认为这段代码甚至无法达成。因为我怀疑这段代码总是触发:

    if (!openNodes.Contains(node))

你知道为什么吗? 我可以看到你想要实现的想法,但是在openset中没有另一个精确的节点副本。首先,因为openset中的节点设置了G,H和F成本,并且您要检查的节点没有(因此它们不相同)。因此,如果其中包含相同的数据,我认为它仍然不会触发,因为两个节点在您的计算机中都有另一个位置(不是100%确定这部分)。这也用于检查节点是否处于闭集状态。

您应该做的是检查开放列表中是否有与您要检查的节点位置相同的节点。如果节点已经在列表中,那么您应该计算我们正在处理的节点的G-cost。并检查G-cost是否小于列表中当前的G-cost,并相应地更改父级等。

对于其他人来说,你的代码看起来很好,尽管在寻路者中经常很难看到错误。希望这有帮助,如果您有任何疑问,请不要犹豫。

答案 1 :(得分:0)

首先,感谢J4stM4rt确定我正在进行无用的比较(integerA&lt; integerA +正整数)和Eric Lippert建议我应该将启发式设置为0来识别问题。

我的第一个问题是我没有积累路径的g分数。在我的G分数计算方法中,我需要intG = 10(或14)+ targetNode.G。

第二个问题是J4stM4rt提到的问题,我正在检查节点的g得分是否大于其自身+另一个节点的g得分,这总是正确的。

最后一个问题是在建立路径方法中,我将封闭列表中每个节点的父节点添加到路径而不是从末尾开始,并将每个后续父节点添加到路径,直到我到达开始。您可以在下面看到我的代码更正:

G-Score计算:

        // Calculates distance cost from start node to an adjacent node.
    public void CalculateG(Node nodeTarget)
    { // The g-score is cumulative so the target node's score is also added.
        // If the node is diagonal to the current node, its cost of movement is higher.
        if (TileX != nodeTarget.TileX && TileY != nodeTarget.TileY)
        {
            intG = nodeTarget.G + 14;
        }
        else intG = nodeTarget.G + 10;
    }

建立路径方法:

 private void EstablishPath(List<Node> closedNodes, Node nodeStart, Node nodeDestination)
    {
        PathOfNodes = new List<Node>(); // Stores the path for the entity to follow to its target.

        // Start at the end of the path.
        Node nodeToAdd = closedNodes[closedNodes.Count - 1];
        while (!PathOfNodes.Contains(nodeStart))
        { // Add each node's parent until the start is reached.
            PathOfNodes.Add(nodeToAdd);
            nodeToAdd = nodeToAdd.Parent;
        }

        PathOfNodes.Remove(null); // Remove the final null node (as the final node had no parent).
    }

G分数比较(这可能实际上仍然是错误的,我尝试将其更改为(node.G(从当前节点的父节点移动到此节点的成本)&lt; nodeCurrent.G(从当前节点父节点移动的成本)到当前节点)+计算从当前节点移动到此节点的g)然而,这导致我的敌人再次陷入困境,所以我将其更改为以下内容并且看似没问题:

if (node.G < nodeCurrent.G)
                    {
                        // Make the node's parent the current node's parent and recalculate its values.
                        node.Parent = nodeCurrent;
                        node.CalculateG(nodeCurrent);
                        node.CalculateF();
                    }