自动寻路之 --AStar算法


如图所示,为了向大家介绍AStar自动寻路算法,我先用一个二维数组作为地图的数据建立了一个如图所示的地图

    privateint[,] map0 = new int[,]{

       { 0,0,0,0,0,0,0},

       { 0,1,1,1,1,1,0},

       { 0,1,2,0,1,1,0},

       { 0,1,1,0,1,1,0},

       { 0,1,1,0,1,1,0},

       { 0,1,1,1,1,1,0},

       { 0,0,0,0,0,0,0}

   };

二维数组用来显示地图的基本信息是最为简单的一种方式,在游戏中的应用是很多的, 例如贪吃蛇和俄罗斯方块基本原理就是移动方块而已. 而一些大型游戏的地图看起来比较平滑的起始只是进行了些处理, 是将各种"地貌"铺在这样的小方块上. 为了抽象理解看如下这张较为平滑的简易地图作为理解

【问题分析】

1.    已知地图样貌信息和起始位置

2.    从初始方格周围开始计算距离终止方格的信息并进行记录,减少信息冗余

3.    可走动方向为棋面可走8个方向,但斜走时不可有上下左右墙阻挡(路径抽象最小化如1图可知次举动不可为,除非特殊情况的设定。本帖设定不可)

【实现策略】

1.    定义方格的信息(距离重点信息+移动位置点信息(包含前一位置))权值

F = G + H

G: 表示从某位置移动到网格上指定位置点的移动耗费 (距离)—包含前一位置耗费

H: .表示从指定的位置点移动到终点 B 的预计耗费

2.     定义两个容器

(1)“开启列表”保存已检测到和未到达的方格

(2)“关闭列表”保存已到达的过方格

3.     先将起始位置保存到(1),从(1)得到F值最小的并设为A, 再将A从(1)中移除,记录A位置可到达方格并将他们的父亲设为A并计算他们的权值并保存到(1)中。如果从(1)中得到F值最小方格为终点则结束

4.     线性递归 【3】

5.     A为终点结束后只需从终点的的父节点开始向上遍历则可得到完整的路径点

【抽象分析】 使用图例加强理解

◆   假设从绿方格到红方格,将起点加入(1);再从(1)得到F值最小从(1)中移除并设为A。在计算A可到达的方格加入(1)中并计算他们的权值,在将他们的父亲设为A

◆   再从(1)得到F值最小的从(1)中移除并设为A。在计算A可到达的方格加入(1)中并将他们的父亲设为A再计算他们的权值(需将G值进行累==加上其父亲的G值表示路程信息),如A上面的方格G值原本位置因为其父亲的G值为1所以就为1+1=2

● 从(1)得到F值最小的以第一个为先(否则只需更改算法设定)

 

◆   再从(1)得到F值最小的从(1)中移除并设为A。在计算A可到达的方格加入(1)中并将他们的父亲设为A再计算他们的权值

◆   再从(1)得到F值最小的从(1)中移除并设为A。在计算A可到达的方格加入(1)中并将他们的父亲设为A再计算他们的权值

◆   再从(1)得到F值最小的从(1)中移除并设为A。如果A为终点则结束

 【代码实现】

Point类        --方格信息类

public class Point
{
    public Point Parent { get; set; }
    public float F { get; set; }
    public float G { get; set; }
    public float H { get; set; }

    public int mapPointX { get; set; }
    public int mapPointY { get; set; }
    public Vector2 MapVector2 { get; set; }

    public bool IsWall { get; set; }
    private bool _isVisi;
    public bool IsVisi { get { return _isVisi && !IsWall; } set { _isVisi = value; } }

    public Point(int mapX, int mapY, Point parent = null)
    {
        this.mapPointX = mapX;
        this.mapPointY = mapY;
        MapVector2 = new Vector2(mapPointX, mapPointY);
        this.Parent = parent;
        IsWall = false;
        IsVisi = true;
    }
    public Point(bool isVisi = false)
    {
        IsVisi = isVisi;
    }
    public void UpdateParent(Point parent, float g)
    {
        this.Parent = parent;
        this.G = g;
        F = G + H;
    }
}

