当前位置: 首页 > article >正文

别再死记F=G+H了!从Dijkstra到A*,用Unity可视化带你彻底理解寻路算法演进

从盲目探索到智能导航Unity中Dijkstra与A*算法的可视化演进在游戏开发的世界里路径规划算法就像是一位无形的向导决定着NPC如何穿越迷宫、敌人如何追踪玩家、或者单位如何在地图上移动。对于Unity开发者而言理解这些算法背后的思维模式远比死记硬背公式更为重要。本文将带你通过Unity的可视化实现直观感受从Dijkstra到A*的算法演进历程揭示启发式搜索如何大幅提升路径规划效率。1. 寻路算法的历史脉络与核心思想寻路算法的发展历程反映了计算机科学家们不断优化问题解决思路的智慧。早期的深度优先搜索(DFS)和广度优先搜索(BFS)虽然简单但在处理路径规划时显得力不从心。Dijkstra算法在1956年提出首次引入了代价的概念而A*算法则是在1968年由Peter Hart等人提出通过启发式函数将搜索方向导向目标。关键概念对比算法类型核心思想适用场景时间复杂度DFS尽可能深地探索单一路径拓扑排序、连通性检测O(VE)BFS均匀向外扩展探索最短路径(无权图)O(VE)Dijkstra优先扩展当前代价最小的节点带权图最短路径O(V^2)A*代价启发式预估引导搜索有明确目标的路径规划取决于启发式质量在Unity中实现这些算法的可视化我们可以创建一个网格地图用不同颜色标记白色未探索区域蓝色已探索节点绿色最短路径红色障碍物// Unity中网格节点的基本数据结构 public class PathNode : MonoBehaviour { public int x, y; public bool isWalkable true; public int gCost; // 从起点到当前节点的实际代价 public int hCost; // 到终点的预估代价 public int fCost gCost hCost; // 总代价 public PathNode cameFrom; // 路径回溯 [SerializeField] private MeshRenderer meshRenderer; public void SetColor(Color color) { meshRenderer.material.color color; } }2. Dijkstra算法平等主义的探索者Dijkstra算法的核心在于它对所有可能方向都保持公平态度像一个谨慎的探险家不放过任何可能的路径。这种盲目性虽然保证了能找到最短路径但也导致了效率问题。算法执行步骤初始化起点将其代价设为0其他节点代价设为无穷大将起点加入待探索集合(openSet)从openSet中取出当前代价最小的节点若该节点是终点则重构路径返回否则遍历该节点的所有邻接节点计算通过当前节点到达邻接节点的新代价如果新代价更优则更新邻接节点的代价和前驱节点将邻接节点加入openSet重复步骤3-5直到找到终点或openSet为空在Unity中可视化Dijkstra算法我们会看到它像水波一样均匀地向四周扩散直到触及目标点。这种盲目的特性在某些情况下会导致大量不必要的计算。// Unity中Dijkstra算法的核心实现 IEnumerator DijkstraVisualization(PathNode startNode, PathNode endNode) { ListPathNode openSet new ListPathNode { startNode }; HashSetPathNode closedSet new HashSetPathNode(); // 初始化所有节点 foreach (var node in grid) { node.gCost int.MaxValue; node.cameFrom null; } startNode.gCost 0; while (openSet.Count 0) { // 获取当前代价最小的节点 PathNode currentNode openSet[0]; for (int i 1; i openSet.Count; i) { if (openSet[i].gCost currentNode.gCost) { currentNode openSet[i]; } } // 可视化当前节点 currentNode.SetColor(Color.blue); yield return new WaitForSeconds(0.05f); // 找到终点 if (currentNode endNode) { RetracePath(startNode, endNode); yield break; } openSet.Remove(currentNode); closedSet.Add(currentNode); // 遍历邻居 foreach (var neighbor in GetNeighbors(currentNode)) { if (!neighbor.isWalkable || closedSet.Contains(neighbor)) continue; int newMovementCost currentNode.gCost GetDistance(currentNode, neighbor); if (newMovementCost neighbor.gCost || !openSet.Contains(neighbor)) { neighbor.gCost newMovementCost; neighbor.cameFrom currentNode; if (!openSet.Contains(neighbor)) { openSet.Add(neighbor); neighbor.SetColor(Color.cyan); } } } } }提示在Unity中实现可视化时使用协程(Coroutine)配合yield return可以实现逐步展示算法过程的效果比一次性计算完再显示更有利于理解。3. A*算法有目标的智能导航A*算法的革命性在于它引入了启发式函数(Heuristic)让搜索过程变得有方向感。这就像在陌生城市问路时当地人不仅告诉你怎么走还会建议那个方向更接近目的地。A*算法的三大核心要素G值从起点到当前节点的实际代价H值(启发式函数)当前节点到终点的预估代价F值F G H决定节点探索优先级常用的启发式函数有曼哈顿距离适用于只能四方向移动的网格对角线距离适用于八方向移动欧几里得距离最精确但计算量稍大// 几种常见启发式函数的实现 int ManhattanDistance(PathNode a, PathNode b) { return Mathf.Abs(a.x - b.x) Mathf.Abs(a.y - b.y); } int DiagonalDistance(PathNode a, PathNode b) { int dx Mathf.Abs(a.x - b.x); int dy Mathf.Abs(a.y - b.y); return 10 * (dx dy) (14 - 2 * 10) * Mathf.Min(dx, dy); } int EuclideanDistance(PathNode a, PathNode b) { return (int)Mathf.Sqrt(Mathf.Pow(a.x - b.x, 2) Mathf.Pow(a.y - b.y, 2)); }A*算法优化点优先队列使用堆结构存储openSet提高获取最小F值节点的效率哈希表快速判断节点是否在openSet或closedSet中权重系数给H值乘以一个略大于1的系数(如1.2)可以加快搜索速度但可能牺牲最优性// Unity中A*算法的核心实现 IEnumerator AStarVisualization(PathNode startNode, PathNode endNode) { HeapPathNode openSet new HeapPathNode(grid.MaxSize); HashSetPathNode closedSet new HashSetPathNode(); openSet.Add(startNode); while (openSet.Count 0) { PathNode currentNode openSet.RemoveFirst(); closedSet.Add(currentNode); // 可视化当前节点 currentNode.SetColor(Color.blue); yield return new WaitForSeconds(0.05f); if (currentNode endNode) { RetracePath(startNode, endNode); yield break; } foreach (var neighbor in GetNeighbors(currentNode)) { if (!neighbor.isWalkable || closedSet.Contains(neighbor)) continue; int newCostToNeighbor currentNode.gCost GetDistance(currentNode, neighbor); if (newCostToNeighbor neighbor.gCost || !openSet.Contains(neighbor)) { neighbor.gCost newCostToNeighbor; neighbor.hCost GetDistance(neighbor, endNode); neighbor.cameFrom currentNode; if (!openSet.Contains(neighbor)) { openSet.Add(neighbor); neighbor.SetColor(Color.cyan); } else { openSet.UpdateItem(neighbor); } } } } }4. 算法对比与实战优化在Unity编辑器中同时运行Dijkstra和A算法两者的差异会非常明显。Dijkstra像无头苍蝇般四处探索而A则像被磁铁吸引一样直奔目标。这种差异在大型地图上尤为显著。性能对比测试数据地图尺寸障碍密度Dijkstra探索节点数A*探索节点数时间比10×1010%68154.5:120×2015%254426:150×5020%189315612:1常见问题与解决方案A*退化为Dijkstra的情况当启发式函数H始终返回0时当H值远小于实际代价时解决方案确保使用合适的启发式函数网格地图的优化技巧使用Jump Point Search跳过直线路径上的冗余节点分层路径规划(Hierarchical Pathfinding)先规划大区域路径再细化方向优先在F值相同时优先选择更接近目标方向的节点// Jump Point Search的简化实现 ListPathNode GetJumpPoints(PathNode node, PathNode parent, PathNode endNode) { ListPathNode jumpPoints new ListPathNode(); Vector2Int direction new Vector2Int(node.x - parent.x, node.y - parent.y); direction.Clamp(-1, 1); // 标准化为-1,0,1 // 强制邻居检查 if (HasForcedNeighbors(node, direction)) { return new ListPathNode { node }; } // 直线跳跃 PathNode next grid[node.x direction.x, node.y direction.y]; if (next ! null next.isWalkable) { return GetJumpPoints(next, node, endNode); } // 对角线跳跃 if (direction.x ! 0 direction.y ! 0) { PathNode horizontal grid[node.x direction.x, node.y]; PathNode vertical grid[node.x, node.y direction.y]; if ((horizontal ! null !horizontal.isWalkable) || (vertical ! null !vertical.isWalkable)) { return new ListPathNode { node }; } } return jumpPoints; }注意在实际游戏开发中路径规划往往需要与游戏逻辑紧密结合。比如RTS游戏中单位可能需要避开动态障碍或者战略游戏需要考虑地形对不同单位的影响。这时可以扩展节点代价计算方式将地形因素、威胁区域等纳入考量。5. 高级应用与扩展思路掌握了基础算法后我们可以进一步探索更复杂的应用场景。现代游戏中的路径规划往往需要处理动态环境、多单位协调等复杂情况。动态障碍处理策略局部避障全局使用A*规划路径遇到动态障碍时局部调整增量式重规划当环境变化时只重新计算受影响部分的路径势场法将障碍视为斥力目标视为引力计算合力方向// 简单的局部避障实现 Vector3 AvoidObstacles(Vector3 currentPosition, Vector3 desiredDirection) { RaycastHit hit; float avoidDistance 2f; // 前方障碍检测 if (Physics.SphereCast(currentPosition, 0.5f, desiredDirection, out hit, avoidDistance)) { Vector3 avoidDirection Vector3.Cross(hit.normal, Vector3.up).normalized; return (desiredDirection avoidDirection * 2f).normalized; } return desiredDirection; }多单位路径协调技术路径预留单位提前预定将要经过的节点避免冲突速度匹配根据前方单位调整速度保持队形群体运动结合flocking算法实现自然群体移动// 简单的群体移动实现 void UpdateGroupMovement(ListUnit units, PathNode targetNode) { foreach (var unit in units) { Vector3 separation CalculateSeparation(unit, units); Vector3 alignment CalculateAlignment(unit, units); Vector3 cohesion CalculateCohesion(unit, units); Vector3 desiredDirection (separation alignment cohesion).normalized; unit.MoveTowards(desiredDirection); } }在Unity中实现这些高级特性时可以考虑使用Scriptable Object来配置不同单位的移动参数或者使用ECS架构来处理大规模单位的路径规划。可视化调试工具也至关重要可以绘制Gizmos来显示路径、探索区域和代价计算。

