在游戏中,当我们需要让角色移动到指定位置时,只需要鼠标轻轻的一点就可以完成这简单的步骤,系统会立即寻找离角色最近的一条路线

可是,这背后的行为逻辑又有什么奥秘呢? 你会怎么写这个寻路算法呢?

一般我们遇到这种路径搜索问题,大家首先可以想到的是广度优先搜索算法(Breadth First Search)、还有深度优先(Depth First Search)弗洛伊德(Floyd迪杰斯特拉(Dij)等等这些非常著名的路径搜索算法,但是在绝大多数情况下这些算法面临的缺点就暴露了出来:时间复杂度比较高

所以,大部分环境里我们用到的是一个名叫A* (A star)的搜索算法

(点击图片浏览动图)

说到最短路径呢,我们就不得不提到广度优先遍历(BFS),它是一个万能算法,它不单单可以用在 寻路或者搜索的问题上。windows的系统工具:画板 中的油漆桶就是其比较典型一个的例子

这里对路径搜索做一个比较简洁的示例

假设我们是在一个网格上面进行最短路径的搜索

我们只能上下左右移动,不可以穿越障碍物。算法的目的是为了能让你寻找到一条从起点到站点的最短路径

假设每次都可以上下左右朝4个方向进行移动

算法在每一轮遍历后会标记这一轮探索过的方块称为边界(Frontier),就是这些绿色的方块。

 (点击图片浏览动图)

然后算法呢会循环往复的从这些边界方块开始,朝他们上下左右四个方向进行探索,直到算法遍历到了终点方块才会停止。而最短路径呢就是算法之前一次探索过的路径。为了得到算法探索过的整条路径呢,我们可以在搜索的过程中顺势记录下路径的来向。

比如这里方块上的白色箭头就代表了之前方块的位置

在每一次探索路径的时候,我们要做的也只是额外的记录下这个信息

要注意,所有探索过的路径我们需要将它们标记成灰色,代表它们“已经被访问过“,这样子算法就不会重复探索已经走过的路径了。

广度优先算法显然可以帮助我们找到最短路径,不过呢它有点傻,因为它对路径的寻找是没有方向性的,它会向各个方向探测过去。

最坏的情况可能是找到终点需要遍历整个地图,因此很不智能,我们需要一个更加高效的算法。

就是本次我们要介绍的A * A star搜索算法

A* Search Algorithm

”A*搜索算法“也被叫做“启发式搜索”

与广度优先不同的是,我们在每一轮循环的时候不会去探索所有的边界方块(Frontier),而会去选择当前“代价(cost)”最低的方块进行探索。

这里的“代价”就很有意思了,也是A*算法智能的地方。

我们可以把这里的代价分成两部分,一部分是“当前路程代价(可表示成f-cost)”:比如你从起点出发一共走过多少个格子,f-cost就是几

另一部分是“预估代价(可表示成g-cost)”:用来表示从当前方块到再终点方块大概需要多少步,预估预估所以它不是一个精确的数值,也不代表从当前位置出发就一定会走那么远的距离,不过我们会用这个估计值来指导算法去优先搜索更有希望的路径。

最常用到的“预估代价”有欧拉距离(Euler Distance)“,就是两点之间的直线距离(x1 - x2)^2 + (y1 - y2)^2

当然还有更容易计算的“曼哈顿距离(Manhattan Distance)”,就是两点在竖直方向和水平方向上的距离总和|x1 - x2|+|y1 - y 2|

曼哈顿距离不用开方,速度快,所以在A* 算法中我们可以用它来充当g-cost

接下来,我们只要把之前讲到的这两个代价相加就得出了总代价:f-cost + g-cost

然后在探索方块中,优先挑选总代价最低的方块进行探索,这样子就会少走很多弯路

而且搜索到的路径也一定是最短路径。

在第一轮循环中,算法对起点周围的四个方块进行探索,并计算出“当前代价”和“预估代价”。

比如这里的1代表从起步到当前方块走了1步

这里的4代表着方块到终点的曼哈顿距离,在这四个边界方块中,右边方块代价最低,因此在下一轮循环中会优先对它进行搜寻

在下一轮循环中,我们已同样的方式计算出方块的代价,发现最右边的方块价值依然最低,因此在下一轮的循环中,我们对它进行搜寻

算法就这样子循环往复下去,直到搜寻到终点为止

增加一下方块的数量级,A*算法同样可以找到正确的最短路径

最为关键的是,它搜寻的方块个数明显比广度优先遍历少很多,因此也就更高效。

理解了算法的基本原理后,接下来就是上代码了,这里我直接引用redblobgamesPython代码实现,因为人家实在写的太好了!

def heuristic(a, b): #Manhattan Distance(x1, y1) = a(x2, y2) = breturn abs(x1 - x2) + abs(y1 - y2)def a_star_search(graph, start, goal):frontier = PriorityQueue()frontier.put(start, 0)came_from = {}cost_so_far = {}came_from[start] = Nonecost_so_far[start] = 0while not frontier.empty():current = frontier.get()if current = goal:breakfor next in graph.neighbors(current):new_cost = cost_so_far[current] + graph.cost(current, next)if next not in cost_so_far or new_cost < cost_so_far[next]:cost_so_far[next] = new_costpriority = new_cost + heuristic(goal, next)frontier.put(next, priority)came_from[next] = currentreturn came_from, cost_so_far

先来看看最上面几行,frontier中存放了我们这一轮探测过的所有边界方块(之前图中那些绿色的方块)

frontier = PriorityQueue()

PriorityQueue代表它是一个优先队列,就是说它能够用“代价”自动排序,并每次取出”代价“最低的方块

frontier.put(start, 0)

队列里面呢我们先存放一个元素,就是我们的起点

came_from = {}

接下来的的 came_from 是一个从当前方块到之前的映射,代表路径的来向

cost_so_far = {}

这里的 cost_so_far 代表了方块的“当前代价”

came_from[start] = None
cost_so_far[start] = 0

这两行将起点的 came_from 置空,并将起点的当前代价设置成0,这样子就可以保证算法数据的有效性

while not frontier.empty():current = frontier.get()

接下来,只要 frontier 这个队列不为空,循环就会一直执行下去,每一次循环,算法从优先队列里抽出代价最低的方块

if current = goal:
break

然后检测这个方块是不是终点块,如果是算法结束,否则继续执行下去

for next in graph.neighbors(current):

接下来,算法会对这个方块上下左右的相邻块,也就是循环中 next 表示的方块进行如下操作

new_cost = cost_so_far[current] + graph.cost(current, next)

算法会先去计算这个 next 方块的“新代价”,它等于之前代价 加上从 current 到 next 块的代价

由于我们用的是网格,所以后半部分是 1

if next not in cost_so_far or new_cost < cost_so_far[next]:

然后只要 next 块没有被检测过,或者 next 当前代价比之前的要低

frontier.put(next, priority)

我们就直接把他加入到优先队列,并且这里的总代价priority等于当前代价加上预估代价”

priority = new_cost + heuristic(goal, next)

预估代价就是之前讲到的“曼哈顿距离”

def heuristic(a, b):     (x1, y1) = a     (x2, y2) = b     return abs(x1 - x2) + abs(y1 - y2)

之后程序就会进入下一次循环,重复执行之前的所有步骤

这段程序真的是写的特别巧妙,可能比较难以理解可是多看几遍说不定你就突然灵光乍现了呢

拓展

如果把地图拓展成网格形式(Grid),因为图的节点太多,遍历起来会非常的低效

于是我们可以吧网格地图简化成 节点更少的路标形式(WayPoints

然后需要注意的是:这里任意两个节点之间的距离就不再是1了,而是节点之间的实际距离

我们还可以用自上而向下分层的方式来存储地图

比如这个四叉树(Quad Tree

又或者像unity中使用的导航三角网(Navigation Mesh,这样子算法的速度就会得到进一步优化

另外,我还推荐redblobgames的教程

各种算法的可视化,以及清楚的看见各种算法的遍历过程、中间结果

以及各种方法之间的比较,非常的直观形象,对于算法的理解也很有帮助。


参考:

[1]周小镜. 基于改进A算法的游戏地图寻径的研究[D].西南大学,2010. [2]https://www.redblobgames.com/pathfinding/a-star/introduction.html [3]https://en.wikipedia.org/wiki/A_search_algorithm

点击关注,第一时间了解华为云新鲜技术~

揭秘在召唤师峡谷中移动路径选择逻辑?相关推荐

  1. 内部设计师揭秘!王者峡谷中竟有隐藏的c++代码??!!腾讯已经炸了!!!

    解析 模拟的时候用关于n的一元二次方程实根公式解的不亦乐乎...后来经高人提醒才发现万物皆为斐波拉契.. 就很<离谱> 于是代码就不难了 也算有收获吧,遇到这种看起来莫名其妙的题时,不着急 ...

  2. 《英雄联盟》——召唤师峡谷模式 游戏设计元素分析

    <英雄联盟>--召唤师峡谷模式 游戏设计元素分析: 前言 玩家 游戏目标 规则与行动 挑战 成功的因素 前言 本文只针对<英雄联盟>这款游戏中的其中一个模式<召唤师峡谷& ...

  3. 机器人无限火力无限e符文_无限火力装备符文评级 召唤师峡谷秒变欢乐谷 机器人化身跳楼机...

    无限火力模式激情上线,拥有80%超快冷却缩减的BUFF,一切都和召唤师峡谷不同.那么在无限火力模式中,哪些装备符文能拥有更为亮眼的表现呢? 装备篇 无限火力最爽的一点就是--装备也能够享受80%的冷却 ...

  4. 关于线程池运行过程中,业务逻辑出现未知异常导致线程中断问题反思

    最近在项目研发中的关于线程池应用过程中由于业务逻辑异常导致的线程中断,但程序未中断导致的脏数据问题  话不多说,在最近最新的一个版本发布过程中,业务需要,我们要定期去给客户预留出可用的资源数据,提供客 ...

  5. centos 调整home分区xfs_Linux中对lvm逻辑卷分区大小的调整教程(针对xfs与ext4不同文件系统)...

    前言 当我们在安装系统的时候,由于没有合理分配分区空间,在后续维护过程中,发现有些分区空间不够使用,而有的分区空间却有很多剩余空间.如果这些分区在装系统的时候使用了lvm(前提是这些分区要是lvm逻辑 ...

  6. 攻防演练中的业务逻辑漏洞及检测思路

    随着各类前后端框架的成熟和完善,传统的SQL注入.XSS等常规漏洞在Web系统里逐步减少,而攻击者更倾向于使用业务逻辑漏洞来进行突破.业务逻辑漏洞,具有攻击特征少.自动化脆弱性工具无法扫出等特点,也为 ...

  7. 代码的世界中,一个逻辑套着另外一个逻辑,如何让每一种逻辑在代码中都有迹可循?...

    代码的世界中,一个逻辑套着另外一个逻辑,如何让每一种逻辑在代码中都有迹可循? 这正式框架的意义所在!

  8. Mybatis-Plus 使用自定义注入器后,查询条件中不再添加逻辑删除字段限定条件

    例如项目中加入如下注入器代码后,查询条件中不再添加逻辑删除字段限定条件 /*** 自定义Sql注入** @author nieqiurong 2018/8/11 20:23.*/ @Component ...

  9. Uber是如何重新思考GPS定位的(尤其是在城市峡谷中)

    郑昀(公众号:老兵笔记) 20180424 2018年4月19日,Uber 公布了 GPS 优化算法,https://eng.uber.com/rethinking-gps/,针对GPS定位在城市环境 ...

最新文章

  1. netty集成ssl完整参考指南(含完整源码)
  2. idea中一个maven项目增加Web支持
  3. [云炬创业基础笔记]第六章商业模式测试6
  4. 计算机有关的文献检索题目,文献检索第二次计算机检索实习题目(2016.4.10).doc
  5. 兼容各种浏览器的自动左右滚动兼左右点击滚动代码
  6. LeetCode 249. 移位字符串分组(哈希)
  7. centos下安装opencv
  8. 假如Python有C/C++ 的性能,会不会称霸IT界?
  9. java关键字super_Java关键字(六)——super
  10. 自定义动画(仿Win10加载动画)
  11. 「实验一小时」——大牛直播讲解PHP楼赛题目,就在今晚!
  12. html渐变色原理,CSS渐变色效果的实现方法与效果演示
  13. 平面解析几何----焦点弦上焦半径长度之比公式
  14. MyBatis -- resultType 和 resultMap
  15. 2018春招实习笔试面试总结(PHP)
  16. V-Ray 2.00.02 最新中文汉化版下载,适用于3dsmax 9.0/2008/2009/2010/2011所有版本, 中文v ray下载
  17. H264编码- 码率控制 RQ 模型参数推导过程以及JM代码分析
  18. 数学问题:导函数的左右极限与函数的左右导数是一回事吗?
  19. 悲剧了 花一千多买的三星的固态硬盘 突然无法访问了,用了这个命令好呆又能访问了
  20. java五子棋实验报告6,五子棋Java实验报告

热门文章

  1. Bootstrap 滚动监听Scrollspy 调用方式
  2. es6 循环加载ES6模块
  3. 和整数相乘_小学数学基础概念归纳总结:整数篇
  4. vim win装_vim插件管理器的安装和配置-windows
  5. 渭南师范计算机科学与技术,渭南师范学院计算机科学与技术专业2016年在陕西理科高考录取最低分数线...
  6. L1-046 整除光棍
  7. 提高程序员工作效率的几大工具
  8. QRCode二维码生成方案及其在带LOGO型二维码中的应用(2)
  9. Python爬虫(二十一)_Selenium与PhantomJS
  10. Hadoop源码分析21:namenode概要