unity:完成A星寻路算法的基本实现

参考了视频来实现A星寻路算法,居然还花费了2天时间,每次只要一动手就会出现无数的坑,对自己产生了深深的怀疑,故决定写博客记录下来。

参考视频:https://www.bilibili.com/video/BV147411u7r5?t=477&p=4

代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using SGF;
using UnityEngine.Tilemaps;

//测试A星寻路算法

public class TestAstar : MonoBehaviour
{
    public Tilemap tilemap;
    public Tile tile0;
    public Tile tile1;
    public Tile tile2;
    public List<AStar> PathList = new List<AStar>();
    public List<GameObject> path = new List<GameObject>();
    // Start is called before the first frame update
    void Start()
    {
        AStarManeger aStarManeger = new AStarManeger();
        aStarManeger.InitAStarManeger(new Vector2(20,20),tilemap,tile0);
        PathList = aStarManeger.FindPath(aStarManeger.Nodes[5,2], aStarManeger.Nodes[12, 15]);
        
        tilemap.SetTile(new Vector3Int(5,2,0), tile1);
        tilemap.SetTile(new Vector3Int(12, 15, 0), tile1);
        for (int i=0;i<PathList.Count;i++)
        {
            tilemap.SetTile(new Vector3Int((int)PathList[i].Postion.x, (int)PathList[i].Postion.y, 1), tile2);
            Debug.Log(PathList[i].Postion);
        }
        


    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
//格子类型
public enum E_Node_Type
{
    Walk,
    Stop,
}

public class AStar
{
    public Vector2 Postion;
    public float f;
    public float g;
    public float h;
    public AStar parent;
    public E_Node_Type Type;
    public AStar(Vector2 postion,E_Node_Type type)
    {
        Postion = postion;
        Type = type;
    }
}

public class AStarManeger : Singleton<AStarManeger>
{
    public AStar[,] Nodes;//所有寻路格子
    private List<AStar> OpenList = new List<AStar>();
    private List<AStar> CloseList = new List<AStar>();

    public Vector2 MapSize;

    public void InitAStarManeger(Vector2 mapSize, Tilemap tilemap, Tile tile)
    {
        //生成地图,生成寻路格子
        MapSize = mapSize;
        Nodes = new AStar[(int)mapSize.x, (int)mapSize.y];
        for (int i = 0; i < mapSize.x; i++)
        {
            for (int j = 0; j < mapSize.y; j++)
            {
                Nodes[i, j] = new AStar(new Vector2(i, j), Random.Range(0, 10) < 4 ? E_Node_Type.Stop : E_Node_Type.Walk) { f = 10000, g = 0, h = 0, parent = null };
                if ((i == 5 && j == 2) || (i == 12 && j == 15))
                {
                    Nodes[i, j].Type = E_Node_Type.Walk;
                }
                if (Nodes[i, j].Type == E_Node_Type.Walk)
                {
                    tilemap.SetTile(new Vector3Int(i, j, 0), tile);
                }
                else
                {
                    tilemap.SetTile(new Vector3Int(i, j, 0), null);
                }
            }
        }
    }

    public List<AStar> FindPath(AStar start, AStar end)
    {
        OpenList.Clear();
        CloseList.Clear();
        //判断起点和终点是否是阻挡格子
        if (start.Type == E_Node_Type.Stop|| end.Type == E_Node_Type.Stop)
        {
            return null;
        }
        //判断起点和终点是否在地图范围内
        if (0 < start.Postion.x && start.Postion.x < MapSize.x && 0 < start.Postion.y && start.Postion.y < MapSize.y)
        {

        }
        else
        {
            this.LogWarning("起点不在地图范围内,MapSize:{0},{1}    Start:{2},{3}", MapSize.x, MapSize.y, start.Postion.x, start.Postion.y);
            return null;
        }
        if (0 < end.Postion.x && end.Postion.x < MapSize.x && 0 < end.Postion.y && end.Postion.y < MapSize.y)
        {

        }
        else
        {
            this.LogWarning("终点不在地图范围内,MapSize:{0},{1}    Start:{2},{3}", MapSize.x, MapSize.y, start.Postion.x, start.Postion.y);
            return null;
        }
        //添加起点到开启列表
        OpenList.Add(start);
        var point = start;
        //寻路循环
        while (point.Postion != end.Postion)
        {
            //添加当前格子四周的格子到开启列表
            AroundToOpenList4(point,start, end);

            //死路判断(开启列表是否为空)
            if (OpenList.Count == 0)
            {
                Debug.Log("死路");
                break;
            }

            //将开启列表排序并将f最小的格子放入关闭列表
            OpenList.Sort(SortOpenList);
            var min = OpenList[0];
            point = min;           //min.parent = point;//错误代码,二次赋值父物体,造成回溯时错误,出现多余路径

            //寻路完成判断
            if (min == end)
            {
                break;
            }
            CloseList.Add(min);
            OpenList.RemoveAt(0);
        }

        //回溯最终路径
        var aStar = end;
        var PathList = new List<AStar>();
        while (aStar.parent != null)
        {
            PathList.Add(aStar);
            aStar = aStar.parent;
        }
        PathList.Reverse();
        return PathList;
    }

    int SortOpenList(AStar a, AStar b)
    {
        if (a.f >= b.f)
        {
            return 1;
        }
        else
        {
            return -1;
        }
    }

    void AddToOpenList(int i, int j, float g, AStar father,AStar start, AStar end)
    {
        //判断是否在地图外
        if (i < 0 || j < 0 || i >= MapSize.x || j >= MapSize.y)
        {
            return;
        }

        //判断是否阻挡
        if (Nodes[i, j].Type == E_Node_Type.Stop)
        {
            return;
        }
        //判断是否是起点
        if (Nodes[i,j]==start)
        {
            return;
        }
        
        //判断是否在开启列表和关闭列表
        if (OpenList.Contains(Nodes[i, j]) || CloseList.Contains(Nodes[i, j]))
        {
            return;
        }
        Nodes[i, j].parent = father;
        Nodes[i, j].g = g;
        Nodes[i, j].h = Mathf.Abs((Nodes[i, j].Postion - end.Postion).x) + Mathf.Abs((Nodes[i, j].Postion - end.Postion).y);
        Nodes[i, j].f = Nodes[i, j].g + Nodes[i, j].h;
        OpenList.Add(Nodes[i, j]);
    }

    //添加周围8格到开启列表
    void AroundToOpenList8(AStar point,AStar start,AStar end)
    {
        AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y - 1, 1.4f, point, start, end);
        AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y, 1, point, start, end);
        AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y + 1, 1.4f, point, start, end);
        AddToOpenList((int)point.Postion.x, (int)point.Postion.y - 1, 1, point, start, end);
        AddToOpenList((int)point.Postion.x, (int)point.Postion.y + 1, 1, point, start, end);
        AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y - 1, 1.4f, point, start, end);
        AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y, 1, point, start, end);
        AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y + 1, 1.4f, point, start, end);
    }

    //添加周围4格到开启列表
    void AroundToOpenList4(AStar point,AStar start,AStar end)
    {
        AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y, 1, point, start, end);
        AddToOpenList((int)point.Postion.x, (int)point.Postion.y - 1, 1, point, start, end);
        AddToOpenList((int)point.Postion.x, (int)point.Postion.y + 1, 1, point, start, end);
        AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y, 1, point, start, end);
    }

}

实现效果:

  • 死路
  • 只能从x,y方向走
  • 可以从斜向方向走
  •  

     

总结:因为不是完全照着视频来写,而是看了原理后自己实现,还是暴露出来了自己的很多问题

  1. 一开始,在初始化寻路格子时将f的值设定为了0,并且将起点格子加入了开启列表,而起点格子的f在之后不会再改变,导致在对开启列表排序时起点排在了最小位置,导致对起点的父物体进行了赋值,最终在回溯时形成了死循环,导致内存溢出。解决方法是,不要把起点加入开启列表,或者将起点的f值设的尽量大;
  2. 对开启列表中的f最小的格子的父物体进行了二次赋值,造成回溯时错误,出现多余路径;(这里是对A星寻路的最终路径回溯的原理没有完全理解导致的)。
posted @ 2021-01-09 00:11  墨色-星辰  阅读(408)  评论(0编辑  收藏  举报