相关文章:

别再死记F=G+H了!从Dijkstra到A*,用Unity可视化带你彻底理解寻路算法演进

从盲目探索到智能导航:Unity中Dijkstra与A*算法的可视化演进在游戏开发的世界里,路径规划算法就像是一位无形的向导,决定着NPC如何穿越迷宫、敌人如何追踪玩家、或者单位如何在地图上移动。对于Unity开发者而言,理解这些算法背后的…...

实战避坑:在Unity里用A*做2D网格寻路,我踩过的性能坑和优化方案都在这了

Unity中A*算法性能优化的实战指南当你在Unity项目中实现了一个基础A寻路系统后,随着游戏单位数量增加或地图规模扩大,性能问题往往会突然出现。帧率下降、卡顿现象频发,这些问题在移动端或需要大量单位同时寻路的RTS、塔防类游戏中尤为明显。…...

别再死记硬背F=G+H了!用Unity手搓一个A*寻路,从DFS、BFS到Dijkstra一步步讲透

从零构建A*寻路:用Unity可视化算法演进之路当我在开发第一个2D策略游戏时,遇到了一个经典问题:如何让单位智能地绕过障碍物找到最短路径?像许多初学者一样,我直接跳到了A*算法的实现,却被那个神秘的FGH公式…...

Python SMTP邮件发送教程

