Unity中利用A*算法实现简单寻路

来源:互联网 发布:陈克礼枪毙 知乎 编辑:程序博客网 时间:2024/06/15 09:35

A*算法理解

A*算法目的在于找到一条能够到达目的地的最短路线,其核心思想是广度优先搜索,以当前格子为中心,遍历周围的格子,通过计算每个格子到达目标点的距离,找出一条能够到达且消耗最小的路径。A*算法中的公式很简单:F =G+H,我是这样理解的:
如图,假设角色需要从A点移动到B点。G代表从A点移动到自身相邻的八个点(A1到A8)所消耗的体力值,因为正方向比斜方向移动的距离短,所以正方向消耗10点体力(A到A5),斜方向消耗14点体力(A到A3);H代表从当前点到终点的横纵坐标之差的绝对值,即曼哈顿距离(从A到B为40,从A5到B为30)。也可以这样理解:G为单次行走消耗的体力值,H表示到达终点还将需要消耗的体力值。当某个点受到阻挡时,需返回到之前的点重新计算。这样一来,地图上每一个格子所代表的点到达终点需要消耗的体力值都被量化,下一步我们要做的事情就是找到一条消耗体力值F最小的路径。
A*示意图

Unity中模拟A*算法

1.定义格子类

格子代表着二维地图中的坐标点,且每个格子有F、G、H值。因此,格子可以用一个类来表示:

public class Node {//坐标public int X, Y;//记录消耗值public int F, G, H;//父节点public Node Parent;//自动计算H值,估计值public void CalculateH(Vector2 end){    this.H = (int)(10 * (Mathf.Abs(end.x - X) + Mathf.Abs(end.y - Y)));}//自动计算F值public void CalculateF(){    this.F = G + H;}public Node(int x, int y, Node parent = null){    this.X = x;    this.Y = y;    this.Parent = parent;           }public Node(Vector2 point, Node parent = null){    this.X = (int)point.x;    this.Y = (int)point.y;    this.Parent = parent;}}

2.生成简易的格子地图

由于格子代表一个个坐标点,因此可以利用txt文档存储地图坐标信息,用0表示起始点,9表示目标点,2表示障碍物,1表示可行走的格子。因此txt中存放数据为:
1111111
1112111
1012191
1112111
1111111
利用StreamReader类加载数据,代码如下:

byte[,] mapData;       //用来接受地图数据int rowCount;          //地图行数int colCount;          //地图列数string mapPath;        //txt文件存放路径void LoadMapData(string path){    StreamReader sr = new StreamReader(mapPath);    string content = sr.ReadToEnd();    string[] rows = content.Split('\n');    rowCount = rows.Length;    colCount = rows[0].Trim().Length;    mapData = new byte[rowCount, colCount];    for (int i = 0; i < rowCount; i++)    {        for (int j = 0; j < colCount; j++)        {            mapData[i, j] = byte.Parse(rows[i][j].ToString());        }    }    sr.Close();    sr.Dispose();}

加载完地图数据后,在场景中创建出地图(用cube表示格子,因此提前制作好三种cube的预制体)

void CreatMap(){    //生成蓝色Cube表示起始点    GameObject bluePrefab = Resources.Load<GameObject>("BluePrefab");    //生成红色Cube表示终点    GameObject redPrefab = Resources.Load<GameObject>("RedPrefab");    //生成黑色Cube表示障碍物    GmaObject blackPrefab = Resources.Load<GameObject>("BlackPrefab");    //生成白色Cube表示普通格子    GmaObject blackPrefab = Resources.Load<GameObject>("WhitePrefab");    //for循环遍历mapData中的数据,并根据数值生成相应的Cube    for (int i = 0; i < rowCount; i++)    {        for (int j = 0; j < colCount; j++)        {            GameObject grid = null;            switch ((int)mapData[i, j])            {                                    case 0:                    grid = Instantiate(bluePrefab);                    break;                case 1:                    grid = Instantiate(WhitePrefab);                    break;                case 2:                    grid = Instantiate(blackPrefab);                    break;                case 9:                    grid = Instantiate(redPrefab);                    break;            }            grid.transform.position = GetPosition(i,j);            grid.transform.SetParent(transform);            grid.transform.localScale = Vector3.one * 0.8f;            grid.name = i + "_" + j;        }            }}

生成后的地图如下:
这里写图片描述

3.实现A*算法

Vector2 start;      //起始点Vector2 endd;       //终止点//某点的八个相邻方向Vector2[] dirs = new Vector[]{Vector2.up,Vector2.down,Vector2.left,Vector2.right,new Vector2(1,1),new Vector2(1,-1),new Vector2(-1,1),new Vector2(-1,-1)};List<Node> list = new List<Node>();    //待访问的节点列表List<Node> visited = new List<Node>(); //已访问的节点列表Stack<Vector2> path = new Stack<Vector2>();//行走路径//A*算法void AStar(){    GetStartAndEnd();    //创建起始节点进入队列    Node root = new Node(start);    list.Add(root);    //开始检索list中的节点以及周围八个方向的点(广度优先算法)    while(list.Count>0)    {        //先对list中的节点按照F值排序        list.Sort(NodeSort);        //取F值最小的节点作为起始节点        Node node = list[0];        list.Remove(node);        visited.Add(node);        //对节点周围八个方向的点进行遍历        for(int i=0;i<dirs.Length;i++)        {            Vector2 point;            point.x=node.X+dirs[i].x;            point.y=node.Y+dirs[i].y;            //判断该点能否到达            if(IsOk(point))            {                Node n = new Node(point);                //计算该点的G、H、F值                n.G = i>3?(node.G+14):(node.G+10);                n.CalculateH(end);                n.CalculateF();                list.Add(n);                if(point == end)                {                    Debug.Log("Find!")                    Node p = n;                    //遍历父节点(来时的路径)                    while(p!=null)                    {                        path.Push(new Vector2(p.x,p.y));                        p = p.parent;                    }                    return;                }            }        }}//获取起始点和终止点void GetStartAndEnd(){    for(int i=0;i<rowCount;i++)    {        for(int j=0;j<colCount;j++)        {            //0表示起始点            if(mapData[i,j]==0)            {                start.x = i;                start.y = j;            }            //9表示终止点            if(mapData[i,j]==9)            {                start.x = i;                start.y = j;            }        }    }}//集合中的节点按照F值排序void NodeSort(Node node1,Node node2){    if (x.F > y.F)        return 1;    else if (x.F < y.F)        return -1;    else        return 0;}//判断该节点是否能够到达bool IsOk(Vector2 point){    //越界    if (point.x < 0 || point.x >= rowCount || point.y < 0 || point.y >= colCount)    {        return false;    }    //障碍物    if (mapData[(int)point.x, (int)point.y] == 2)    {        return false;    }    //节点已访问    for (int i = 0; i < visited.Count; i++)    {        if (visited[i].X == point.x && visited[i].Y == point.y)        {            return false;        }                }    //节点在List中    for (int i = 0; i < list.Count; i++)    {        if (list[i].X == point.x && list[i].Y == point.y)        {            return false;        }    }    return true;}

4.创建角色进行寻路测试

//用于测试的移动角色Transform player;        //角色Vector3 target;          //当前移动的目标点Vector3 moveDir;         //当前移动的方向void Awake(){    mapPath=Application.dataPath+"Map.txt";    LoadMapData(mapPath);    CreateMap();}void Start(){    AStar();    player = GameObject.CreatePrimitive(PrimitiveType.Capsule).transform;    player.transform.position = GetPosition(start);    target = getPosition(path.Peek());}void Update(){    moveDir = (target - player.position).normalized;    player.Translate(moveDir*Time.deltaTime);    if(Vector3.Distance(player.position,target)<0.1f&&path.Count>0)    {        path.Pop();        if(path.Count == 0)        {            this.enabled = false;        }        else        {            target = GetPosition(path.Peek());        }               }       }//获取横纵坐标值获取世界坐标Vector3 GetPosition(int i,int j){    //j控制内层循环,因此关联x轴坐标(表示列数)    float x = j - colCount * 0.5f;    //i控制外层循环,因此关联z轴坐标(表示行数)    float y = rowCount*0.5f - i;    return new Vector3(x, 0, y);}//根据vector2坐标获取世界坐标Vector3 GetPosition(Vector2 point){    float x = point.y - colCount * 0.5f;    float y = rowCount * 0.5f - point.x ;    return new Vector3(x, 0, y);}

运行结果如下:
这里写图片描述

0 0
原创粉丝点击