AStar类        --实现类

public class Astar :MonoBehaviour{

    private const float COST_HOR = 1f;
    private const float COST_VER = 1f;
    private const float COST_DIA = 2f;
    private int mapWidth;
    private int mapHeight;

    private Point[,] map;                                           //数据地图
    private List<GameObject> objList = new List<GameObject>();      //最佳移动点集
    private GameObject testObj;                                     //FGH数值显示(测试使用)
    List<Point> closeList = new List<Point>();

    //得到传入的数据--初始化数据地图--计算最佳移动点集--返回最佳移动点集
    public List<Vector3> StartMake (int[,] map0,Vector3 sPos,Vector3 ePos,GameObject testTObj)
    {
        testObj = testTObj;
        InitMap(map0);

        Point end = map[Mathf.RoundToInt(ePos.x), Mathf.RoundToInt(ePos.z)];
        Point start = map[Mathf.RoundToInt(sPos.x), Mathf.RoundToInt(sPos.z)];
        FindPath(start, end);
        
        return GetPathPoints(start, end);                          //返回最佳移动点集
    }

    //初始化数据地图
    void InitMap(int[,] map0)
    {
        mapWidth = map0.GetLength(0);
        mapHeight = map0.GetLength(1);
        map = new Point[mapWidth, mapHeight];
        for (int x = 0; x < mapWidth; x++) {
            for (int y = 0; y < mapHeight; y++) {
                map[x,y] = new Point(x,y);
                if(map0[x,y] == 0)
                    map[x, y].IsWall = true;
            }
        }
    }
    //计算最佳移动点集
    void FindPath(Point start, Point end) {

        List<Point> openList = new List<Point>();
        //List<Point> closeList = new List<Point>();
        closeList.Clear();
        
        openList.Add(start);

        while (openList.Count > 0) {
            Point point = GetMinF(openList);
            openList.Remove(point);
            closeList.Add(point);

            List<Point> surroundPoints = GetGroundPoint(point);
            GetSurroundPoints(surroundPoints, closeList);

            foreach (Point surroundPoint in surroundPoints) {
                if (openList.IndexOf(surroundPoint) > -1)
                {
                    float nowG = CalcG(surroundPoint, point);
                    if (nowG < point.G)
                    {
                        surroundPoint.UpdateParent(point, nowG);
                    }
                }
                else {
                    surroundPoint.Parent = point;
                    CalcFGH(surroundPoint, end);
                    FindAndSort(openList, surroundPoint);
                }
            }
            if (openList.IndexOf(end) > -1) {
                break;
            }
        }
    }

    void FindAndSort(List<Point> openList, Point point)
    {

        bool IsInsert = true;
        foreach (Point pointT in openList)
        {
            if (point.mapPointX == pointT.mapPointX && point.mapPointY == pointT.mapPointY)
            {
                IsInsert = false;
            }
        }
        if (IsInsert)
        {
            openList.Add(point);
        }
    }
    void GetSurroundPoints(List<Point> src, List<Point> closeList) {
        foreach (Point p in closeList) {
            if (src.IndexOf(p) > -1)
            {
                src.Remove(p);
            }
        }
    } 

