如何快速搭建一個簡單的塔防小遊戲

C語言是全部編程語言的基礎,當咱們對C語言有足夠深刻的理解後,就能輕鬆入門其餘語言,好比如今流行的Python。如今,我將帶領你們看一個基於C語言經典算法,使用Python編寫的塔防小遊戲。python

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

01

咱們如何來實現這種效果?算法

像A*這樣的圖搜索算法常常被用來尋找兩點之間的最短路徑。你能夠用這個來爲每個敵人找到前往目標的路徑。在這種類型的遊戲當中,咱們有不少不一樣的圖搜索算法來。這是一些經典方法編程

單源,單目標:

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

單源多目標或多源單目標

  • 廣度優先算法-無加權邊
  • Dijkstra算法-有加權邊
  • Bellman-Ford算法-支持負權重

多源多目標

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

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

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

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

邊界隊列是一個圖節點(網格瓦片)是否須要被分析的列表/數組。它最開始僅僅包含一個元素,起始節點。每一個節點上的訪問標誌追蹤咱們是否採訪過該節點。開始的時候除了起始節點都標誌爲FALSE。使用滑塊來查看邊界是如何擴展的:spa

02

這個算法是如何工做的?在每一步,得到一個元素的邊界並把它命名爲current。而後尋找current的每一個鄰居,next。若是他們尚未被訪問過的話,將他們都添加到邊界隊列裏面。下面是一些python代碼:3d

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

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

如今已經看見代碼了,試着步進上面的動畫。注意邊界隊列,關於current的代碼,還有next節點的集合。在每一步,有一個邊界元素成爲current節點,它的鄰居節點會被標註,而且未被拜訪過的鄰居節點會被添加到邊界隊列。有一些鄰居節點可能已經被訪問過,他們就不須要被添加到邊界隊列裏面了。指針

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

1.標識全部可達的點。這在你的圖不是徹底鏈接的,而且想知道哪些點是可達的時候是頗有用的。這就是我再上面用visited這部分所作的。

2.尋找從一個點到全部其餘點或者全部點到一個點的路徑。我在文章開始部分的動畫demo裏面使用了它。

3.測量從一個點到全部其餘點的距離。這在想知道一個移動中的怪物的距離時是頗有用的。

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

Python

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

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

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

03

若是你須要距離,你能夠在起始節點講一個計數器設置爲0,並在每次訪問鄰居節點的時候將它加一。讓咱們把visitd重命名爲distance,而且用它來存儲一個計數器:

Python

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]

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]

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

04

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

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

下一步

我有python和c++代碼的實現。 若是你想要找到從一個點出發而不是到達一個點的路徑,只須要在檢索路徑的時候翻轉came_from指針。

若是你想要知道一些點而不是一個點的路徑,你能夠在圖的邊緣爲你的每一個目標點添加一個額外的點。額外的點不會出如今網格中,可是它會表示在圖中的目標位置。 提早退出:若是你是在尋找一個到達某一點或從某一點出發,。我在A*算法的文章當中描述了這種狀況。

加權邊:若是你須要不一樣的移動成本,廣度優先搜索能夠替換爲爲Dijkstra算法。我在A*算法的文章當中描述了這種狀況。

啓發:若是你須要添加一種指導尋找目標的方法,廣度優先算法能夠替換爲最佳優先算法。我在A*算法的文章當中描述了這種狀況。

若是你從廣度優先算法,而且加上了提早退出,加權邊和啓發,你會獲得A*。如你所想,我在A*算法的文章當中描述了這種狀況。

滿滿的自豪感,真的很想知道你們的想法,還請持續關注更新,更多幹貨和資料請直接聯繫我,也能夠加羣710520381,邀請碼:柳貓,歡迎你們共同討論

相關文章
相關標籤/搜索