天天看點

塔防遊戲的路徑尋找算法分析

在塔防遊戲中,有很多敵人都是向着同一目标前進的。在衆多塔防遊戲當中,有一條或幾條預定好的路徑。在一些塔防遊戲中,比如經典的《Desktop Tower Defense》,你可以将塔放在地圖上的任何地方,把他們作為阻礙敵人通往預定路徑的障礙。試着點選地圖來切換或移動牆壁:

塔防遊戲的路徑尋找算法分析

我們将如何實作?

圖搜尋算法例如A*這樣的算法經常被用來搜尋兩點之間的最短路徑。你可以用這個來找到每一個敵人前往的目标的路徑。有很多不同的圖搜尋算法可以用在這種類型的遊戲。這些都是經典的算法:

單源,單目标:

  • 貪心搜尋算法
  • A*算法 – 在遊戲當中常使用

單源多目标或多源單目标

  • 廣度優先搜尋算法-無權重邊緣
  • Dijkstra算法-有權重邊緣
  • Bellman-Ford算法-支援負權重

多源多目标

  • Floyd-Warshall算法
  • Johnson’s算法

像《Desktop Tower Defense》這樣的遊戲會有很多個敵人的位置(源)和一個共同的目的地。這使得它被歸為多源單目标這一類。我們可以執行一個算法,一次性算出所有敵人的路徑,而不是為每個敵人執行一次A*算法。更好的是,我們可以計算出每個位置的最短路徑,是以當敵人擠在一起或者新的敵人被建立出來時,他們的路徑已經被預先計算好了。

我們先來看看廣度優先算法,有時也被稱作“洪水填充法”(FIFO的變種)。雖然圖搜尋算法是适用于任何由節點和邊構成的網格圖,但是我還是使用方形網格來說明這些例子。網格是圖的一個特例。每個網格瓦片是圖節點,網格瓷磚之間的邊界是圖的邊。我會在另一篇文章當中探讨非網格圖。

廣度優先搜尋從一個節點開始,并通路鄰居節點。關鍵的概念是“邊界”,它在已探索和未開發的區域之間的邊界。邊界從原來的節點向外擴充,直到探索了整張圖。

邊界隊列是一個圖節點(網格瓦片)是否需要被分析的清單/數組。它最開始僅僅包含一個元素,起始節點。每個節點上的通路标志追蹤我們是否采訪過該節點。開始的時候除了起始節點都标志為FALSE。使用滑塊來檢視邊界是如何擴充的:

塔防遊戲的路徑尋找算法分析

這個算法是如何運作的?每走一步,獲得一個元素的邊界,把它命名為current。然後尋找current的每個鄰居,next。如果他們還沒有被通路過,将他們都添加到邊界隊列裡面。下面是一些python代碼:

  1. frontier = Queue()
  2. frontier.put(start)
  3. visited = {}
  4. visited[start] = True
  5. while not frontier.empty():
  6.    current = frontier.get()
  7.    for next in graph.neighbors(current):
  8.       if next not in visited:
  9.          frontier.put(next)
  10.          visited[next] = True

複制代碼

現在你已經看見代碼了,試着進入上面的動畫。注意邊界隊列,關于current的代碼,還有next節點的集合。在每一步中,有一個邊界元素成為current節點,它的鄰居節點會被标注,而且沒有被拜訪過的鄰居節點會被添加到邊界隊列。有一些鄰居節點可能已經被通路過,他們就不需要被添加到邊界隊列裡面了。

這是一個相對簡單的算法,并且對于包括AI在内的很多事情都是有用的。我有三種主要使用它的辦法:

1.标記所有可達的點。這在你的圖不是完全連接配接的,并且想知道哪些點是可達的時候是很有用的。這就是我再上面用visited這部分所做的。

2.找到從一個點到所有其他點或者所有點到一個點的路徑。我在文章開頭的動畫demo裡面使用了它。

3.測量從一個節點到所有其他節點的距離。這在預先知道一個移動中的怪物的距離時是很有用的。

如果你正在生成路徑,你可能會想知道從每個節點移動的方向。 ​​當你通路一個鄰居節點的時候,要記得你是從哪個節點過來的。讓我們把visited重命名為came_from并且用它來記錄之前位置的軌迹:

  1. came_from = {}
  2. came_from[start] = None
  3.       if next not in came_from:
  4.          came_from[next] = current

我們來看看它是這樣的:

塔防遊戲的路徑尋找算法分析

如果你需要距離,你可以在起始節點将一個計數器設定為0,并在每次通路鄰居節點的時候将它加一個鄰居節點。讓我們把visitd重命名為distance,并且用它來存儲一個計數器:

  1. distance = {}
  2. distance[start] = 0
  3.       if next not in distance:
  4.          distance[next] = 1 + distance[current]
塔防遊戲的路徑尋找算法分析

如果你想同時計算路徑和距離,你可以使用兩個變量。

這就是廣度優先檢索算法。對于塔防風格的遊戲,我用它來搜尋所有位置到一個指定位置的路徑,而不是重複使用A*算法為每個敵人計算路徑。我用它來尋找每一個移動的怪物的指定行動距離内所有的位置。我也是用它來進行程式化地生成地圖。Minecraft使用它來進行可見性剔除。這是一個好的算法。

接下來的步驟:

我有實作python和c++代碼。

如果你想要找到從一個點出發而不是到達一個點的路徑,隻需要在檢索路徑的時候翻轉came_from指針。

如果你想要多個點路徑而不是一個點的路徑,你可以在圖的邊緣為你的每個目标點添加一個額外的點。額外的點不會出現在網格中,但是它會表示在圖中的目标位置。

提前退出:如果你是在尋找一個到達某一點或從某一點出發,。我在A*算法的文章當中描述了這種情況。

權重邊:如果你需要不同的移動成本,廣度優先搜尋可以替換為為Dijkstra算法。我在A*算法的文章當中描述了這種情況。

啟發:如果你要添加一種指導搜尋目标的方法,廣度優先算法可以替換為最佳優先算法。我在A*算法的文章當中描述了這種情況。