    List<Point> GetGroundPoint(Point point) {

        Point up = null, down = null, right = null, left = null;
        Point uR = null, uL = null, dR = null, dL = null;
        List<Point> tempList = new List<Point>();

        //上下左右
        if (point.mapPointY < mapHeight - 1) up = map[point.mapPointX, point.mapPointY + 1];
        else up = new Point();

        if (point.mapPointY > 0) down = map[point.mapPointX, point.mapPointY - 1];
        else down = new Point();

        if (point.mapPointX < mapWidth - 1) right = map[point.mapPointX + 1, point.mapPointY];
        else right = new Point();

        if (point.mapPointX > 0) left = map[point.mapPointX - 1, point.mapPointY];
        else left = new Point();

        //上右 上左 下右 下左 
        if (up != null && right != null) uR = map[point.mapPointX + 1, point.mapPointY + 1];
        else uR = new Point();

        if (up != null && left != null) uL = map[point.mapPointX - 1, point.mapPointY + 1];
        else uL = new Point();

        if (down != null && right != null) dR = map[point.mapPointX + 1, point.mapPointY - 1];
        else dR = new Point();

        if (down != null && left != null) dL = map[point.mapPointX - 1, point.mapPointY - 1];
        else dL = new Point();


        if (up.IsVisi) tempList.Add(up);
        if (down.IsVisi) tempList.Add(down);
        if (right.IsVisi) tempList.Add(right);
        if (left.IsVisi) tempList.Add(left);
        if (uR.IsVisi && up.IsVisi && right.IsVisi) tempList.Add(uR);
        if (uL.IsVisi && up.IsVisi && left.IsVisi) tempList.Add(uL);
        if (dR.IsVisi && down.IsVisi && right.IsVisi) tempList.Add(dR);
        if (dL.IsVisi && down.IsVisi && right.IsVisi) tempList.Add(dL);

        return tempList;
    }

    Point GetMinF(List<Point> tempList) {
        Point point = null;
        float minPoint = float.MaxValue;

        for (int i = tempList.Count - 1; i >= 0; i--) {
            if (tempList[i].F <= minPoint)
            {
                minPoint = tempList[i].F;
                point = tempList[i];
            }
        }
        return point;
    }

    void CalcFGH(Point now, Point end) {
        // F =G + H
        float h = Mathf.Abs(end.mapPointX - now.mapPointX) + Mathf.Abs(end.mapPointY - now.mapPointY);
        float g;
        if (now.Parent == null)
        {
            g = 0;
        }
        else
        {
            float disance = Vector2.Distance(now.Parent.MapVector2, now.MapVector2);
            float disanceZ = Mathf.RoundToInt(disance);
            if (disance - disanceZ != 0)
            {
                g = disance * COST_DIA + now.Parent.G;
            }
            else
            {
                if (now.Parent.mapPointX == now.mapPointX && now.Parent.mapPointY != now.mapPointY)
                    g = disance * COST_HOR + now.Parent.G;
                else
                    g = disance * COST_VER + now.Parent.G;
            }
        }
        now.G = g;
        now.H = h;
        now.F = g + h;
    }

    float CalcG(Point now, Point parent) {
        return Vector2.Distance(now.MapVector2, parent.MapVector2) + parent.G;
    }

    List<Vector3> GetPathPoints1(Point start, Point end)
    {
        List<Vector3> path = new List<Vector3>();
        List<Vector3> path0 = new List<Vector3>();

        Point point = end;
        while (true)
        {
            path.Add(new Vector3(point.mapPointX, 0.5f, point.mapPointY));
            point = point.Parent;
            if (point == null)
                break;
        }
        for (int i = 1; i <= path.Count; i++)
        {
            path0.Add(path[path.Count - i]);
        }
        return path0;
    }
}

AStar 辅助方法测试用

    #region AStarPaint

    List<Vector3> GetPathPoints(Point start, Point end) {
        foreach (GameObject obj in objList) {
            Destroy(obj);
        }
        objList.Clear();
        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                if (map[x, y].IsWall)
                {
                    PaintSceneCube(x+ mapWidth, y, Color.black);
                }
                PaintTest(x + mapWidth, y, map[x, y]);
            }
        }
        List<Vector3> path = new List<Vector3>();
        List<Vector3> path0 = new List<Vector3>();
        Point point = end;
        while (true) {

            path.Add(new Vector3(point.mapPointX, 0.5f, point.mapPointY));
            Color color = Color.gray;
            if (point == start)
            {
                color = Color.green;
                
            }
            if (point == end)
            {
                color = Color.red;
            }
            PaintSceneCube(point.mapPointX+ mapWidth, point.mapPointY, color,point);
            point = point.Parent;
            if (point == null)
                break;
        }
        for (int i=1;i<= path.Count;i++) {
            path0.Add(path[path.Count - i]);
        }
        return path0;
    }
    void PaintSceneCube(int x, int y, Color color)
    {
        GameObject gameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
        gameObject.transform.position = new Vector3(x, 0, y);
        gameObject.transform.SetParent(GameObject.Find("GameFaced").transform);
        gameObject.GetComponent<Renderer>().material.color = color;
        objList.Add(gameObject);
    }
    void PaintSceneCube(int x, int y, Color color,Point point) 
    {
        GameObject gameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
        gameObject.transform.position = new Vector3(x, 0, y);
        gameObject.transform.SetParent(GameObject.Find("GameFaced").transform);
        gameObject.GetComponent<Renderer>().material.color = color;
        objList.Add(gameObject);
    }
    void PaintTest(int x, int y, Point point) {
        if (point.F <= 0)
            return;
        
        GameObject testObject = Instantiate(testObj);
        testObject.transform.position = new Vector3(x, 0.7f, y);
        testObject.transform.SetParent(GameObject.Find("GameFaced").transform);
        testObject.transform.Find("F").GetComponent<Text>().text = Mathf.RoundToInt(point.F).ToString();
        testObject.transform.Find("G").GetComponent<Text>().text = Mathf.RoundToInt(point.G).ToString();
        testObject.transform.Find("H").GetComponent<Text>().text = Mathf.RoundToInt(point.H).ToString();
        objList.Add(testObject);

    }
    #endregion

