您的位置:首页 > 其它

塔防中的路径查找

2015-12-03 09:49 148 查看
原文链接:http://www.redblobgames.com/pathfinding/tower-defense/

在塔防游戏中,有许多敌人都把矛头指向同一个地方。在许多塔防游戏中,有的是预先定下的路径,或者可数的几条路径.其中一些,像经典的桌面塔防,你可以放置塔们在任何地方,它们扮演着障碍物的角色去影响杀来的敌人.试试看,点击地图切换出墙壁来看看效果:

我们该怎样实现这个呢?

图搜索算法像A*,常常被用在查找从一个点到另一个点的最短路径.你可以用这个算法到每个敌人身上去查找到达目标的路径.有大量的不同的可供使用的图搜索算法在典型的游戏中.典型的有:

One source, one destination(一源一目标):
Greedy Best First Search 贪心搜索算法
A* - commonly used in games A*算法-最常使用

One source, all destinations(一源多目标), or all sources, one destination(多源一目标):
Breadth First Search - unweighted edges 广度优先算法
Dijkstra’s Algorithm - adds weights to edges Dijkstra算法
Bellman-Ford - supports negative weights Bellman-Ford算法

All sources, all destinations(多源多目标):
Floyd-Warshall   Floyd-Warshall算法
Johnson’s Algorithm Johnson算法

像桌面塔防游戏中,拥有大量的敌人点(源点)以及一个它们的共同目标点.这样的情形刚好匹配多源一目标策略算法.不采用A*算法给每个敌人进行路径计算,我们可以运行一个算法一次,就可以计算出所有敌人想要的路径了.甚至更好的化,可以给每一个位置计算出最短路径,所以当敌人前仆后继着好多或者新的敌人创建的时候,它们移动的路径早已经被计算出来了.

让我们探索下广度优先搜索,有时候也称为"泛洪"(FIFO变体).虽然图搜索可以工作在任何节点-边缘的图中,我这里例子中用的是方格表.表格是图中特别的案例.每一个网格瓷砖都是一个图节点,瓷砖间的边界就是图的边.我将在其他文章中探索非表格图的应用.

广度优先搜索从一个点出发并且重复访问邻居节点们.关键的概念是前驱队列(“frontier”),位于访问过的跟未访问过的区域中间.前驱队列从起始点开始朝外扩张,直到探索完整个图.

前驱队列是前驱:图的一个数组或者列表中的节点需要被分析.从只包含一个单个元素(起始节点)开始.在每一个节点上的"访问过标记"从我们看到后就一直保持跟踪记录.开始时所有的一切都是False状态(未访问).通过滑块来观察前驱队列的扩张:

这个算法是怎么工作的呢?在每一步中,从前驱队列中取出来一个节点,称它为当前节点.然后查看每个当前节点的邻居节点(变量名叫next).如果之前没有访问过的化就把现在这个next节点添加到前驱队列中去.这里是对应的python代码:

frontier = Queue()

frontier.put(start)

visited = {}

visited[start] = True


while not frontier.empty():

  current = frontier.get()

  for next in graph.neighbors(current):

     if next not in visited:

        frontier.put(next)

        visited[next] = True


现在你已经看过代码了,尝试一步一步前进动画.注意前驱队列的变化情况,当前节点,以及邻居节点(next nodes).在每一步中,前驱队列中的一个元素变成当前节点,邻居节点被打上标记,其他的未访问的邻居加入到前驱队列中.一些邻居如果已经被访问过就不需要加入到前驱队列中了.

它是相对简单的算法,对于所有有序的东西都是有用的,包括AI.有三个主要的方式我曾用到的:

标记所以可到达的节点.如果你的地图不是完全连通的话,浙江是很有用的,或许你想要知道哪些是可到达的.那就是我们上面所实现的,用已访问过标记来记录.

寻找从一个节点到其他所有节点的路径,或者从所有节点到一个节点的路径.这是我在顶层页中使用到的.

测量从一个节点到其他节点的距离.这对于想知道距离一个怪兽有多远是非常有用的.

测量从一个节点到其他节点们的距离.

如果你正在生成路径,你将想要知道每个节点来自于哪里.当你访问邻居节点的时候,顺便记下邻居来自于哪里.重命名visited表为came_from,用它去跟踪记录当前节点来自于哪里:

frontier = Queue()

frontier.put(start)

came_from = {}

came_from[start] = None


while not frontier.empty():

  current = frontier.get()

  for next in graph.neighbors(current):

     if next not in came_from:

        frontier.put(next)

        came_from[next] = current


让我们看看这个算法跑起来像什么:

如果你需要距离,你可以弄一个计时器,初值设置为0,每访问一个邻居你就将该值+1.重命名visited 表 为distance,并且用它去保存到达它的距离:

frontier = Queue()

frontier.put(start)

distance = {}

distance[start] = 0


while not frontier.empty():

  current = frontier.get()

  for next in graph.neighbors(current):

     if next not in distance:

        frontier.put(next)

        distance[next] = 1 + distance[current]


如果你想保存距离和路径,你可以弄俩变量进行保存记录.

所以说那是广度优先搜索.对于塔防风格的游戏,我曾经用它去查找从一个给定点到所有位置的路径,而不是用A*算法重复去为每个敌人查找路径.也曾经用它去为一个指定攻击范围的怪兽查找到所有位置的路径.还将它用在程序地图生成中.Minecraft用它去可视化剔除计算.

下一步:

我已经用python 和C++实现了该算法.

如果你想要从一个位置出发的所有路径,而不是到一个位置,翻转came_from指针就可以了.

If you want paths to one of several locations instead of a single location, you can add edges to your graphs from each of your destinations to an extra destination node. The extra
node won’t show up on the grid, but in the graph it will represent the destination.

早点退出来:如果你寻找到一个位置或者从一个位置出发的路径,你可以在找到路径后就停止搜索了.

加权边:如果你需要有代价的移动,广度优先搜索需要变成Dijkstra算法.我在A*文章中有描述.

启发式:如果你添加了一个方式去引导搜索到目标,广度优先搜索改为贪婪搜索算法.同样在A*文章中有描述.

如果你用广度优先搜索启动搜索,并且添加了及早退出策略,权重边,以及启发函数,你就可以用A*了.正如你想的那样,下句话当然还是在A*文章中有描述.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: