unity中A*算法的实现

算法介绍

A*(念做:A Star)算法是一种很常用的路径查找和图形遍历算法。它有较好的性能和准确度。本文在讲解算法的同时也会提供Python语言的代码实现,并会借助matplotlib库动态的展示算法的运算过程。

A*算法最初发表于1968年,由Stanford研究院的Peter Hart, Nils Nilsson以及Bertram Raphael发表。它可以被认为是Dijkstra算法的扩展。

由于借助启发函数的引导,A*算法通常拥有更好的性能。

A*算法通过下面这个函数来计算每个节点的优先级。

其中:

f(n)是节点n的综合优先级。当我们选择下一个要遍历的节点时,我们总会选取综合优先级最高(值最小)的节点。
g(n) 是节点n距离起点的代价。
h(n)是节点n距离终点的预计代价,这也就是A*算法的启发函数。关于启发函数我们在下面详细讲解。

A*算法在运算过程中,每次从优先队列中选取f(n)值最小(优先级最高)的节点作为下一个待遍历的节点。

另外,A*算法使用两个集合来表示待遍历的节点,与已经遍历过的节点,这通常称之为 open_setclose_set

完整的A*算法描述如下:

* 初始化open_set和close_set;
* 将起点加入open_set中,并设置优先级为0(优先级最高);
* 如果open_set不为空,则从open_set中选取优先级最高的节点n:
    * 如果节点n为终点,则:
        * 从终点开始逐步追踪parent节点,一直达到起点;
        * 返回找到的结果路径,算法结束;
    * 如果节点n不是终点,则:
        * 将节点n从open_set中删除,并加入close_set中;
        * 遍历节点n所有的邻近节点:
            * 如果邻近节点m在close_set中,则:
                * 跳过,选取下一个邻近节点
            * 如果邻近节点m也不在open_set中,则:
                * 设置节点m的parent为节点n
                * 计算节点m的优先级
                * 将节点m加入open_set中

启发函数

上面已经提到,启发函数会影响A*算法的行为。

在极端情况下,当启发函数h(n)始终为0,则将由g(n)决定节点的优先级,此时算法就退化成了Dijkstra算法。
如果h(n)始终小于等于节点n到终点的代价,则A*算法保证一定能够找到最短路径。但是当h(n)的值越小,算法将遍历越多的节点,也就导致算法越慢。
如果h(n)完全等于节点n到终点的代价,则A*算法将找到最佳路径,并且速度很快。可惜的是,并非所有场景下都能做到这一点。因为在没有达到终点之前,我们很难确切算出距离终点还有多远。
如果h(n)的值比节点n到终点的代价要大,则A*算法不能保证找到最短路径,不过此时会很快。
在另外一个极端情况下,如果h()n相较于g(n)大很多,则此时只有h(n)产生效果,这也就变成了最佳优先搜索。

关于距离

曼哈顿距离

如果图形中只允许朝上下左右四个方向移动,则启发函数可以使用曼哈顿距离

计算曼哈顿距离的函数如下,这里的D是指两个相邻节点之间的移动代价,通常是一个固定的常数。

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy)
对角距离

如果图形中允许斜着朝邻近的节点移动,则启发函数可以使用对角距离。它的计算方法如下:

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)
欧几里得距离

如果图形中允许朝任意方向移动,则可以使用欧几里得距离。

欧几里得距离是指两个节点之间的直线距离,因此其计算方法也是我们比较熟悉的:

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * sqrt(dx * dx + dy * dy)
完整的A*代码

以下是unity中实现A*寻路的代码,由C#编写


public class Point 
{
    public int x,y;//点的坐标
    public int F, G, H;  //F=G+H;
    public Point parent;  //父节点
    public Point(int x,int y)
    {
        this.x = x;
        this.y = y;
        F = 0;
        G = 0;
        H = 0;
    }
}





public class AStar
{
    private int kCost1 = 10;  //直移动一个单位
    private int kCost2 = 14;   //斜着移动一个单位

    int[,] maze = new int[8, 8];
    List<Point> openList = new List<Point>();  //开启列表
    List<Point> closeList = new List<Point>(); //关闭列表

    public void initAstar(int[,] _maze)
    {
        maze = _maze;
        /*for (int i = 0; i < 20; i++)
        {
            for (int j = 0; j < 20; j++)
            {
                std::cout << maze[i][j] << " ";
            }
            std::cout << std::endl;
        }*/
    }

    int calcG(Point temp_start, Point point)
    {
        int extraG = (Math.Abs(point.x - temp_start.x) + Math.Abs(point.y - temp_start.y)) == 1 ? kCost1 : kCost2;
        int parentG = point.parent == null ? 0 : point.parent.G; //如果是初始节点,则其父节点是空  
        return parentG + extraG;
    }

    int calcH(Point point, Point end)
    {
        return (Math.Abs(end.x - point.x) + Math.Abs(end.y - point.y)) * kCost1;
    }

    int calcF(Point point)
    {
        return point.G + point.H;
    }

    //获取开启列表中最小F
    Point getLeastFpoint()
    {


        if (openList.Count!=0)
        {
            Point resPoint = openList[0];
            foreach(Point  point in openList)
                if (point.F < resPoint.F)
                    resPoint = point;
            return resPoint;
        }
        return null;
    }


    //寻找路径
    Point findPath(Point startPoint, Point endPoint, bool isIgnoreCorner)
    {
        openList.Add(new Point(startPoint.x, startPoint.y)); //置入起点,拷贝开辟一个节点,内外隔离  
        while (openList.Count!=0)
        {
            Point curPoint = getLeastFpoint(); //找到F值最小的点  
            openList.Remove(curPoint); //从开启列表中删除  
            closeList.Add(curPoint); //放到关闭列表  
                                           //1,找到当前周围八个格中可以通过的格子  
            List<Point> surroundPoints = getSurroundPoints(curPoint, isIgnoreCorner);
            foreach (var  target in surroundPoints)
            {
                //2,对某一个格子,如果它不在开启列表中,加入到开启列表,设置当前格为其父节点,计算F G H  
                if (isInList(openList, target)==null)
                {
                    target.parent = curPoint;

                    target.G = calcG(curPoint, target);
                    target.H = calcH(target, endPoint);
                    target.F = calcF(target);

                    openList.Add(target);
                }
                //3,对某一个格子,它在开启列表中,计算G值, 如果比原来的大, 就什么都不做, 否则设置它的父节点为当前点,并更新G和F  
                else
                {
                    //int tempG = calcG(curPoint, target);
                    Point tempPoint = isInList(openList, target);
                    int tempG = calcG(curPoint, target);

                    if (tempG < tempPoint.G)
                    {
                        //cout << "x:" << target->x << "y:" << target->y << "改变后" << tempG << "改变前:" << tempPoint->G <<"CurrPoint:"<<curPoint->x<<curPoint->y<< endl;
                        target.parent = curPoint;
                        //cout << curPoint->x << ":" << curPoint->y << endl;
                        target.G = tempG;
                        target.F = calcF(target);
                        /*tempPoint->parent = curPoint;
                        cout << curPoint->x << ":" << curPoint->y << endl;
                        tempPoint->G = tempG;
                        tempPoint->F = calcF(tempPoint);*/

                    }
                }
                Point resPoint = isInList(openList, endPoint);
                if (resPoint!=null)
                    return resPoint; //返回列表里的节点指针,不要用原来传入的endpoint指针,因为发生了深拷贝  
            }
        }

        return null;
    }


    //开始寻路
    public List<Point> GetPath(Point startPoint, Point endPoint, bool isIgnoreCorner)
    {
        Point result = findPath(startPoint, endPoint, isIgnoreCorner);
        List<Point> path = new List<Point>();
        //返回路径,如果没找到路径,返回空链表  
        while (result!=null)
        {
            path.Add(result);
            result = result.parent;
        }
        path.Reverse();
        openList.Clear();
        closeList.Clear();
        return path;
    }

