【Unity】一篇文章搞定AStar(A*)算法

这篇具有很好参考价值的文章主要介绍了【Unity】一篇文章搞定AStar(A*)算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

AStar(A*)算法,是一种在静态网格中求解最短路径直接有效的搜索方法。在游戏开发中,A*算法常应用于部分RPG游戏和策略战棋类游戏。对于Unity开发者来说,掌握A*算法也是十分有必要的。不过在了解A*算法之前,有必要先回顾一下深度优先算法(DFS)、广度优先算法(BFS)以及迪杰斯特拉算法(Dijkstra),这是理解和掌握A*算法的必要基础。

深度优先算法(DFS)

所谓深度优先,通俗一点解释就是:先选择一个方向头也不回的一条路走到黑,再回头从另一个方向又一条路走到黑,如此往复直到走遍整张图。

算法的基本流程是:

  1. 获取给定起点周围未访问的邻接节点
  2. 对这些邻接节点进行递归,直到所有节点访问完毕
private void StartDFS(int x, int y, bool[] visits = null)
{
    if (visits == null)
    {
        visits = new bool[map.Length * map[0].Length];
    }

    if (x >= 0 && x <= map.Length && y >= 0 && y <= map[0].Length)
    {
        int index = x + y * map.Length + 1;

        if (!visits[index - 1])
        {
            visits[index - 1] = true;
            Debug.Log("(x:+" + x + ",y:" + y + ")");
            StartDFS(x - 1, y, visits);//左边的邻接节点
            StartDFS(x + 1, y, visits);//右边的邻接节点
            StartDFS(x, y - 1, visits);//下边的邻接节点
            StartDFS(x, y + 1, visits);//上边的邻接节点
        }
    }
}

        【Unity】一篇文章搞定AStar(A*)算法,Unity3D,算法,unity,c#,游戏引擎

上面我的访问优先级是左、右、下、上。

为了更好地进行理解,可以调换一下顺序,比如:上、右、下、左

StartDFS(x, y + 1, visits);//上
StartDFS(x + 1, y, visits);//右
StartDFS(x, y - 1, visits);//下
StartDFS(x - 1, y, visits);//左

【Unity】一篇文章搞定AStar(A*)算法,Unity3D,算法,unity,c#,游戏引擎

广度优先算法(BFS)

而广度优先顾名思义就是优先访问周围的邻接节点,像水流一样慢慢的向外扩张,直到走遍整张图。

算法的基本流程是:

  1. 把给定起点入队
  2. 从队列中出队一个点
  3. 获取该点周围邻接的未访问节点,并逐个入队
  4. 重复2,3步骤直到所有点访问完毕
private void StartBFS(int x, int y)
{
    bool[] visits = new bool[map.Length * map[0].Length];
    Queue<int> bfs = new Queue<int>();

    int index = x + y * map.Length + 1;
    bfs.Enqueue(index);
    visits[index - 1] = true;

    while (bfs.Count > 0)
    {
        int currIndex = bfs.Dequeue();
        int currX = (currIndex - 1) % map.Length;
        int currY = (currIndex - 1) / map.Length;

        m_QueuePoints.Enqueue(currIndex);

        AddPoint(currX - 1, currY, bfs, visits);
        AddPoint(currX + 1, currY, bfs, visits);
        AddPoint(currX, currY - 1, bfs, visits);
        AddPoint(currX, currY + 1, bfs, visits);
    }
}

private void AddPoint(int x, int y, Queue<int> bfs, bool[] visits)
{
    if (x >= 0 && x <= map.Length && y >= 0 && y <= map[0].Length)
    {
        int index = x + y * map.Length + 1;

        if (!visits[index - 1])
        {
            bfs.Enqueue(index);
            visits[index - 1] = true;
        }
    }
}

【Unity】一篇文章搞定AStar(A*)算法,Unity3D,算法,unity,c#,游戏引擎        

迪杰斯特拉算法(Dijkstra)

