寻路问题。没有走最直接的道路

时间:2015-06-15 13:23:11

标签: c# path find path-finding shortest

我正在使用c#pathfinding实现。我遇到了一些问题。它工作正常,但它不会给我我想要的路径类型。最好的例子是当我在无障碍环境中寻找A到B之间的路径而不是直接到目标的锯齿形时(我希望如此),它将需要更少的转弯并且更多地形成L形或钝角。我的算法是否有利于减少转弯?我用启发式方法搞砸了几个小时,并尝试了很多东西。使用平局破坏者,尝试曼哈顿和octile启发式。改变启发式似乎并没有影响行为。现在我认为它在系统的其他地方?

这张照片是我得到的行为,下面的照片是我想要的。 http://i.imgur.com/LjWy34E.png http://i.imgur.com/tWnr30X.png

这是我的长寻路代码。原谅杂乱: 使用UnityEngine; 使用System.Collections;

公共类路径查找器 {

//Declare readonlyants
private int onClosedList = 10;
private readonly int notStarted = 0;
private readonly int found = 1;
private readonly int nonexistent = 2;
private readonly int walkable = 0; // walkability array readonlyants
private readonly int unwalkable = 1;
private readonly int diagonal_cost = 14;
private readonly int horizontal_cost = 10;

//Create needed arrays
private int[] openList;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) * (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 2]; //1 dimensional array holding ID# of open list items
private int[,] whichList;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1, (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1]; //2 dimensional array used to record
//Whether a cell is on the open list or on the closed list.
private int[] openX;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) * (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 2]; //1d array stores the x location of an item on the open list
private int[] openY;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) * (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 2]; //1d array stores the y location of an item on the open list
private int[,] parentX;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1, (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1]; //2d array to store parent of each cell (x)
private int[,] parentY;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1, (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1]; //2d array to store parent of each cell (y)
private float[] Fcost;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) * (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 2]; //1d array to store F cost of a cell on the open list
private float[,] Gcost;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1, (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 1]; //2d array to store G cost for each cell.
private float[] Hcost;// = new int[(int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) * (int) ((Config.MAP_SIZE * TileRow) /(float) Config.CLIP_MAP_SQUARE_SIZE) + 2]; //1d array to store H cost of a cell on the open list
private int pathLength; //stores length of the found path for critter
private int pathLocation; //stores current position along the chosen path for critter
private Vector3[] pathBank;



//Path reading variables
private int pathStatus;
private int xPath;
private int yPath;

public PathFinder (int MapSizeX, int MapSizeY, float WaypointDistance)
{

    int totalX = Mathf.RoundToInt (MapSizeX / WaypointDistance);
    int totalY = Mathf.RoundToInt (MapSizeY / WaypointDistance);
    //Correct is totalX * totalY, but we don't want it to try to create paths 5000 units long and lag the server, 
    //so we ruduce the array size allowing us to set a MAXIMUM path length befoe it gives up.
    int total = totalX * totalY;//2000;//totalX * totalY; 

    openList = new int[total]; 
    whichList = new int[totalX + 1, totalY + 1];
    openX = new int[total];
    openY = new int[total];
    parentX = new int[totalX, totalY];
    parentY = new int[totalX, totalY];
    Fcost = new float[total];
    Gcost = new float[totalX, totalY];
    Hcost = new float[total];

}

//==========================================================
//READ PATH DATA: These functions read the path data and convert
//it to screen pixel coordinates.

public Vector3[] GetPath (float WaypointDistance)
{
    Vector3[] pathBank2 = new Vector3 [pathBank.Length];
    for (int i = 0; i < pathBank2.Length; i++) {
        pathBank2 [i] = new Vector3 (Mathf.RoundToInt (pathBank [i].x * WaypointDistance), pathBank [i].y, Mathf.RoundToInt (pathBank [i].z * WaypointDistance));
    }
    return pathBank2;
}

