天天看点

A星寻路算法 Unity C#

在Unity中新建类A_Star,其内复制下述全文,再根据下文中讲述的应用案例就可以明白如何使用了

using System.Collections.Generic;
using UnityEngine;

public class A_Star
{
    /*
    自己书写的Unity可使用的A星算法(C#版)
    该脚本仅针对上下左右移动,没有做斜向移动
    如果要看算法过程,可以转到最下面,不保证说明正确或清晰
    */

    /*
       该脚本应用举例:
       //设置地图stringMap,可走点1,不可走点0,比如:
       string stringMap =//作为地图数据
       "0000001111" +
       "0000111000" +
       "0000011010" +
       "0011111010" +
       "0110101010" +
       "1111111000";
        int maxX = 9;//行从0计到9,则声明maxX=9
        int maxY = 5;//列从0计到5,则声明maxY=5

        //上方数据中,最左下角为坐标(0,0),然后:
        Vector2Int[] way;
        A_Star.GetWay(new Vector2Int(1, 0), new Vector2Int(6, 4), stringMap,maxX,maxY, out way);
        //就可以得到stringMap中,从坐标(0,0)到坐标(6,4)的路线way,way[0]表示起点坐标,其后元素顺序与路径上经过的各点一致
       -------------------------------------------------------------------
       //或者使用int[]作为地图数据也行,比如:
       int[] intMap =
       {
           0,1,1,1,1
           1,1,0,1,1
           1,0,0,0,1
       }
       int maxX=4;//行从0计到4,则声明maxX=4
       int maxY=2;//列从0计到2,则声明maxY=2

        //上方数据中,最左下角为坐标(0,0),然后:
       Vector2Int[] way;
       A_Star.GetWay(new Vector2Int(0,1),new Vector2Int(4,0),intMap,maxX,maxY,out way);
       //就可以得到intMap中,从坐标(0,1)到坐标(4,0)的路线way,way[0]表示起点坐标,其后元素顺序与路径上经过的各点一致
        -------------------------------------------------------------------
        //如果对同一个地图使用多次A_Star寻路,那么使用下述方式最好:
        A_Star.Point[][] points=A_Star.GetPointsMatrixByStringMap(stringMap,maxX,maxY);//或根据intMap得到points
        A_Star.GetWay(new VectorInt(起点1x,起点1y), new VectorInt(终点1x,终点1y), points, out way);
        A_Star.GetWay(new VectorInt(起点2x,起点2y), new VectorInt(终点2x,终点2y), points, out way);
        A_Star.GetWay(new VectorInt(起点3x,起点3y), new VectorInt(终点3x,终点3y), points, out way);
        //...
        //上述将Point[][]类型数据暂存起来,再多次使用GetWay
        */

    //根据string型地图以及起点,终点,来试图获取路径(经过点的坐标数组),返回是否存在一条可行的路径
    public static bool GetWay(Vector2Int startPos, Vector2Int endPos, string stringMap, int maxX, int maxY, out Vector2Int[] way)
    {
        return GetWay(startPos, endPos, GetPointsMatrixByStringMap(stringMap, maxX, maxY), out way);
    }
    //根据int[]型地图以及起点,终点,来试图获取路径(经过点的坐标数组),返回是否存在一条可行的路径
    public static bool GetWay(Vector2Int startPos, Vector2Int endPos, int[] intMap, int maxX, int maxY, out Vector2Int[] way)
    {
        return GetWay(startPos, endPos, GetPointsMatrixByIntMap(intMap, maxX, maxY), out way);
    }

    //根据string型地图信息获得Point[][]类型数据
    public static Point[][] GetPointsMatrixByStringMap(string stringMap, int maxX, int maxY)
    {
        int[] map = GetIntMapByString(stringMap, maxX, maxY);
        bool[][] boolMatrix = GetBoolMatrixByIntMap(map, maxX, maxY);
        return GetPointsMatrixByBoolMatrix(boolMatrix, maxX, maxY);
    }
    //根据int[]型地图信息获得Point[][]类型数据
    public static Point[][] GetPointsMatrixByIntMap(int[] intMap, int maxX, int maxY)
    {
        bool[][] boolMatrix = GetBoolMatrixByIntMap(intMap, maxX, maxY);
        return GetPointsMatrixByBoolMatrix(boolMatrix, maxX, maxY);
    }