然而不管是深度优先(DFS)还是广度优先(BFS)都只是在逐个遍历一张图,当图中有两点A和B并且要计算出从A点到B点的最短路线时应该如何去规划?于是Dijkstra提出了一种最短路径算法。

简单来说,Dijkstra算法就是在广度优先(BFS)的基础上,优先选择移动消耗最低的节点,并在广度优先(BFS)的遍历过程中逐步累加该点周围邻接节点的移动消耗,直到找到目标点为止。

算法的基本流程是:

  1. 把给定起点入队,设置该点移动消耗为0
  2. 出队一个点,获取该点周围可以移动的邻接节点,然后检测这些邻接节点是否被访问过
  3. 若已经被访问过,检测当前点移动到该邻接节点的移动消耗,是否比该邻接节点的移动消耗更低,并对该邻接节点的移动消耗进行更新
  4. 若没有访问过,则累加该邻接节点的移动消耗
  5. 从所有节点中选择一个移动消耗最低并没有被访问过的点并把该点入队
  6. 重复2,3,4,5直到找到目标点。
private void StartDijkstra(int x, int y)
{
    Queue<int> dijkstra = new Queue<int>();
    bool[] visits = new bool[m_MapWidth * m_MapHeight];//访问列表
    int[] dis = new int[m_MapWidth * m_MapHeight];//所有点的移动消耗

    for (int i = 0; i < dis.Length; i++)//初始为无穷大
    {
        dis[i] = int.MaxValue;
    }

    int currNode = x + y * m_MapWidth + 1;
    dis[currNode - 1] = 0;//起点的移动消耗为0
    dijkstra.Enqueue(currNode);//起点入队

    while (dijkstra.Count > 0)
    {
        currNode = dijkstra.Dequeue();
        m_QueuePoints.Enqueue(currNode);

        if (currNode == m_MapWidth * m_MapHeight)//目标节点(这里设置第一个点为起点,最后一个点为终点)
        {
            break;
        }

        visits[currNode - 1] = true;

        int currX = (currNode - 1) % m_MapWidth;
        int currY = (currNode - 1) / m_MapWidth;

        FindDestNode(currX, currY + 1, visits, dis[currNode - 1], dis, m_Nodes[currNode - 1]);
        FindDestNode(currX, currY - 1, visits, dis[currNode - 1], dis, m_Nodes[currNode - 1]);
        FindDestNode(currX - 1, currY, visits, dis[currNode - 1], dis, m_Nodes[currNode - 1]);
        FindDestNode(currX + 1, currY, visits, dis[currNode - 1], dis, m_Nodes[currNode - 1]);

        int minDisNode = -1;
        int tempDis = int.MaxValue;

        for (int j = 0; j < dis.Length; j++)//从所有节点中选择一个移动消耗最低且没有被访问过的点
        {
            if (!visits[j] && dis[j] < tempDis)
            {
                minDisNode = j;
                tempDis = dis[j];
            }
        }

        if (minDisNode >= 0)
        {
            dijkstra.Enqueue(minDisNode + 1);//入队
        }
    }

    for(int i = 0; i < dis.Length; i++)
    {
        if (visits[i])
        {
            Debug.Log(dis[i]);
        }
    }
}

private void FindDestNode(int x, int y, bool[] visits, int currDis, int[] dis, Node currNode)
{
    if (x >= 0 && x < m_MapWidth && y >= 0 && y < m_MapHeight)
    {
        int index = x + y * m_MapWidth + 1;

        if (m_MapData[index - 1] != -1)
        {
            if (!visits[index - 1])//没有访问过
            {
                dis[index - 1] = currDis + m_Map[index - 1];//累加移动消耗
            }
            else if (dis[index - 1] > currDis + m_Map[index - 1]))//已访问,但当前路径的移动消耗更低
            {
                dis[index - 1] = currDis + m_Map[index - 1];//更新移动消耗
            }
        }
    }
}

