如何快速搭建一个简单的塔防小游戏

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,邀请码:柳猫,欢迎你们共同讨论

相关文章
相关标签/搜索