Python SMTP邮件发送教程 随着互联网的快速发展,电子邮件已经成为人们日常工作和生活中不可或缺的通讯工具。Python作为一种功能强大的编程语言,同样具备发送电子邮件的能力。本文将详细介绍如何使用Python进行SMTP邮件发送,包括环境配置、代码实现、发送邮件的格式和附件等…...

JMeter并发与持续性压测:从工具使用到系统级性能诊断

1. 这不是“点几下就出报告”的玩具,而是压测工程师的听诊器很多人第一次打开JMeter,以为它就是个带图形界面的curl增强版:填个URL、设个线程数、点“启动”,等跑完看个聚合报告,就觉得自己完成了接口性能测试。我见过…...

从原理到操作:彻底搞懂Linux服务器UEFI启动项管理(efibootmgr命令详解)

深入解析Linux服务器UEFI启动管理:efibootmgr命令全攻略当你在Linux服务器上执行efibootmgr命令时,是否曾被那些神秘的Boot000X条目搞得一头雾水?作为现代服务器的主流启动方式,UEFI远比传统的BIOS复杂得多。本文将带你从底层原理…...

JMeter接口功能测试实战:从契约解码到全链路断言

1. 这不是“点点点”的接口测试,而是用JMeter把业务逻辑钉在验证线上 很多人第一次打开JMeter,看到那个树形结构、一堆监听器和配置元件,下意识就把它当成“高级版Postman”——填个URL、加几个参数、点“启动”,看绿色小三角跑起…...