以上代码虽然可以计算出从给定起点到终点的最少移动消耗,但仍然无法获取移动的路线,为了获得移动路线,要在此基础上增加回溯。

简单粗暴的方法就是使用一个链表把走过的路线链起来,等算法结束再从终点往起点回溯即可得到移动路线。

先声明一个链表

class Node
{
    public int index;
    public Node parent;
    public Node(int index, Node parent)
    {
        this.index = index;
        this.parent = parent;
    }
}

m_Nodes = new Node[m_MapWidth * m_MapHeight];

在处理移动消耗时把当前点和邻接节点进行链接

if (!visits[index - 1])
{
    dis[index - 1] = currDis + m_Map[index - 1];
    m_Nodes[index - 1] = new Node(index, currNode);
}
else if (dis[index - 1] > currDis + m_Map[index - 1])
{
    dis[index - 1] = currDis + m_Map[index - 1];
}

在算法结束后增加回溯

Node node = m_Nodes[currNode - 1];

while (node != null)
{
    m_QueuePath.Enqueue(node.index);
    node = node.parent;
}

【Unity】一篇文章搞定AStar(A*)算法,Unity3D,算法,unity,c#,游戏引擎

AStar(A*)算法

终于进入了本篇的正题。其实A*算法就是对Dijkstra算法的优化,在Dijkstra中每一步只计算移动消耗,这导致在向终点递进的过程中访问到许多无用节点,当地图中没有障碍时,甚至可能要把所有节点全访问到才得出结果。这显然不是一种最优解。

【Unity】一篇文章搞定AStar(A*)算法,Unity3D,算法,unity,c#,游戏引擎

于是A*算法在Dijkstra算法的基础上引入一个当前点到终点的距离H,然后使F = 移动消耗G+距离H,也就是A*大名鼎鼎的F=G+H公式;再引入一个Open列表和Close列表,每次出队时把该节点从Open列表中移除并加入到Close列表,每次进行节点筛选时只从Open列表中选择一个F最小的节点入队。这使得A*算法能在访问更少节点的情况下找到通往目标点的最短路径。

算法的基本流程是:

  1. 把给定起始点入队,该点G = 0,计算该点到终点的距离H后使该点的F=G+H
  2. 把起始点加入Open列表
  3. 出队一个节点,并把该节点从Open列表中移除,再加入Close列表中
  4. 获取当前节点周围可以移动的邻接节点,并检测这些邻接节点是否在Open列表中
  5. 若已存在于Open列表,检测当前点移动到该邻接节点的移动消耗,是否比该邻接节点的移动消耗更低,并对该邻接节点的移动消耗G进行更新,重新计算该点的F=G+H
  6. 若不在则累加该邻接节点的移动消耗即G = G+1,计算该点到终点的距离H,使该点的F=G+H,然后加入到Open列表中
  7. 从Open列表中选择一个F值最低的节点入队
  8. 重复3,4,5,6,7直到Close列表中存在目标点
class Node : IComparable
{
    public int index; //节点编号
    public Node parent;
    public float f;
    public float g;
    public float h;
    public bool isOpen;
    public Node(int index, Node parent, float g, float h)
    {
        this.index = index;
        this.parent = parent;
        this.g = g;
        this.h = h;
        this.f = g + h;
        this.isOpen = true;
    }

    public int CompareTo(object obj)
    {
        if (obj == null)
        {
            return -1;
        }

        Node node = obj as Node;

        if (node.f == this.f)
        {
            return this.g > node.g ? -1 : 1;
        }

        return this.f > node.f ? 1 : -1;
    }
}