    //是否在开启列表内
  Point isInList(List<Point> list, Point point) 
   {
        //判断某个节点是否在列表中,这里不能比较指针,因为每次加入列表是新开辟的节点,只能比较坐标  
        foreach (var p in list)
		if (p.x == point.x&&p.y == point.y)
			return p;
	return null;
}

//是否可以到达target
bool isCanreach( Point point, Point target, bool isIgnoreCorner)
{
	if (target.x<0 || target.x> 8 - 1
		|| target.y<0 || target.y> 8 - 1
		|| maze[target.x,target.y] == 1
		|| target.x == point.x&&target.y == point.y
		|| isInList(closeList, target)!=null) //如果点与当前节点重合、超出地图、是障碍物、或者在关闭列表中,返回false  
		return false;
	else
	{
		if (Math.Abs(point.x - target.x) + Math.Abs(point.y - target.y) == 1) //非斜角可以  
			return true;
		else
		{
			//斜对角要判断是否绊住  
			if (maze[point.x,target.y] == 0 && maze[target.x,point.y] == 0)
				return true;
			else
				return isIgnoreCorner;
		}
	}
}

//获取周围8个格子,可以到达的格子,并且没有在关闭列表中
List<Point> getSurroundPoints(Point point, bool isIgnoreCorner)
{
	List<Point> surroundPoints = new List<Point>();

	for (int x = point.x - 1; x <= point.x + 1; x++)
		for (int y = point.y - 1; y <= point.y + 1; y++)
			if (isCanreach(point, new Point(x, y), isIgnoreCorner))
				surroundPoints.Add(new Point(x, y));

	return surroundPoints;
}
}

我们在unity中制作一个简单的地图

在这里插入图片描述

给小球添加脚本

 public int[,] maze =
    { 
    { 0,0,0,0,0,0,0,0},
    { 0,0,0,0,0,0,0,0},
    { 0,1,0,1,0,0,1,0},
    { 0,0,0,1,0,1,0,0},
    { 0,0,0,1,0,0,0,0},
    { 0,0,0,1,0,1,0,0},
    { 0,1,0,0,0,0,0,0},
    { 0,0,0,0,0,0,0,0}
    };

    int num = 0;
    bool isFindPath = false;  //是否在寻路
    List<Point> path = new List<Point>();
    public AStar astar = new AStar();

    public GameObject red_click;
    //设置起始和结束点
    Point start = new Point(0, 0);
    Point end = new Point(7, 7);


    // Start is called before the first frame update
    private void Awake()
    {
        astar.initAstar(maze);
        A*算法找寻路径
        //path = astar.GetPath(start, end, false);
        //isFindPath = true;
        //foreach (var p in path)
        //Debug.Log("(" + p.x + "," + p.y + ")");
    }

    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            bool collider = Physics.Raycast(ray, out hit);
            if (collider)
            {

                if (hit.collider.tag == "Plane")
                {
                    path.Clear();
                    num = 0;
                    start = new Point((int)Mathf.Round(transform.position.z), (int)Mathf.Round(transform.position.x));
                    end = new Point((int)Mathf.Round(hit.point.z), (int)Mathf.Round(hit.point.x));
                    isFindPath = true;
                    path = astar.GetPath(start, end, false);
                    //Debug.Log((int)hit.point.z+":"+ (int)hit.point.x);
                    Instantiate(red_click, new Vector3((int)Mathf.Round(hit.point.x), 0, (int)Mathf.Round(hit.point.z)), Quaternion.identity);
                }
            }
        }
       


        if (!isFindPath) return;
        if(num>=path.Count)
        {
            num = 0;
            path.Clear();
            isFindPath = false;
            return;
        }
        if(transform.position.x==path[num].y&&transform.position.z==path[num].x)
        {
            num++;
        }
        else
        {
            transform.position = Vector3.MoveTowards(transform.position, new Vector3(path[num].y, 0, path[num].x), 1.3f*Time.deltaTime);
        }
    }
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值