Unity2022数字孪生变电站工程包:URP优化+IEC104直连+Win11深度适配

1. 这不是个“能跑就行”的Demo,而是一套可交付的数字孪生工程基线“Unity源码:数字孪生变电站场景,支持Unity2022与Win11运行,完整包”——看到这个标题,我第一反应不是点开下载,而是下意识翻了翻发布者主…...

r2frida:打通静态分析与动态调试的逆向工作流

1. 这不是“又一个插件”,而是动态分析工作流的物理层重构你有没有过这样的经历:在逆向一个加固App时,刚用r2 -A扫完符号,发现关键函数全被混淆成sub_401a2c;切到Frida写个Java.perform脚本hook住目标方法,…...

r2frida:打通Radare2静态分析与Frida动态调试的逆向工程工作流

1. 为什么你还在用 Frida CLI 单打独斗,而高手早已把 Radare2 的逆向能力“焊”进动态分析流程? 如果你做过 Android 或 iOS 应用的深度安全分析,大概率经历过这样的场景:Frida hook 到目标函数后,看到 this 指针指…...

Unity Addressable本地HTTP托管实战:5分钟跑通远程加载

1. 为什么Addressable本地托管总卡在“5分钟”这个幻觉里?Unity Addressable Asset System(可寻址资源系统)上线这么多年,我见过太多团队在“本地HTTP服务器”这一步摔得最狠——不是不会写代码,而是根本没搞清Address…...

Unity Addressable本地HTTP服务器5分钟合规搭建指南

1. 为什么Addressable资源托管总卡在“本地跑不通”这一步? Unity Addressable Asset System(可寻址资源系统)上线这么多年,我见过太多团队在最后一步集体卡壳:资源打包没问题,加载逻辑写得滴水不漏&#…...

Unity Timeline激活与动画控制实战:5分钟精准调度

1. 这不是“Timeline入门”,而是你真正能用上的控制逻辑很多人第一次点开Unity Timeline面板时,第一反应是:“这不就是个时间轴剪辑工具吗?跟AE差不多?”——然后转身就去写Update里硬编码的if-else开关,或…...

量子纠错新突破:VarQEC变分编码技术解析

1. 量子纠错基础与VarQEC创新点量子计算的核心挑战在于量子态的脆弱性——环境噪声会导致量子信息不可逆的丢失。传统量子纠错(QEC)采用类似经典重复码的思路,通过将逻辑量子比特编码到多个物理比特上构建纠错码。例如著名的[[5,1,3]]完美码使用5个物理比特保护1个逻…...

避开Cox回归的坑:你的数据真的满足比例风险假定吗?

避开Cox回归的坑:你的数据真的满足比例风险假定吗?在医学研究和流行病学分析中,Cox比例风险模型因其能够处理删失数据且不依赖基准风险函数的特定形式而广受欢迎。然而,许多研究者在使用这一强大工具时,往往忽略了一个…...

Unity游戏本地化:XUnity Auto Translator运行时文本注入方案

1. 这不是“翻译插件”,而是一套专为Unity游戏本地化设计的轻量级运行时注入方案你有没有遇到过这样的情况:接手一个老项目,UI文本全写死在代码里,或者Text组件上直接填了中文字符串;美术给的按钮图上还带着“开始游戏…...

Unity游戏本地化实战:XUnity.AutoTranslator核心机制与真机调试

1. 这不是“加个插件就完事”的翻译方案,而是游戏本地化工程的起点在Unity项目里点开Asset Store搜“translation”,你会看到一堆标着“一键汉化”“自动翻译”的插件,图标闪亮,描述诱人。我去年接手一个海外发行的休闲游戏时也这…...

Unity游戏实时翻译工程化实践:从XUnity.AutoTranslator配置到本地化流水线构建

1. 这不是“加个插件就完事”的翻译方案,而是游戏本地化工程的起点你刚在Unity Asset Store里搜到XUnity.AutoTranslator,点开文档看到“支持实时翻译”“自动注入UI文本”,心里一热:终于能绕过繁琐的多语言资源表管理&#xff0c…...

通过奇异的镜子:LLM 是否像人类大脑一样记忆?

原文:通过奇异的镜子:LLM 是否像人类大脑一样记忆? |LLM|AI|人类大脑|记忆|认知| https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7fcf9c5caa8b28d372dbcb4caeb706af.png 作者使用 DALL-E 创建的图片 …...

