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

简介: C语言是所有编程语言的基础,当我们对C语言有足够深入的理解后,就能轻松入门其他语言,比如现在流行的Python。现在,我将带领大家看一个基于C语言经典算法,使用Python编写的塔防小游戏。

C语言是所有编程语言的基础,当我们对C语言有足够深入的理解后,就能轻松入门其他语言,比如现在流行的Python。现在,我将带领大家看一个基于C语言经典算法,使用Python编写的塔防小游戏。

在塔防游戏中,有许多敌人向着同一目标前进。在很多塔防游戏当中,有一条或几条事先预定好的路径。在一些中,比如经典的《Desktop Tower Defense》,你可以将塔放在任何位置,它们充当障碍影响敌人选择的路径。试一试,点击地图来移动墙壁:

01

我们如何来实现这种效果?

像A*这样的图搜索算法经常被用来寻找两点之间的最短路径。你可以用这个来为每一个敌人找到前往目标的路径。在这种类型的游戏当中,我们有很多不同的图搜索算法来。这是一些经典方法

单源,单目标:

  • 贪心搜索算法
  • A*算法 – 在游戏当中常用

单源多目标或多源单目标

  • 广度优先算法-无加权边
  • Dijkstra算法-有加权边
  • Bellman-Ford算法-支持负权重

多源多目标

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

像《Desktop Tower Defense》这样的游戏会有很多个敌人(源)和一个共同的目的地。这使得它被归为多源单目标一类。我们可以执行一个算法,一次算出所有敌人的路径,而不是为每个敌人执行一次A*算法。更好的是,我们可以计算出每个位置的最短路径,所以当敌人挤在一块或者新敌人被创建时,他们的路径已经被计算好了。

我们先来看看有时也被称作“洪水填充法”(FIFO变种)的广度优先算法。虽然图搜索算法是适用于任何由节点和边构成的图,但是我还是使用方形网格来表示这些例子。网格是图的一个特例。每个网格瓦片是图节点,网格瓷砖之间的边界是图的边。我会在另一篇文章当中探讨非网格图。

广度优先搜索始于一个节点,并访问邻居节点。关键的概念是“边界”,在已探索和未开发的区域之间的边界。边界从原始节点向外扩展,直到探索了整张图。

边界队列是一个图节点(网格瓦片)是否需要被分析的列表/数组。它最开始仅仅包含一个元素,起始节点。每个节点上的访问标志追踪我们是否采访过该节点。开始的时候除了起始节点都标志为FALSE。使用滑块来查看边界是如何扩展的:

02

这个算法是如何工作的?在每一步,获得一个元素的边界并把它命名为current。然后寻找current的每个邻居,next。如果他们还没有被访问过的话,将他们都添加到边界队列里面。下面是一些python代码:

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

目录
相关文章
|
数据采集 算法 数据挖掘
MyEMS:2025 年能源管理系统性价比之巅的实力担当
在全球节能减排趋势下,MyEMS凭借高效能、低成本和智能化管理,成为2025年备受推崇的能源管理系统,助力工业、商业、医疗等多领域实现节能降本与可持续发展。
289 0
|
设计模式 网络协议 Java
07.迪米特原则介绍
本文详细介绍了迪米特原则(Law of Demeter),包括其定义、核心思想及实际应用。迪米特原则强调类之间应保持低耦合,通过限制对象间的直接交互,减少依赖关系,从而提高代码的可维护性和复用性。文章通过案例分析(如集团公司员工管理和体育课场景)展示了如何遵循该原则优化设计,避免不必要的类依赖。同时探讨了“高内聚、松耦合”的概念及其重要性,并总结了迪米特原则的优点与潜在缺点。最后列举了外观模式、中介者模式等符合迪米特原则的设计模式,帮助读者深入理解其应用场景。
479 18
|
Java API 微服务
2025 年 Java 核心技术全面升级与实战应用详解
这份Java校招实操内容结合了最新技术趋势,涵盖核心技术、微服务架构、响应式编程、DevOps及前沿技术等六大模块。从函数式编程到Spring Cloud微服务,再到容器化与Kubernetes部署,帮助你掌握企业级开发技能。同时,提供AI集成、区块链实践和面试技巧,包括高频算法题与系统设计案例。通过学习这些内容,可应对90%以上的Java校招技术面试,并快速上手实际项目开发。资源链接:[点此获取](https://pan.quark.cn/s/14fcf913bae6)。
1134 41
|
搜索推荐 测试技术 C语言
NPU适配推荐系统GR模型流程
本示例将开源Generative Recommendations模型迁移至NPU训练,并通过HSTU融合算子优化性能。基于Atlas 800T A2平台,使用PyTorch 2.1.0、Python 3.11.0等环境。文档涵盖容器启动、依赖安装、算子适配、源码修改、数据预处理及配置文件设置等内容。性能测试显示,使用HSTU融合算子可显著降低端到端耗时(如ml_1m数据集单step从346ms降至47.6ms)。
|
负载均衡 监控 应用服务中间件
配置Nginx反向代理时如何指定后端服务器的权重?
配置Nginx反向代理时如何指定后端服务器的权重?
836 61
|
SQL 机器学习/深度学习 大数据
【赵渝强老师】Flink生态圈组件
Flink 是一个大数据计算引擎,支持批处理和流处理。其优势在于流处理引擎 DataStream。Flink 的生态圈分为三层:平台部署层、核心引擎层和 API&Library 层。平台部署层支持多种部署模式,核心引擎层负责任务执行,API&Library 层提供丰富的开发工具。
410 1
|
存储 Java Maven
使用Java实现OAuth 2.0认证授权
使用Java实现OAuth 2.0认证授权
2278 0
|
SQL 存储 运维
数据库生态工具&架构方案| 学习笔记(一)
快速学习数据库生态工具&架构方案
1320 0
数据库生态工具&架构方案| 学习笔记(一)
|
机器学习/深度学习 人工智能 自然语言处理
音频基座大模型FunAudioLLM评测
通义语音团队最新开源了音频基座大模型FunAudioLLM,包含两大模型SenseVoice和CosyVoice。本文对这两种大模型进行整体的评测。
937 1
音频基座大模型FunAudioLLM评测
|
机器学习/深度学习 编解码 算法
在线打开CAD或Solidworks的STP文件,通过以图搜图与实物比对搜索
智能比对系统利用大模型技术,实现设计图纸与实物的高效、精准比对。系统支持在线3D模型解析、多视图图片自动生成、实物照片智能比对及实时偏差标注,全面提升机械制造行业的设计、生产和质量控制效率。
1169 2