private void StartAStar(int xFrom, int yFrom, int xTo, int yTo)
{
    Queue<Node> astar = new Queue<Node>();
    List<Node> openList = new List<Node>();

    int indexFrom = xFrom + yFrom * m_MapWidth + 1;
    int indexTo = xTo + yTo * m_MapWidth + 1;

    astar.Enqueue(new Node(indexFrom, null, 0, GetDistance(indexFrom, indexTo)));

    Node currNode = null;

    while (astar.Count > 0)
    {
        currNode = astar.Dequeue();
        m_QueuePoints.Enqueue(currNode);

        if (currNode.index == indexTo)
        {
            break;
        }

        currNode.isOpen = false;

        if (!openList.Contains(currNode))
        {
            openList.Add(currNode);
        }

        Tiled tiled = TiledMapMgr.instance.GetGridByIndex(currNode.index);
        tiled.txtRightBottom.text = currNode.h.ToString();
        tiled.txtLeftTop.text = currNode.f.ToString();

        int currX = (currNode.index - 1) % m_MapWidth;
        int currY = (currNode.index - 1) / m_MapWidth;

        FindDestNode(currX, currY + 1, xTo, yTo, currNode, openList);
        FindDestNode(currX, currY - 1, xTo, yTo, currNode, openList);
        FindDestNode(currX - 1, currY, xTo, yTo, currNode, openList);
        FindDestNode(currX + 1, currY, xTo, yTo, currNode, openList);

        Node minDistanceNode = openList.FindAll(node => node.isOpen).Min();

        if (minDistanceNode != null)
        {
            astar.Enqueue(minDistanceNode);
        }
    }

    while (currNode != null)
    {
        m_QueuePath.Enqueue(currNode.index);
        currNode = currNode.parent;
    }
}

private void FindDestNode(int x, int y, int xTo, int yTo, Node currNode, List<Node> openList)
{
    if (x >= 0 && x < m_MapWidth && y >= 0 && y < m_MapHeight)
    {
        int index = x + y * m_MapWidth + 1;
        int toIndex = xTo + yTo * m_MapWidth + 1;

        if (m_MapData[index - 1] != -1)
        {
            Node temp = openList.Find(obj => obj.index == index);

            if (temp == null)
            {
                temp = new Node(index, currNode, currNode.g + 1, GetDistance(index, toIndex));
                openList.Add(temp);
            }
            else if (temp.isOpen && temp.f > currNode.g + 1 + temp.h)
            {
                temp.g = currNode.g + 1;
                temp.f = temp.g + temp.h;
            }
        }
    }
}

private int GetDistance(int from, int to)//计算起点到终点的距离,使用平方和即可
{
    int xFrom = (from - 1) % m_MapWidth;
    int yFrom = (from - 1) / m_MapWidth;

    int xTo = (to - 1) % m_MapWidth;
    int yTo = (to - 1) / m_MapWidth;

    int xDistance = Mathf.Abs(xTo - xFrom);
    int yDistance = Mathf.Abs(yTo - yFrom);
    return xDistance * xDistance + yDistance * yDistance;
}

【Unity】一篇文章搞定AStar(A*)算法,Unity3D,算法,unity,c#,游戏引擎

不难看出,同样的地图A*算法访问了更少的节点就找到了最短路径。

【Unity】一篇文章搞定AStar(A*)算法,Unity3D,算法,unity,c#,游戏引擎

而去掉障碍后更是直接就找到了终点,效率提高了无数倍。


值得一提的是,当Open列表中存在相同F值的节点时,应选择G值最高的点,这符合A*算法始终朝终点扩张的思想。

结语

以上是对A*算法的一次探究和实现,所有代码和动态效果全部手撸,有错误或者不足欢迎指出。

工程地址:WuWu03/AStar: A*算法Untiy工程 (github.com)https://github.com/WuWu03/AStar

 个人博客

一篇文章搞定AStar(A*)算法 | 幻想乡の红魔馆 (ming-e.space)https://ming-e.space/post/1d428483/文章来源地址https://www.toymoban.com/news/detail-781415.html