主控制类

public class GameManager : MonoBehaviour {

    private int[,] map0 = new int[,]{
        { 0,0,0,0,0,0,0},
        { 0,1,1,1,1,1,0},
        { 0,1,2,0,1,1,0},
        { 0,1,1,0,1,1,0},
        { 0,1,1,0,1,1,0},
        { 0,1,1,1,1,1,0},
        { 0,0,0,0,0,0,0}
    };
    
    #region AStar
    private Astar astar = new Astar();
    public GameObject testObj;

    #endregion

    public GameObject wallObject;
    public GameObject planeObject;
    public GameObject gamePlayerObject;
    private GameObject gamePlayer = null;

    private List<Vector3> targetPos = new List<Vector3>();


    void Start () {
        Init();
    }
	
	// Update is called once per frame
	void Update () {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.tag == "Plane")
                {
                    Vector3 temp = new Vector3(hit.transform.position.x,0.5f, hit.transform.position.z);

                    AtarGetPos(temp);
                    //targetPos.Add(temp);
                }
            }
        }
    }
    private void FixedUpdate()
    {
        PlayerMove(targetPos);
    }
    void Init() {

        for (int i = 0; i < map0.GetLength(0); i++)
        {
            for (int j = 0; j < map0.GetLength(1); j++)
            {
                switch (map0[i,j])
                {
                    case 0:
                        Instantiate(wallObject, new Vector3(i, 0, j), Quaternion.identity, transform);
                        break;
                    case 1:
                        Instantiate(planeObject, new Vector3(i, 0, j), Quaternion.identity, transform);
                        break;
                    case 2:
                        Instantiate(planeObject, new Vector3(i, 0, j), Quaternion.identity, transform);
                        gamePlayer = Instantiate(gamePlayerObject, new Vector3(i, 0.5f, j), Quaternion.identity);
                        break;
                    default:
                        break;
                }
            }
        }
    }
    void PlayerMove(List<Vector3> vector3) {
        if (vector3.Count > 0)
        {
            Vector3 pos = new Vector3((int)vector3[0].x, 0.5f, (int)vector3[0].z);
            gamePlayer.transform.position = pos;
            vector3.Remove(vector3[0]);
        }
    }
    void AtarGetPos(Vector3 temp) {
        targetPos.Clear();
        Vector3 pos = new Vector3((int)gamePlayer.transform.position.x, 0.5f, (int)gamePlayer.transform.position.z);
        targetPos = astar.StartMake(map0, gamePlayer.transform.position, temp, testObj);
    }
}

【效果演示】    右边为AStar 辅助方法测试用


【工程代码】

*提示:本博文中的脚本可能会与工程代码中的有些许不同以工程代码中的为准

链接:https://pan.baidu.com/s/1BFvsK84ygT-nXO8r1AQUoA 密码:rji3

猜你喜欢

转载自blog.csdn.net/f980511/article/details/80643247