    //根据Point[][],以及起点和终点的信息,获得最终路径(经过点的坐标数组),返回是否存在一条可行的路径
    public static bool GetWay(Vector2Int startPos, Vector2Int endPos, Point[][] points, out Vector2Int[] way)
    {
        foreach (var array in points)
        {
            foreach (var o in array)
            {
                o.fromStart = int.MaxValue;
                o.toEnd = 0;
                o.inClosed = false;
                o.inOpen = false;
            }
        }

        open.RemoveRange(0, open.Count);
        closed.RemoveRange(0, closed.Count);

        way = new Vector2Int[0];

        Point start = points[startPos.x][startPos.y];
        start.Refresh(0, start, endPos.x, endPos.y);

        while (true)
        {
            if (open.Count <= 0) return false;

            if (FindMinPointF().CalculateAround(points, endPos))
            {
                way = GetWayFromPointMatrix(points, startPos, endPos);
                return true;
            }
        }
    }

    static Point FindMinPointF()
    {
        int where = -1;
        int min = int.MaxValue;
        for (int i = 0; i < open.Count; ++i)
            if (open[i].F <= min)
            {
                where = i;
                min = open[i].F;
            }
        return open[where];
    }
    static Vector2Int[] GetWayFromPointMatrix(Point[][] points, Vector2Int startPos, Vector2Int endPos)
    {
        List<Vector2Int> wayList = new List<Vector2Int>();

        Point prePoint = points[endPos.x][endPos.y];
        wayList.Add(new Vector2Int(prePoint.x, prePoint.y));

        while (true)
        {
            if (prePoint.x == startPos.x && prePoint.y == startPos.y) break;

            prePoint = prePoint.lastPoint;
            wayList.Add(new Vector2Int(prePoint.x, prePoint.y));
        }

        Vector2Int[] way = new Vector2Int[wayList.Count];
        for (int i = 0; i < wayList.Count; ++i)
            way[i] = wayList[wayList.Count - 1 - i];

        return way;
    }
    static int[] GetIntMapByString(string stringMap, int maxX, int maxY)
    {
        int amout = (maxX + 1) * (maxY + 1);
        int[] intMap = new int[amout];
        char[] cs = stringMap.ToCharArray();
        for (int i = 0; i < amout; ++i)
            intMap[i] = cs[i] - 48;

        return intMap;
    }
    static bool[][] GetBoolMatrixByIntMap(int[] map, int maxX, int maxY)
    {
        bool[][] canTo = new bool[maxX + 1][];
        for (int i = 0; i <= maxX; ++i)
            canTo[i] = new bool[maxY + 1];

        int tempX = maxX + 1;
        int mapPos;
        for (int i = 0; i <= maxX; i += 1)
            for (int j = 0; j <= maxY; j += 1)
            {
                /*
                map一行的个数是maxX+1个,一列的个数是maxY+1个
                这里举一个例子:
                这里有个int[] map如下,其中maxX=5,maxY=3
                1,0,1,1,0,1,
                1,0,1,1,0,1,
                1,1,0,1,0,1,
                0,1,1,1,1,1
                最左下角(0,0)点对应map的位置是(maxX+1)*(maxY-0)+0=18
                (1,0)点对应map的位置是(maxX+1)*(maxY-0)+1=19
                (1,1)点对应map的位置是(maxX+1)*(maxY-1)+1=13
                (4,3)点对应map的位置是(maxX+1)*(maxY-3)+4=4
                这样就得到了规律(x,y),x作为加数加到前式的后面,y作为maxY后面的减数
                */
                mapPos = tempX * (maxY - j) + i; ;
                if (map[mapPos] == 1)
                    canTo[i][j] = true;
                else canTo[i][j] = false;
            }

        return canTo;
    }
    static Point[][] GetPointsMatrixByBoolMatrix(bool[][] canTo, int maxX, int maxY)
    {
        int tempX = maxX + 1, tempY = maxY + 1;
        Point[][] points = new Point[tempX][];
        for (int i = 0; i < tempX; i += 1)
            points[i] = new Point[tempY];

        for (int i = 0; i < tempX; i += 1)
            for (int j = 0; j < tempY; j += 1)
                points[i][j] = new Point(i, j, canTo[i][j]);

        return points;
    }

    static List<Point> open = new List<Point>();
    static List<Point> closed = new List<Point>();

    public class Point
    {
        public int x;
        public int y;
        public int fromStart;//G
        public int toEnd;//H
        public int F;
        public bool canTo;

        public bool inOpen;
        public bool inClosed;

        public Point lastPoint;