到了这里,关于【Unity】一篇文章搞定AStar(A*)算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一篇文章搞定Android权限问题(全版本)

    文章内容如下: 如果你只是想快速的完成你Android权限申请的工作,那么直接上工具PermissionX 如果是想真正的了解Android的权限问题,那么建议你用15分钟通读一下本文。(可以不去实验,收藏以备后用) 首先了解Android版本和SDK的关系,帮助我们分辨后面的权限版本。 其次把最常

    2023年04月20日
    浏览(57)
  • 一篇文章搞定Java中常用集合的排序方法

    目录 Array · 数组 List · 列表 Collections.sort() 简单类型 复杂对象 类 使用Lambda表达式 Stream API Map · 键值对 对 Map 的 Key 进行排序 对 Map 的 Value 进行排序 最近在做算法题的时候,发现排序在大部分题中都不可或缺,今天心血来潮,总结下Java中集合排序常用的方法,基本覆盖了大

    2024年02月09日
    浏览(55)
  • Spring之AOP(带你一篇文章搞定AOP)

    Spring的核心之一:AOP 用的依赖(包括上篇文章讲诉的IOC依赖): AOP:面向切面编程。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能

    2024年02月16日
    浏览(56)
  • 一篇文章搞定什么是nodeJs它和NPM关系与应用

    现在前端的入门门槛越来越高了,不再是单纯 html+css+js ,各种前端框架 层出不穷,各种ui组件库层出不穷。 模块化,打包化,各种工具库层出不穷,前端变成 大前端 ,甚至前端可以搞定整个项目,通过 node 作为服务端api, 这里我们主角就是 nodeJs javaScript是一门脚本语言,

    2024年02月03日
    浏览(48)
  • 一篇文章搞定《实战中的设计模式之Android版》

    其实大多数可能和我一样,在开发项目的累积经验下,和对设计模式隐约的记忆下。 在开发项目的过程中其实已经使用到了设计模式,但是不能自知。 比如:之前开发的基于AI的一个对话IM,里面涉及到了很多的设计模式。但是都是下意识的去使用,甚至连他是那种设计模式

    2024年02月10日
    浏览(51)
  • 一篇文章搞定《动手学深度学习》-(李沐)PyTorch版本的所有内容

    目录 目录 简介 阅读指南 1. 深度学习简介 2. 预备知识 3. 深度学习基础 4. 深度学习计算 5. 卷积神经网络 6. 循环神经网络 7. 优化算法 8. 计算性能 9. 计算机视觉 10. 自然语言处理 环境 参考(大家可以在这里下载代码) 原书地址(大家可以在这里阅读电子版PDF内容) 引用 阅读

    2023年04月24日
    浏览(49)
  • OSPF技术连载16:DR和BDR选举机制,一篇文章搞定!

    你好,这里是网络技术联盟站。 在计算机网络中,开放最短路径优先(Open Shortest Path First,OSPF)是一种广泛使用的内部网关协议(Interior Gateway Protocol,IGP),用于在大型网络中实现路由选择。在OSPF网络中,当一个OSPF区域内有多个路由器时,为了减少链路状态数据库(Link

    2024年02月07日
    浏览(46)
  • 【仿真+实测】一篇文章搞定RC延迟电路 1.延迟开启 2.快速泄放 3.精确泄放

     作者:面向搜索,排版:晓宇 微信公众号:芯片之家(ID:chiphome-dy) RC延迟电路 在许多芯片的应用手册中都要求了 对上电时序进行控制 ,在这种场合下我们会经常看到RC延迟,今天我们通过multisim 14.0 对RC延迟计算电路的理论计算进行仿真验证 Multisim软件版本 附上multisi

    2024年02月09日
    浏览(120)
  • 七大 排序算法(一篇文章梳理)

    排序算法是计算机科学中不可或缺的一部分,它们在数据处理、数据库管理、搜索引擎、数据分析等多个领域都有广泛的应用。排序算法的主要任务是将一组数据元素按照某种特定的顺序(如升序或降序)进行排列。本文将对一些常见的排序算法进行详细的介绍和分析,包括

    2024年03月08日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包