演算法:塔防遊戲中的路徑尋找

sunday0314發表於2014-12-26

在塔防遊戲中,有許多敵人向著同一目標前進。在很多塔防遊戲當中,有一條或幾條事先預定好的路徑。在一些中,比如經典的《Desktop Tower Defense》,你可以將塔放在任何位置,它們充當障礙影響敵人選擇的路徑。試一試,點選地圖來移動牆壁:

(原文中的可播放的動畫,點選跳轉檢視

我們如何來實現這種效果?

像A*這樣的圖搜尋演算法經常被用來尋找兩點之間的最短路徑。你可以用這個來為每一個敵人找到前往目標的路徑。在這種型別的遊戲當中,我們有很多不同的圖搜尋演算法來。這是一些經典方法

單源,單目標:

單源多目標或多源單目標

多源多目標

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

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

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

邊界佇列是一個圖節點(網格瓦片)是否需要被分析的列表/陣列。它最開始僅僅包含一個元素,起始節點。每個節點上的訪問標誌追蹤我們是否採訪過該節點。開始的時候除了起始節點都標誌為FALSE。使用滑塊來檢視邊界是如何擴充套件的:

(原文中的可播放的動畫,點選跳轉檢視

這個演算法是如何工作的?在每一步,獲得一個元素的邊界並把它命名為current。然後尋找current的每個鄰居,next。如果他們還沒有被訪問過的話,將他們都新增到邊界佇列裡面。下面是一些python程式碼:

現在已經看見程式碼了,試著步進上面的動畫。注意邊界佇列,關於current的程式碼,還有next節點的集合。在每一步,有一個邊界元素成為current節點,它的鄰居節點會被標註,並且未被拜訪過的鄰居節點會被新增到邊界佇列。有一些鄰居節點可能已經被訪問過,他們就不需要被新增到邊界佇列裡面了。

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

1.標識所有可達的點。這在你的圖不是完全連線的,並且想知道哪些點是可達的時候是很有用的。這就是我再上面用visited這部分所做的。
2.尋找從一個點到所有其他點或者所有點到一個點的路徑。我在文章開始部分的動畫demo裡面使用了它。
3.測量從一個點到所有其他點的距離。這在想知道一個移動中的怪物的距離時是很有用的。

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

我們來看看它看起來是怎樣的:

(原文中的可播放的動畫,點選跳轉檢視

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

我們來看看它看起來是怎樣的:

(原文中的可播放的動畫,點選跳轉檢視

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

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

下一步

  • 我有python和c++程式碼的實現。
  • 如果你想要找到從一個點出發而不是到達一個點的路徑,只需要在檢索路徑的時候翻轉came_from指標。
  • 如果你想要知道一些點而不是一個點的路徑,你可以在圖的邊緣為你的每個目標點新增一個額外的點。額外的點不會出現在網格中,但是它會表示在圖中的目標位置。
  • 提前退出:如果你是在尋找一個到達某一點或從某一點出發,。我在A*演算法的文章當中描述了這種情況。
  • 加權邊:如果你需要不同的移動成本,廣度優先搜尋可以替換為為Dijkstra演算法。我在A*演算法的文章當中描述了這種情況。
  • 啟發:如果你需要新增一種指導尋找目標的方法,廣度優先演算法可以替換為最佳優先演算法。我在A*演算法的文章當中描述了這種情況。
  • 如果你從廣度優先演算法,並且加上了提前退出,加權邊和啟發,你會得到A*。如你所想,我在A*演算法的文章當中描述了這種情況。

相關文章