//-----------------------------------------------------------------------------
// Name: FindPath
// Desc: Finds a path using A*
//-----------------------------------------------------------------------------
public int FindPath (ref Vector3[,,] ClipMap, Vector3 startPos, Vector3 endPos, int heightLevel, float entitySize, int MapSizeX, int MapSizeY, float WaypointDistance)
{

    int entityClearance = Mathf.CeilToInt (entitySize / 2 / WaypointDistance);
    int onOpenList = 0;
    int parentXval = 0;
    int parentYval = 0;
    int a = 0;
    int b = 0;
    int m = 0;
    int u = 0;
    int v = 0;
    int temp = 0;
    int corner = 0;
    int numberOfOpenListItems = 0;
    int addedGCost = 0;
    float tempGcost = 0;
    int path = 0;
    int tempx;
    int pathX;
    int pathY;
    int cellPosition;
    int newOpenListItemID = 0;
    //1. Convert location data to coordinates in the walkability array.
    int startX = Mathf.RoundToInt (startPos.x / WaypointDistance);
    int startY = Mathf.RoundToInt (startPos.z / WaypointDistance);
    int targetX = Mathf.RoundToInt (endPos.x / WaypointDistance);
    int targetY = Mathf.RoundToInt (endPos.z / WaypointDistance);

    if (targetX >= ClipMap.GetLength (0) || targetX < 0 || targetY >= ClipMap.GetLength (1) || targetY < 0 || heightLevel < 0 || heightLevel >= ClipMap.GetLength (2)) {
        Debug.Log ("Here");
        return nonexistent;
    }
    if(ClipMap[targetX, targetY, heightLevel].x == unwalkable) {
        Debug.Log ("Here");
        return nonexistent;

    }
    //If starting location and target are in the same location...
    if (startX == targetX && startY == targetY && pathLocation >= 0) {
        if (ClipMap [startX, startY, heightLevel].x == walkable) {
            pathBank = new Vector3[] {new Vector3 (startX, ClipMap [startX, startY, heightLevel].y, startY)};
            return found;
        } else {
            Debug.Log ("Here");
            return nonexistent;
        }
    }

    // If standing on unwalkable square, continue on last path
    if (ClipMap [startX, startY, heightLevel].x == unwalkable) {
        Debug.Log ("Here");
        return nonexistent;
    }

    //  If target square is unwalkable, return that it's a nonexistent path.
    if (ClipMap [startX, startY, heightLevel].x == unwalkable || ClipMap [targetX, targetY, heightLevel].z < entityClearance) {
        Debug.Log ("Here");
        goto noPath;
    }

    //3.Reset some variables that need to be cleared
    if (onClosedList > 1000000) { //reset whichList occasionally
        for (int x = 0; x < Mathf.RoundToInt (ClipMap.GetLength (0) / WaypointDistance); x++) {
            for (int y = 0; y < Mathf.RoundToInt (ClipMap.GetLength (1) / WaypointDistance); y++) {
                whichList [x, y] = 0;
            }
        }
        onClosedList = 10;
    }
    onClosedList = onClosedList + 2; //changing the values of onOpenList and onClosed list is faster than redimming whichList() array
    onOpenList = onClosedList - 1;
    pathLength = notStarted; //i.e, = 0
    pathLocation = notStarted; //i.e, = 0
    Gcost [startX, startY] = 0; //reset starting square's G value to 0

    //4.Add the starting location to the open list of squares to be checked.
    numberOfOpenListItems = 1;
    openList [1] = 1; //assign it as the top (and currently only) item in the open list, which is maintained as a binary heap (explained below)
    openX [1] = startX;
    openY [1] = startY;

    //5.Do the following until a path is found or deemed nonexistent.
    do {

        //6.If the open list is not empty, take the first cell off of the list.
        //  This is the lowest F cost cell on the open list.
        if (numberOfOpenListItems != 0) {

            //7. Pop the first item off the open list.
            parentXval = openX [openList [1]];
            parentYval = openY [openList [1]]; //record cell coordinates of the item
            whichList [parentXval, parentYval] = onClosedList; //add the item to the closed list

            //  Open List = Binary Heap: Delete this item from the open list, which
            //  is maintained as a binary heap. For more information on binary heaps, see:
            //  http://www.policyalmanac.org/games/binaryHeaps.htm
            numberOfOpenListItems = numberOfOpenListItems - 1; //reduce number of open list items by 1

            //  Delete the top item in binary heap and reorder the heap, with the lowest F cost item rising to the top.
            openList [1] = openList [numberOfOpenListItems + 1]; //move the last item in the heap up to slot #1
            v = 1;

            //  Repeat the following until the new item in slot #1 sinks to its proper spot in the heap.
            do {
                u = v;
                if (2 * u + 1 <= numberOfOpenListItems) { //if both children exist
                    //Check if the F cost of the parent is greater than each child.
                    //Select the lowest of the two children.
                    if (Fcost [openList [u]] >= Fcost [openList [2 * u]]) {
                        v = 2 * u;
                    }
                    if (Fcost [openList [v]] >= Fcost [openList [2 * u + 1]]) {
                        v = 2 * u + 1;
                    }
                } else {
                    if (2 * u <= numberOfOpenListItems) { //if only child #1 exists
                        //Check if the F cost of the parent is greater than child #1    
                        if (Fcost [openList [u]] >= Fcost [openList [2 * u]]) {
                            v = 2 * u;
                        }
                    }
                }

                if (u != v) { //if parent's F is > one of its children, swap them
                    temp = openList [u];
                    openList [u] = openList [v];
                    openList [v] = temp;
                } else
                    break; //otherwise, exit loop

            } while (true); //reorder the binary heap


            //7.Check the adjacent squares. (Its "children" -- these path children
            //  are similar, conceptually, to the binary heap children mentioned
            //  above, but don't confuse them. They are different. Path children
            //  are portrayed in Demo 1 with grey pointers pointing toward
            //  their parents.) Add these adjacent child squares to the open list
            //  for later consideration if appropriate (see various if statements
            //  below).
            for (b = parentYval - 1; b <= parentYval + 1; b++) {
                for (a = parentXval - 1; a <= parentXval + 1; a++) {

                    //  If not off the map (do this first to avoid array out-of-bounds errors)
                    if (a != -1 && b != -1 && a != ClipMap.GetLength (0) && b != ClipMap.GetLength (1)) {

                        //  If not already on the closed list (items on the closed list have
                        //  already been considered and can now be ignored).            
                        if (whichList [a, b] != onClosedList) {
                            //  If not a wall/obstacle square.
                            if (ClipMap [a, b, heightLevel].x != unwalkable && ClipMap [a, b, heightLevel].z >= entityClearance) {

                                //  Don't cut across corners
                                corner = walkable;
                                if (a == parentXval - 1) {
                                    if (b == parentYval - 1) {
                                        if (ClipMap [parentXval - 1, parentYval, heightLevel].x == unwalkable || ClipMap [parentXval - 1, parentYval, heightLevel].z < entityClearance || ClipMap [parentXval, parentYval - 1, heightLevel].x == unwalkable || ClipMap [parentXval, parentYval - 1, heightLevel].z < entityClearance) {
                                            corner = unwalkable;
                                        }
                                    } else if (b == parentYval + 1) {
                                        if (ClipMap [parentXval, parentYval + 1, heightLevel].x == unwalkable || ClipMap [parentXval, parentYval + 1, heightLevel].z < entityClearance || ClipMap [parentXval - 1, parentYval, heightLevel].x == unwalkable || ClipMap [parentXval - 1, parentYval, heightLevel].z < entityClearance) {
                                            corner = unwalkable;
                                        }
                                    }
                                } else if (a == parentXval + 1) {
                                    if (b == parentYval - 1) {
                                        if (ClipMap [parentXval, parentYval - 1, heightLevel].x == unwalkable || ClipMap [parentXval, parentYval - 1, heightLevel].z < entityClearance || ClipMap [parentXval + 1, parentYval, heightLevel].x == unwalkable || ClipMap [parentXval + 1, parentYval, heightLevel].z < entityClearance) {
                                            corner = unwalkable;
                                        }
                                    } else if (b == parentYval + 1) {
                                        if (ClipMap [parentXval + 1, parentYval, heightLevel].x == unwalkable || ClipMap [parentXval + 1, parentYval, heightLevel].z < entityClearance || ClipMap [parentXval, parentYval + 1, heightLevel].x == unwalkable || ClipMap [parentXval, parentYval + 1, heightLevel].z < entityClearance) {
                                            corner = unwalkable;
                                        }
                                    }
                                }
                                if (corner == walkable) {

                                    //  If not already on the open list, add it to the open list.           
                                    if (whichList [a, b] != onOpenList) {

                                        //Create a new open list item in the binary heap.
                                        newOpenListItemID = newOpenListItemID + 1; //each new item has a unique ID #
                                        m = numberOfOpenListItems + 1;
                                        openList [m] = newOpenListItemID; //place the new open list item (actually, its ID#) at the bottom of the heap
                                        if (newOpenListItemID >= openX.Length) {
                                            Debug.Log ("Here");
                                            return nonexistent;
                                        }
                                        openX [newOpenListItemID] = a;
                                        openY [newOpenListItemID] = b; //record the x and y coordinates of the new item

                                        //Figure out its G cost
                                        if (Mathf.Abs (a - parentXval) == 1 && Mathf.Abs (b - parentYval) == 1) {
                                            addedGCost = diagonal_cost; //cost of going to diagonal squares
                                        } else {
                                            addedGCost = horizontal_cost; //cost of going to non-diagonal squares
                                        }
                                        Gcost [a, b] = Gcost [parentXval, parentYval] + addedGCost;

                                        //Figure out its H and F costs and parent
                                        float d = horizontal_cost;
                                        float d2 = diagonal_cost;
                                        //Hcost [openList [m]] = 10 * (Mathf.Abs (a - targetX) + Mathf.Abs (b - targetY));
                                        int dx = Mathf.Abs (a - targetX);
                                        int dy = Mathf.Abs (b - targetY);
                                        /*int dx2 = startX - targetX;
                                        int dy2 = startY - targetY;
                                        int cross = Mathf.Abs (dx*dy2 - dx2*dy);
                                        float p = 0.001f;*/

                                        Hcost [openList [m]] = (d * (dx + dy) + (d2 - 2.0f * d) * (float) Mathf.Min (dx, dy));


                                        Fcost [openList [m]] = Gcost [a, b] + Hcost [openList [m]];
                                        parentX [a, b] = parentXval;
                                        parentY [a, b] = parentYval;

                                        //Move the new open list item to the proper place in the binary heap.
                                        //Starting at the bottom, successively compare to parent items,
                                        //swapping as needed until the item finds its place in the heap
                                        //or bubbles all the way to the top (if it has the lowest F cost).
                                        while (m != 1) { //While item hasn't bubbled to the top (m=1)
                                            //Check if child's F cost is < parent's F cost. If so, swap them.   
                                            if (Fcost [openList [m]] <= Fcost [openList [m / 2]]) {
                                                temp = openList [m / 2];
                                                openList [m / 2] = openList [m];
                                                openList [m] = temp;
                                                m = m / 2;
                                            } else
                                                break;
                                        }
                                        numberOfOpenListItems = numberOfOpenListItems + 1; //add one to the number of items in the heap

                                        //Change whichList to show that the new item is on the open list.
                                        whichList [a, b] = onOpenList;
                                    }

                                    //8.If adjacent cell is already on the open list, check to see if this 
                                    //  path to that cell from the starting location is a better one. 
                                    //  If so, change the parent of the cell and its G and F costs. 
                                    else { //If whichList(a,b) = onOpenList

                                        //Figure out the G cost of this possible new path
                                        if (Mathf.Abs (a - parentXval) == 1 && Mathf.Abs (b - parentYval) == 1) {
                                            addedGCost = diagonal_cost; //cost of going to diagonal tiles
                                        } else {
                                            addedGCost = horizontal_cost; //cost of going to non-diagonal tiles
                                        }
                                        tempGcost = Gcost [parentXval, parentYval] + addedGCost;

                                        //If this path is shorter (G cost is lower) then change
                                        //the parent cell, G cost and F cost.       
                                        if (tempGcost < Gcost [a, b]) { //if G cost is less,
                                            parentX [a, b] = parentXval; //change the square's parent
                                            parentY [a, b] = parentYval;
                                            Gcost [a, b] = tempGcost; //change the G cost

                                            //Because changing the G cost also changes the F cost, if
                                            //the item is on the open list we need to change the item's
                                            //recorded F cost and its position on the open list to make
                                            //sure that we maintain a properly ordered open list.
                                            for (int x = 1; x <= numberOfOpenListItems; x++) { //look for the item in the heap
                                                if (openX [openList [x]] == a && openY [openList [x]] == b) { //item found
                                                    Fcost [openList [x]] = Gcost [a, b] + Hcost [openList [x]]; //change the F cost

                                                    //See if changing the F score bubbles the item up from it's current location in the heap
                                                    m = x;
                                                    while (m != 1) { //While item hasn't bubbled to the top (m=1)
                                                        //Check if child is < parent. If so, swap them. 
                                                        if (Fcost [openList [m]] < Fcost [openList [m / 2]]) {
                                                            temp = openList [m / 2];
                                                            openList [m / 2] = openList [m];
                                                            openList [m] = temp;
                                                            m = m / 2;
                                                        } else
                                                            break;
                                                    }
                                                    break; //exit for x = loop
                                                } //If openX(openList(x)) = a
                                            } //For x = 1 To numberOfOpenListItems
                                        } //If tempGcost < Gcost(a,b)

                                    } //else If whichList(a,b) = onOpenList
                                } //If not cutting a corner
                            } //If not a wall/obstacle square.
                        } //If not already on the closed list
                    } //If not off the map
                } //for (a = parentXval-1; a <= parentXval+1; a++){
            } //for (b = parentYval-1; b <= parentYval+1; b++){

        } //if (numberOfOpenListItems != 0)

        //9.If open list is empty then there is no path.    
        else {
            Debug.Log ("Here");
            path = nonexistent;
            break;
        }

        //If target is added to open list then path has been found.
        if (whichList [targetX, targetY] == onOpenList) {
            path = found;
            break;
        }

    } while (true); //Do until path is found or deemed nonexistent

    //10.Save the path if it exists.
    if (path == found) {

        //a.Working backwards from the target to the starting location by checking
        //  each cell's parent, figure out the length of the path.
        pathX = targetX;
        pathY = targetY;
        do {
            //Look up the parent of the current cell.   
            tempx = parentX [pathX, pathY];
            pathY = parentY [pathX, pathY];
            pathX = tempx;

            //Figure out the path length
            pathLength = pathLength + 1;
        } while (pathX != startX || pathY != startY);

        //b.Resize the data bank to the right size in bytes
        pathBank = new Vector3[pathLength];

        //c. Now copy the path information over to the databank. Since we are
        //  working backwards from the target to the start location, we copy
        //  the information to the data bank in reverse order. The result is
        //  a properly ordered set of path data, from the first step to the
        //  last.
        pathX = targetX;
        pathY = targetY;
        cellPosition = pathLength; //start at the end
        do {
            cellPosition = cellPosition - 1; //work backwards 1 integer
            pathBank [cellPosition] = new Vector3 (pathX, ClipMap [pathX, pathY, heightLevel].y, pathY);

            //d.Look up the parent of the current cell. 
            tempx = parentX [pathX, pathY];
            pathY = parentY [pathX, pathY];
            pathX = tempx;

            //e.If we have reached the starting square, exit the loop.  
        } while (pathX != startX || pathY != startY);

    }
    return path;


    //13.If there is no path to the selected target, return non existant
noPath:
        return nonexistent;
}