        public Point(int x, int y, bool canTo)
        {
            this.x = x; this.y = y; fromStart = int.MaxValue; toEnd = 0;
            F = 0; this.canTo = canTo; inOpen = false; inClosed = false;
        }

        public void Refresh(int _fromStart, Point _lastPoint, int endX, int endY)
        {
            if (!inOpen)
                open.Add(this);
            inOpen = true;

            int temp = endX - x;
            if (temp < 0) temp = -temp;
            toEnd += temp;

            temp = endY - y;
            if (temp < 0) temp = -temp;
            toEnd += temp;

            if (_fromStart <= fromStart)
            {
                fromStart = _fromStart;
                F = fromStart + toEnd;
                lastPoint = _lastPoint;
            }
        }
        public void Passed()
        {
            closed.Add(this);

            for (int i = 0; i < open.Count; ++i)
                if (open[i].x == x && open[i].y == y)
                {
                    open.RemoveAt(i);
                    break;
                }

            inOpen = false;
            inClosed = true;
        }
        public bool CalculateAround(Point[][] points, Vector2Int endPos)
        {
            if (x == endPos.x && y == endPos.y)
                return true;

            int maxX = points.Length - 1;
            int maxY = points[0].Length - 1;

            if (x - 1 >= 0)
            {
                if (!points[x - 1][y].inClosed && points[x - 1][y].canTo)
                    points[x - 1][y].Refresh(fromStart + 1, this, endPos.x, endPos.y);
            }
            if (x + 1 <= maxX)
            {
                if (!points[x + 1][y].inClosed && points[x + 1][y].canTo)
                    points[x + 1][y].Refresh(fromStart + 1, this, endPos.x, endPos.y);
            }
            if (y - 1 >= 0)
            {
                if (!points[x][y - 1].inClosed && points[x][y - 1].canTo)
                    points[x][y - 1].Refresh(fromStart + 1, this, endPos.x, endPos.y);
            }
            if (y + 1 <= maxY)
            {
                if (!points[x][y + 1].inClosed && points[x][y + 1].canTo)
                    points[x][y + 1].Refresh(fromStart + 1, this, endPos.x, endPos.y);
            }

            Passed();

            return false;
        }
    }
}

/*
包含的数据结构:

    Point以及Point[][],后者记录整个地图上的点
    每个Point点除了含有坐标值x,y,还含有  fromStart/toEnd/F/[上一个点]/[是否在open列表中]/[是否在closed列表中]
    fromStart表示从原点到该点的总路程(fromStart会动态变化)
    toEnd表示从该点到目的坐标的直接路程(固定值,就是 横坐标差(绝对值)+纵坐标差(绝对值))
    F值赋值为fromStart+toEnd
    [上一个点]赋值为上一个经过的点(下面有具体的赋值方式说明)

    [open列表]和[closed列表] 分别记录 [可以走的点]和[不能再走的点]
------------------------
现在给出了地图,然后要计算从起点到终点的一条最短路径:

     过程描述:
     计算起点的fromStart(=0),toEnd,F,起点的[上一个点]可以赋值为空或者自身,然后加入该起点到open列表
     然后重复下列步骤,直到找到最终点,如果循环过程中,若open列表元素数量变为0,表示没有找到一条可行的路径
    {
    从open列表中选择F最小的点记为P点(如果多个点有相同的最小值F,一般取其中最后添加进open列表的点)

    然后判断P点是否为终点,若是,则得到最终路径(根据每一个点所记录的[上一个点]可以得到完整路径)并返回,若不是,则对该点周围(上下左右)的每个点C进行下述:
    .若C没有在open列表中,则该点的fromStart赋值为P点的fromStart+1,并计算toEnd,F,以及设置C点的[上一个点]为P点,然后将C点添加到open列表,C点的[是否在open列表中]赋值为true
    .若C已在open列表中,则判断P点的fromStart+1是否比当前点的fromStart更小,若更小则更新该点的fromStart,F,以及替换[上一个点]为P点
    .若C在closed列表中则不计算

    加入P点到closed列表(closed列表中的点不会再被计算或刷新),P点的[是否在closed列表中]赋值为true,[是否在open列表中]赋值为false
    }
*/

/*
如果将上述脚本用到C#项目,需要新建类Vector2Int,如下:
public class Vector2Int
{
    public int x;
    public int y;

    public Vector2Int(){}

    public Vector2Int(int x,int y)
    {
        this.x=x;
        this.y=y;
    }
}
*/
           

继续阅读