【Unity】多种寻路算法实现 —— BFS,DFS,Dijkstra,A*
本实验寻路算法均基于网格实现,整体称呼为Grid,单个瓦片称之为Tile
考虑程序处理的简洁性,所有算法使用同一种Tile,且权值点,A*所需的记录数值也全部放在Tile中记录
前排贴上代码仓库链接:
GitHub - Sirokus/Unity-Pathfinding-Project: 内置了BFS,DFS,GBFS,Dijkstra,A*有权,A*无权的寻路函数,以及一个能够控制大小,障碍物,起始点和终点的网格系统,仅供个人了解原理使用
数据结构
首先是Tile的数据结构
已剪除不必要的代码保证逻辑清晰
public class Tile : MonoBehaviour
{public bool walkable;public bool visited;public Tile pre;public float cost = 1;//A*所需public float G;public float H;public float F => G + H;public void setWalkable(bool canWalk){walkable = canWalk;}public int GetManhattanDistance(Tile tile){Vector2Int aCoord = Grid.getCoordByTile(this);Vector2Int bCoord = Grid.getCoordByTile(tile);return Mathf.Abs(aCoord.x - bCoord.x) + Mathf.Abs(aCoord.y - bCoord.y);}
}
接着是Grid
鉴于Grid中集中了大量业务代码,此处只贴出部分有用的函数内容
Grid中存储着所有的格子,并提供格子和索引之间的转换,以下是具体方式
public static Vector2Int getCoordByTile(Tile tile)
{return new Vector2Int((int)tile.transform.localPosition.x, (int)tile.transform.localPosition.y);
}
public static Tile getTileByCoord(Vector2Int coord)
{if (coord.x < 0 || coord.y < 0 || coord.x >= Grid.ins.gridSize.x || coord.y >= Grid.ins.gridSize.y)return null;return Grid.ins.tiles[coord.x][coord.y];
}
接着是关键的寻路代码
BFS寻路算法
以下是BFS代码(为了能够运行时观看寻路过程,因此所有寻路代码皆使用协程实现)
BFS的基本思想是从出发点开始,向四周所有可以行走的节点进行访问,并在碰到目标值后停止
BFS是不考虑权值的
IEnumerator BfsPathFindingTask()
{Queue<Tile> queue = new Queue<Tile>(); //遍历队列Dictionary<Tile, Tile> came_from = new Dictionary<Tile, Tile>(); //前驱队列queue.Enqueue(start);came_from[start] = null;while(queue.Count > 0){//访问当前TileTile cur = queue.Dequeue();Vector2Int curCoord = getCoordByTile(cur);//依次访问其四个邻居,若可行走且未被访问过则入队foreach(var d in dir){//获取邻居坐标Vector2Int tmp = curCoord + d;//按坐标获取邻居Tile neighbor = getTileByCoord(tmp);//确保邻居可访问且没有前驱(没有访问过)if (neighbor && neighbor.walkable && !came_from.ContainsKey(neighbor)){if(neighbor != end){neighbor.setColor(Color.black, VisitedColorBlendLerp);}//入队该邻居,标记已访问,记录其父tilequeue.Enqueue(neighbor);came_from[neighbor] = cur;//终止条件if(CheckFindPathComplete(neighbor, came_from))yield break;}}if(ShowProcess)yield return new WaitForSeconds(0.001f / (0.001f * speedMultiple));}
}
DFS寻路算法
我写的这个就是纯搞笑的算法,它会向一个方向一直探测转向直到走无可走再从下一个相邻点进行行走
也就是说这个不是一个启发式算法,目标肯定是可以找到的,但中间要走多少个弯就只有天知道了
(不过review一下发现写的还挺长的)
IEnumerator DfsPathFindingTask(Tile tile, System.Action isDone)
{if(end.visited || !tile){isDone();yield break;}tile.visited = true;Vector2Int cur = getCoordByTile(tile);Tile neighbor = null;float minDis = float.MaxValue;foreach(var d in dir){Vector2Int tmp = cur + d;Tile tmpTile = getTileByCoord(tmp);if(tmpTile && tmpTile.walkable && !tmpTile.visited){float dis = Vector2.Distance(tmp, endCoord);if(dis < minDis){neighbor = tmpTile;minDis = dis;}}} if(neighbor){if(neighbor != end){neighbor.setColor(Color.black, VisitedColorBlendLerp);}neighbor.visited = true;neighbor.pre = tile;if(neighbor == end){float costCount = neighbor.cost;neighbor = neighbor.pre;List<Tile> path = new List<Tile>();while(neighbor != start){costCount += neighbor.cost;path.Add(neighbor);neighbor = neighbor.pre;}costCount += start.cost;Debug.Log(costCount);path.Reverse();foreach(Tile t in path){t.setColor(Color.green, 0.5f);yield return new WaitForSeconds(0.02f);}yield break;}if(ShowProcess)yield return new WaitForSeconds(0.01f / (0.01f * speedMultiple));bool isdone = false;coroutines.Add(StartCoroutine(DfsPathFindingTask(neighbor, () => { isdone = true; })));yield return new WaitUntil(() => isdone);}else{bool isdone = false;coroutines.Add(StartCoroutine(DfsPathFindingTask(tile.pre, () => { isdone = true; })));yield return new WaitUntil(() => isdone);}isDone();
}
GBFS寻路算法
GBFS是在BFS基础上进行改进的算法,G代表Greedy(贪心),这是一个启发式算法,这意味着其知道终点的位置,并且会一直向理论最靠近终点的位置靠近
这也代表如果其和目标中间有个墙角,其也会先深入墙角再沿着墙走出来找到目标(因为其不考虑已付出代价)
IEnumerator GBfsPathFindingTask(){SortedDictionary<float, LinkedList<Tile>> queue = new SortedDictionary<float, LinkedList<Tile>>(){{0, new LinkedList<Tile>()}};Dictionary<Tile, Tile> came_from = new Dictionary<Tile, Tile>();queue[0].AddLast(start);came_from[start] = null;Vector2Int endCoord = getCoordByTile(end);while(queue.Count > 0){//访问当前TileTile cur = queue.First().Value.First();//当前Tile出队if(queue.First().Value.Count > 1)queue.First().Value.RemoveFirst();elsequeue.Remove(queue.First().Key);//获取当前Tile坐标Vector2Int curCoord = getCoordByTile(cur);//依次访问其四个邻居,若可行走且未被访问过则入队foreach(var d in dir){//计算邻居坐标Vector2Int tmp = curCoord + d;//按坐标获取邻居Tile neighbor = getTileByCoord(tmp);//确保邻居可访问if (neighbor && neighbor.walkable && !came_from.ContainsKey(neighbor)){//计算优先级float priority = Vector2Int.Distance(tmp, endCoord);//入队if(!queue.ContainsKey(priority))queue.Add(priority, new LinkedList<Tile>());queue[priority].AddLast(neighbor);//设置前驱came_from[neighbor] = cur;//终止条件if(CheckFindPathComplete(neighbor, came_from))yield break;}}if(ShowProcess)yield return new WaitForSeconds(0.01f / (0.01f * speedMultiple));}}
Dijkstra寻路算法
该算法不是一个启发式算法,但该算法能够根据不同权值找到最优路径(一定最优),其几乎相当于一个广度优先的有权版本,其会评估路上每一个的已付出代价,并选择付出代价最小的节点进行扩展,当找到一个已访问结点的更优路径时,还会修改其前驱
IEnumerator DijkstraPathFindingTask()
{SortedDictionary<float, LinkedList<Tile>> queue = new SortedDictionary<float, LinkedList<Tile>>(){{0, new LinkedList<Tile>()}};Dictionary<Tile, Tile> came_from = new Dictionary<Tile, Tile>();Dictionary<Tile, float> cost_so_far = new Dictionary<Tile, float>();queue[0].AddLast(start);came_from[start] = null;cost_so_far[start] = 0;while(queue.Count > 0){//访问当前TileTile cur = queue.First().Value.First();//当前Tile出队if(queue.First().Value.Count > 1)queue.First().Value.RemoveFirst();elsequeue.Remove(queue.First().Key);//获取当前Tile坐标Vector2Int curCoord = getCoordByTile(cur);//依次访问其四个邻居,若可行走且未被访问过则入队foreach(var d in dir){//计算邻居坐标Vector2Int tmp = curCoord + d;//按坐标获取邻居Tile neighbor = getTileByCoord(tmp);if(!neighbor)continue;//计算costfloat new_cost = cost_so_far[cur] + neighbor.cost;//可行走,且第一次走或者此为更优路线if (neighbor.walkable && (!cost_so_far.ContainsKey(neighbor) || new_cost < cost_so_far[neighbor])){if(neighbor != end){neighbor.setColor(Color.black, VisitedColorBlendLerp);}//入队if(!queue.ContainsKey(new_cost))queue.Add(new_cost, new LinkedList<Tile>());queue[new_cost].AddLast(neighbor);//设置前驱came_from[neighbor] = cur;//更新costcost_so_far[neighbor] = new_cost;//终止条件if(CheckFindPathComplete(neighbor, came_from))yield break;}}if(ShowProcess)yield return new WaitForSeconds(0.001f / (0.001f * speedMultiple));}
}
A*寻路算法
A*寻路算法是非常著名的寻路算法,其相当于一个混合算法,其存在一个启发函数
即 F = G + H + C
G = 已经付出的代价(类似Dijkstra)
H = 预估到达目标代价(类似GBFS)
C = 用户自定义算法(可用于实现一些A*寻路的特殊偏好,如减少拐点等)
A*的H计算可以计算欧式距离或者曼哈顿距离,通常使用后者,因为计算机计算起来比需要平方根的欧氏距离更快,方便大量计算
Tips:我在算法中,还额外使用了向量的叉乘,使得寻路时会更倾向于扩展朝向目标的点而非扩展更远的点
IEnumerator AStarPathFindingTask()
{SortedDictionary<float, LinkedList<Tile>> openQueue = new SortedDictionary<float, LinkedList<Tile>>(){{0, new LinkedList<Tile>()}};Dictionary<Tile, Tile> preDic = new Dictionary<Tile, Tile>();Dictionary<Tile, float> costDic = new Dictionary<Tile, float>();//用start初始化容器openQueue[0].AddLast(start);preDic[start] = null;costDic[start] = 0;Vector2Int endCoord = getCoordByTile(end);bool wait;while(openQueue.Count > 0){wait = false;//访问当前TileTile cur = openQueue.First().Value.First();//当前Tile出队if(openQueue.First().Value.Count > 1)openQueue.First().Value.RemoveFirst();elseopenQueue.Remove(openQueue.First().Key);//获取当前Tile坐标Vector2Int curCoord = getCoordByTile(cur);//依次访问其四个邻居,若可行走且未被访问过则入队foreach(var d in dir){//计算邻居坐标Vector2Int tmp = curCoord + d;//按坐标获取邻居Tile neighbor = getTileByCoord(tmp);if(!neighbor)continue;//计算邻居的Costfloat new_cost = costDic[cur] + neighbor.cost;//可行走,且第一次走或者此为更优路线if (neighbor.walkable && (!costDic.ContainsKey(neighbor) || new_cost < costDic[neighbor])){if(neighbor != end){neighbor.setColor(Color.black, VisitedColorBlendLerp);}//更新cost(G)costDic[neighbor] = new_cost;//F = G+H,switch中主要是不同的H的计算方式switch(AStarType){case 0:new_cost += Vector2Int.Distance(tmp, endCoord);break;case 1:float dx = Mathf.Abs(tmp.x - endCoord.x);float dy = Mathf.Abs(tmp.y - endCoord.y);new_cost += dx + dy + (Mathf.Sqrt(2) - 2) * Mathf.Min(dx, dy);break;case 2:float dx1 = Mathf.Abs(tmp.x - endCoord.x);float dy1 = Mathf.Abs(tmp.y - endCoord.y);float dx2 = Mathf.Abs(startCoord.x - endCoord.x);float dy2 = Mathf.Abs(startCoord.y - endCoord.y);float cross = dx1 * dy2 - dx2 * dy1;new_cost += neighbor.GetManhattanDistance(end) + (cross < 0 ? (cross + 1) * -2 : cross) * AstarCrossMultiple;break;}//入队if(!openQueue.ContainsKey(new_cost))openQueue.Add(new_cost, new LinkedList<Tile>());openQueue[new_cost].AddLast(neighbor);//记录前驱preDic[neighbor] = cur;//终止条件if(CheckFindPathComplete(neighbor, preDic))yield break;wait = true;}}if(wait && ShowProcess)yield return new WaitForSeconds(0.001f / (0.001f * speedMultiple));}
}
相关文章:
【Unity】多种寻路算法实现 —— BFS,DFS,Dijkstra,A*
本实验寻路算法均基于网格实现,整体称呼为Grid,单个瓦片称之为Tile 考虑程序处理的简洁性,所有算法使用同一种Tile,且权值点,A*所需的记录数值也全部放在Tile中记录 前排贴上代码仓库链接: GitHub - Sir…...
十大游戏设计软件:创意实现的利器
在数字娱乐的多彩世界里,游戏设计无疑是一项充满创意与技术挑战的艺术。随着技术的进步,游戏设计师的手中拥有了一系列强大的工具,它们让想象中的世界得以呈现,让玩家的体验更加丰富和真实。本文将带你走进游戏设计的幕后…...
Pandas高级操作:多级索引、窗口函数、数据透视表等
在数据处理和分析中,pandas库提供了强大的功能,支持从简单到复杂的数据操作。本文将介绍一些pandas的高级操作,包括多级索引(MultiIndex)、窗口函数(Window Functions)、数据透视表与复杂聚合、数据合并与连接、高级数据变换以及时间序列数据的高级处理。 1. 多级索引(…...
mysql源码编译启动debug
对于没有C语言基础的同学来说,想看看源码,在搞定编辑器做debug的时候就被劝退了,发生点啥了,完全看不懂,不知道从哪里入手去做debug;我为了看看 mysql 的 insert buffer 到底存的是索引页还是数据页&#x…...
吴恩达机器学习-C1W3L2-逻辑回归之S型函数
可选实验:逻辑回归 在这个不评分的实验中,你会 探索sigmoid函数(也称为logistic函数)探索逻辑回归;哪个用到了s型函数 import numpy as np %matplotlib widget import matplotlib.pyplot as plt from plt_one_addpt_onclick import plt_one_addpt_onclick from l…...
P-one新增火焰图-为性能测试开启新视野
随着软件业务流程的日益复杂,传统的性能测试方法已经难以满足对性能问题精准定位的需求。测试人员需要一种更加直观、全面的方式来分析软件在运行过程中的性能表现,以便快速准确地找到性能瓶颈并进行优化。因此,我们在性能测试平台P-One中加入…...
CTF-web基础 TCP/UDP协议
传输层协议由TCP/UDP协议组成,来控制信息的传输,二者有什么区别呢,TCP比较靠谱,但是UDP速度比较快一点。 TCP协议 Transmission Control protocol, 三次握手:先给服务器传输询问要发消息,然后…...
sql常用语法总结
SQL(Structured Query Language,结构化查询语言)是一种用于管理和操作关系数据库的标准编程语言。本文用来记录一些接触到的sql语句,随着学习不断进行更新: 选择数据 - SELECT 语句用于从数据库表中检索数据。 SELECT column1, column2 FROM table_name;插入数据 - INSERT…...
实验八 题目描述 从键盘上输入任意一个整数(正负数皆可),判断该整数的绝对值是否为回文数。
实验八 题目描述 从键盘上输入任意一个整数(正负数皆可),判断该整数的绝对值是否为回文数。[提示:取数的绝对值,然后使用用循环语句从该绝对值的末位开始至最高位,重新构造一个数,…...
IsaacLab | Workflow 中 rsl_rl 的 play.py 脚本精读
如是我闻: 在用IsaacLab 做强化学习实验时,回顾已训练好的模型需要调用workflow中的play.py脚本,以下是对rsl_rl的play.py脚本的逐行精读。 1. 版权声明和文件描述 # Copyright (c) 2022-2024, The Isaac Lab Project Developers. # All ri…...
PYTHON专题-(8)我错了该怎么整?
什么是异常处理? 异常处理是一种机制,用于在程序执行期间发生错误或异常时,对发生的异常进行捕获、处理和恢复,以确保程序能够继续执行或正确地终止。异常处理可以包括捕获异常、处理异常,以及执行相应的操作来处理异常…...
【自然资源】设施农业用地的学习梳理
【自然资源】设施农业用地的学习梳理 什么是设施农业用地? 2019年12月17日,自然资源部 、农业农村部印发的《关于设施农业用地管理有关问题的通知》规定:设施农业用地包括农业生产中直接用于作物种植和畜禽水产养殖的设施用地。其中&#x…...
【秋招笔试】24-07-27-OPPO-秋招笔试题(后端卷)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 💡 01.二进制反转游戏 问题描述 K小姐…...
JS 补充内容
一、dir 打印对象 二、获取 html 中的元素 常用的两种方式 其他获取元素的方法 三、 innerText 四、innerHTML 五、修改元素的值 六、鼠标放上去,显示图片的提示文字 img . title 七、获取 N ~ M 之间的随机整数 八、修改属性样式 1. style 2. className 将后面 …...
H5+JS 4096小游戏
主要实现 1.使用WASD或方向按钮控制游戏 2.最高值4096,玩到4096视为胜利 3.随机生成2、4、8方块 4.移动方块 5.合并方块 JS代码干了什么 初始化游戏界面:创建游戏板和控制按钮。 定义游戏相关变量:如棋盘大小、棋盘状态、得分等。 初始化棋…...
常见中间件漏洞(二、WebLogin合集)
目录 二、WebLogic Weblogic介绍 2.1 后台弱口令GetShell 漏洞描述 影响范围 环境搭建 漏洞复现 2.2 CVE-2017-3506 漏洞描述 影响版本 环境搭建 漏洞复现 2.3 CVE-2019-2725 漏洞描述 影响版本 环境搭建 漏洞复现 2.4 CVE-2018-2628 漏洞描述 漏洞影响 环…...
LeetCode LCR147.最小栈
LeetCode LCR147.最小栈 思路🤔: 建立两个栈,一个栈正常入栈出栈,一个栈只用于出入最小数,当push值小于minst栈顶才入栈,当pop值等于minst栈顶才出栈。 代码🔎: class MinStack { pu…...
目标检测的算法有哪些
目标检测是计算机视觉领域的一个重要任务,它涉及识别图像或视频中的对象,并确定它们的位置和类别。随着深度学习的发展,出现了许多高效且准确的目标检测算法。以下是一些主要的目标检测算法: 两阶段检测器(Region-bas…...
HDU多校-交通管控
Problem - 7498 (hdu.edu.cn) 直接dfs显然不行,达到了2^500,那么我们可以考虑枚举所有红绿灯的状态,总共有三种状态,k的范围小于等于10,因此所有状态数为3^10不会超,所以通过三进制状压dp即可完成…...
【C++】string类
🚀个人主页:奋斗的小羊 🚀所属专栏:C 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 前言💥1、标准库中的string类💥1.1string类的常用接口💥string类对象常见…...
3分钟解锁B站缓存视频:m4s-converter无损转换指南
3分钟解锁B站缓存视频:m4s-converter无损转换指南 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾为B站下架的视频感到惋惜&…...
深入理解Strudel核心组件:从模式语法到音频处理
深入理解Strudel核心组件:从模式语法到音频处理 【免费下载链接】strudel MOVED TO CODEBERG - Web-based environment for live coding algorithmic patterns, incorporating a faithful port of TidalCycles to JavaScript 项目地址: https://gitcode.com/gh_mi…...
内网环境下的Conan服务器搭建:基于Artifactory的完整配置指南
内网环境下的Conan服务器搭建:基于Artifactory的完整配置指南 在企业级C/C开发中,依赖管理一直是困扰开发团队的痛点。当项目规模扩大、团队协作需求增加时,如何高效管理第三方库和内部组件成为关键挑战。特别是在金融、军工等对网络安全要求…...
短剧付费转化系统设计:试看 + 阶梯定价 + 会员锁客全链路
短剧赛道正从“流量驱动”转向“付费驱动”,但用户对付费短剧的信任门槛依然很高。一套科学的转化系统,能显著提升从试看到首充、从单集付费到会员订阅的转化率。本文结合实战经验,拆解短剧付费转化系统的核心设计。一、试看机制:…...
Plant Simulation数字孪生实战:从零搭建生产车间模型(附SimTalk脚本示例)
Plant Simulation数字孪生实战:从零搭建生产车间模型(附SimTalk脚本示例) 在工业4.0的浪潮中,数字孪生技术正成为制造业转型升级的核心驱动力。作为西门子Tecnomatix产品线中的重要组成部分,Plant Simulation以其强大的…...
ROS2 Humble下Cartographer纯定位不成功?别急,可能是你的.lua配置文件少了这行关键代码
ROS2 Humble下Cartographer纯定位失败的深度排查与解决方案 当你在RViz中看到地图显示正常,但激光雷达点云始终无法与地图正确匹配时,那种挫败感我深有体会。去年在部署仓库AGV项目时,我花了整整三天时间排查类似问题,最终发现是.…...
别再只盯着MSE了!图像配准效果好不好,这5个评价指标你用过几个?
图像配准效果评估:超越MSE的五大核心指标实战指南 在医学影像分析和计算机视觉领域,图像配准技术如同一位精准的"空间协调师",将不同时间、不同视角或不同设备获取的图像对齐到同一坐标系。但如何判断这位"协调师"的工作…...
YOLOv8在Jetson上实时推理的终极优化:从.pt到INT8/FP16量化TensorRT引擎全流程
YOLOv8在Jetson平台上的极致性能优化:从模型量化到TensorRT部署实战 当你在Jetson边缘设备上部署YOLOv8模型时,是否遇到过这样的困境——明明使用了GPU加速,推理速度却依然无法满足实时视频分析的需求?这可能是由于你没有充分利用…...
(学习笔记)3.11 浮点代码(3.11.4 定义和使用浮点数3.11.5 在浮点代码使用位级操作)
文章目录线索栏笔记栏1.定义和使用浮点常数1)核心机制2)示例分析3)练习题3.552.在浮点代码中使用位级操作1)指令与功能2)标量应用3)练习题3.56(逆向工程位操作)总结栏线索栏 为什么…...
Artisan:从咖啡豆到完美烘焙,掌握专业级烘焙曲线可视化工具
Artisan:从咖啡豆到完美烘焙,掌握专业级烘焙曲线可视化工具 【免费下载链接】artisan artisan: the worlds most trusted roasting software 项目地址: https://gitcode.com/gh_mirrors/ar/artisan 你是否曾经在烘焙咖啡豆时,感觉整个…...