UE5 CPU瓶颈定位实战:用ProfileCPU精准揪出Game线程卡顿根因

1. 这不是“点开就看”的性能分析,而是UE5里真正能救命的CPU瓶颈定位术在UE5项目做到中后期,你肯定经历过那种“明明没加多少新功能,帧率却从60掉到35,Editor卡得像PPT”的窒息时刻。打开Stat Unit,看到Game线程时间飙…...

GCN vs MLP:在Cora数据集上,图神经网络到底强在哪?(附可视化对比)

GCN与MLP在Cora数据集上的本质差异:从特征聚合到空间重构的认知升级当我们面对学术文献分类任务时,传统机器学习方法往往将每篇文献视为独立个体进行处理。这种处理方式在Cora数据集上通常只能获得约50%的分类准确率,而图卷积网络(GCN)却能轻…...

从COCO person_keypoints到YOLO格式:一份完整的姿态估计数据集转换脚本与避坑指南

从COCO到YOLO格式:姿态估计数据集转换实战手册在计算机视觉领域,姿态估计任务正从学术研究快速走向工业应用。许多开发者希望利用YOLO系列模型(如YOLOv8-Pose)进行训练,却常常在数据预处理阶段遇到障碍。本文将提供一套…...

手把手教你用Powergui的FFT Tool分析Simulink示波器数据(从记录到出图)

从仿真到频谱:Powergui FFT工具在Simulink中的完整应用指南当你在Simulink中完成电力系统或信号处理的仿真后,如何从时域波形中提取有价值的频域信息?许多工程师在第一次接触FFT分析时,往往会被各种参数设置和数据格式问题困扰。本…...

用PyTorch和TD3教AI玩赛车:从像素输入到稳定驾驶的保姆级调参指南

用PyTorch和TD3构建赛车AI:视觉输入下的强化学习调参实战当游戏画面从单纯的娱乐载体转变为强化学习的训练场时,每一个像素都承载着决策信息。CarRacing-v2环境将这种挑战具象化——96x96的彩色图像输入需要转化为精确的转向、油门和刹车控制。不同于传统…...

麒麟KYLINOS声音设置进阶:用命令行玩转‘寻光’主题、单声道和侦听模式

麒麟KYLINOS声音设置进阶:用命令行玩转‘寻光’主题、单声道和侦听模式对于追求系统深度定制的极客用户、音频工作者或无障碍功能使用者来说,图形界面往往只是冰山一角。麒麟KYLINOS基于UKUI桌面的声音子系统隐藏着诸多实用功能,通过命令行可…...

UE5小地图实战:SceneCapture2D+RenderTarget动态雷达优化指南

1. 这不是“加个UI贴图”就能糊弄过去的小地图在UE5项目里做小地图,很多人第一反应是:找张静态地图图片,用UMG拖个Image控件,再写个蓝图把玩家坐标换算成UI像素位置——做完就交差。我去年带一个独立团队做开放世界生存游戏时&…...

Kali Linux忘记root密码别慌!两种方法(登录态/非登录态)手把手教你重置

Kali Linux忘记root密码的终极恢复指南:从原理到实战当你正专注于一个关键的安全测试项目,突然发现无法执行需要root权限的操作——这种场景对Kali Linux用户来说并不陌生。作为渗透测试和网络安全研究的标配系统,Kali Linux的root账户是系统…...

UE5小地图性能优化:SceneCapture2D+RenderTarget动态雷达实战

1. 为什么小地图不能只靠蓝图“拖一拖”就完事?在UE5项目里,我见过太多团队把小地图当成UI组件来处理——用一个Widget画个圆圈,再用几个蓝色小点代表队友,红色小点代表敌人,位置靠GetActorLocation硬算、角度靠FVecto…...

TT100K数据集类别不平衡?手把手教你用Python筛选并重划分(保留45类实战)

TT100K数据集类别不平衡解决方案:Python实战指南当你第一次打开TT100K数据集时,可能会被其庞大的图片数量震撼——train文件夹6105张,test文件夹3071张,other文件夹更是多达7641张。但兴奋过后,细看类别分布&#xff0…...

避坑指南:在openEuler 22.03上配置vsftpd虚拟用户,解决PAM认证和SELinux权限问题

深度实战:openEuler 22.03中vsftpd虚拟用户配置全流程与疑难解析 在服务器运维领域,FTP服务作为经典的文件传输方案,其安全配置一直是系统管理员的核心技能。本文将聚焦openEuler 22.03操作系统环境,深入剖析vsftpd虚拟用户模式的…...