2 个答案:

答案 0 :(得分:1)

您需要使用的不是路径查找器,而是线条绘制算法。基本上,你只是画一条从A到B的线。如果有障碍物,你需要一个探路者。

查看Bresenham的线路算法(https://en.wikipedia.org/wiki/Bresenham's_line_algorithm),这将让你到达那里。

答案 1 :(得分:0)

您遇到的不是错误。不幸的事实是,当在空白空间中寻路时,A* 可以找到多条路径具有相同的价值,因此它会根据选择邻居的顺序等因素选择“随机”的一条。

这个问题被称为'tie Breaking',也就是说A*进行比较并找到两个或更多具有相同成本的邻居。在这种情况下,它可能会选择第一个。假设您的代码检查北、东、南、西,这将解释为什么它似乎在很长一段时间内更喜欢向东。

有多种方法可以尝试解决该问题(不幸的是没有快速解决方案),例如,更改移动成本或启发式以在水平和垂直之间交替,或更改选择邻居的顺序,或更新优先级以便与具有相同值的旧邻居相比,新邻居的优先级更高。

<块引用>

这是 A* 和 Dijkstra 算法的技巧:在图形类中, 使移动成本取决于 (x + y) % 2:

当 0 时:使水平移动稍微更昂贵 当 1 时:使 垂直移动稍贵

Red Blob Games 拥有大量有关该问题的信息库,特别是此处的“Ugly Paths”和此处的“Heuristics”。

<块引用>

Steven van Dijk 建议使用更直接的方法来做到这一点 将 h 传递给比较函数。当 f 值为 相等,比较函数会通过查看 h 来打破平局。

另一种打破平局的方法是添加一个确定性随机数到 启发式或边缘成本。 (选择确定性随机数的一种方法 number 是计算坐标的散列。)这打破了更多的联系 比调整 h 如上所述。感谢 Cris Fuhrman 提出这个建议。

另一种打破关系的方法是更喜欢沿着 从起点到终点的直线:

dx1 = current.x - goal.x 
dy1 = current.y - goal.y 
dx2 = start.x - goal.x 
dy2 = start.y - goal.y 
cross = abs(dx1*dy2 - dx2*dy1) 
heuristic += cross*0.001 
<块引用>

此代码计算起始到目标向量和目标向量之间的向量叉积 当前点到目标向量。当这些 向量不对齐,叉积会更大。结果是 这段代码会对位于的路径略有偏好 沿着从起点到终点的直线路径。当有 没有障碍,A* 不仅探索了更少的地图,路径看起来 也很不错

我不知道哪个选项最适合您,您可能需要先尝试几个,然后才能找到最适合您需求的选项。

编辑

我遇到了同样的问题,发现最好的解决方案是结合上述启发式+交叉,以及改变邻居选择顺序的广度优先黑客。

<块引用>

这是广度优先搜索的技巧:在图形类中,使 邻居列表取决于 (x + y) % 2:

0 时:返回南、北、西、东邻居 当 1:返回 东、西、北、南邻居

这似乎解决了直线对角线和不规则线的问题。