70 万行代码、20 年和一名开发人员:矮人要塞是如何构建的

2021-07-30 00:41:28

矮人要塞是闯入互联网意识的那些古怪的激情项目之一。这是一款免费游戏,您可以在随机生成的幻想世界中扮演冒险家或充满矮人的堡垒。模拟运行深入,新游戏创造了多个具有历史、神话和文物的文明。它已经臭名昭著,这是正确的。个别矮人有情绪状态、最喜欢的宝石和怨恨。这一切都发生在一个 ASCII 界面中,对新手来说看起来很威风,但感觉就像矩阵中的文字:手工艺侏儒,河流,传奇巨兽。整个游戏是开发者 Tarn Adams(又名 Toady One)的产品,他自 2002 年以来一直在 Dwarf Fortress 工作。前四年它是一个兼职项目,但自 2006 年以来一直是全职项目。他自己编写所有代码,尽管他的兄弟帮助设计并根据游戏创建故事。到目前为止,他依靠捐款来维持生计,但他目前正在开发一个带有像素图形和改进后的用户界面的版本,可以在 Steam 上购买。我联系了 Tarn Adams,想了解他在 15 年多的时间里如何管理一个不断增长的代码库、路径的危险以及调试死猫。为了清楚起见,我们对下面的对话进行了编辑。问:您使用哪些编程语言和其他技术?基本上,你的堆栈是什么?在您这样做的 15 到 20 年间,这种情况发生了变化吗?答:DF 是 C 和 C++ 的某种组合,不是以某种标准的服从方式,而是随着时间的推移而产生的混乱。自 MSVC 6 以来,我一直在使用 Microsoft Visual Studio,但现在我使用的是 Visual Studio 社区的某个版本。我使用 OpenGL 和 SDL 来处理引擎问题。我们选择了那些,因为将它们移植到 OSX 和 Linux 更容易,尽管我自己当然仍然无法做到这一点。如果我有选择,我不确定现在是否会使用 Unity 或 Unreal 之类的东西,因为我不知道如何使用它们中的任何一个。但是处理你自己的引擎也是一个真正的痛苦,特别是现在我正在做一些超出文本图形的事情。我将 FMOD 用于声音。

所有这一切在项目过程中一直保持不变,只是几年后引入了 SDL,因此我们可以进行移植。在游戏的机械方面,我不使用很多外部库,但我偶尔会使用一些随机数生成器——很久以前我放入了一个 Mersenne Twister,最近我采用了 SplitMix64,这是在上一届 Roguelike 庆典的演讲中提到的。 Q : 开发一个项目这么长时间有什么挑战?你认为这更容易自己做吗?也就是说,因为你写了每一行,是不是更容易维护和改变? A:很容易忘记东西!搜索“;”,这是一种松散的方法,但足够接近,我们有多达 711,000 行,所以现在不可能把它全部记在我的脑海里。我尝试一致且令人难忘地命名我的变量和对象,并在周围留下足够的注释以提醒自己当我到达某个代码点时发生了什么。有时,当我去重温我十年未接触过的游戏的某个部分时,需要多次搜索才能找到我想要拉扯的确切线索,这种情况经常发生。我想说大多数改动都只集中在游戏的某些部分,所以有一种活跃的熔化核心,我有更好的工作知识。在 2006 年首次发布之前,我还没有看过一些非常棘手的部分。 关于自己做事的相对容易程度,当然对于我来说,我没有从事大型多人项目的工作经验,这个是要走的路!人们显然擅长以另一种方式做这件事,例如在 AAA 游戏环境中,并且显然需要多名工程师来按时完成工作。我会犹豫地说我可以比他们更快地进入并改变东西,因为我之前没有在这种情况下工作过,但确实我没有任何以团队为导向或官僚主义的障碍可以跳过通过当我想改变。我可以去做。但我也必须独自完成。 A:有一些重构持续了几个月,重做某些数据结构等等,虽然我不确定这里有什么是严格的重构,因为总是有机会同时推动机制向前发展,这样做是有意义的所以当代码知识新鲜时。添加 Z 坐标使游戏机械化 3D(同时仍然是文本)是另一种方式,这可能是我做过的最令人麻木的事情。仅仅数周又数周的时间进行依赖于 X 和 Y 的逻辑和函数调用,并查看 Z 是如何适应的。使物品系统多态最终是一个错误,但这是一个很大的错误。

答:当你声明一个类是一种项时,它会将你锁定在该结构中,而不是你只有成员元素。能够使用虚函数之类的东西很好,但是权衡太多了。我开始在层次结构中使用“工具”项目,它开始获得各种功能,现在可以支持从梯子到蜂箱到研钵(和杵,分别地,哈哈)的任何东西,而且感觉更灵活,我希望游戏中的每一个精心制作的物品都在这把伞下。我们做了很多程序生成,如果我们想生成一个部分像一个东西而部分像另一个东西的项目,当你被锁定在一个类层次结构中时,要做到这一点就更难了。添加诸如钻石依赖项之类的东西,当有更简洁的方法来做时,所有这些最终都会让您束手无策。如果可以关闭和打开不同的组件,那就更容易了,并且可以让您做更多的事情。我认为一些游戏开发人员将其称为实体组件系统,但我的理解是,核心优化器的人认为它是其他东西,您实际上在其中按各个领域分解事物。对于缓存未命中,使用具有不同分配子对象的单个对象几乎肯定会更糟,这是另一回事,但在组织、灵活性和可扩展性方面的好处是不容忽视的,工具项的不同子字段是t 使用太频繁以至于它变成了一个优化问题。问:您从 32 位迁移到 64 位时遇到过任何问题吗?这感觉就像是当时很重要但已被广泛接受的事情之一。答:完全没有!我正在努力思考一个问题。对我们来说幸运的是,我们已经很好地控制了我们的字节大小,因为它会保存和加载世界;当我们设置它时,需要确定格式,特别是因为我们不得不处理操作系统之间的字节序等等。而且我们不做任何粗糙的指针操作或其他可能给我们带来麻烦的东西。由于我们的其他实践,它最终成为非常好的 64 位转换代码,完全是偶然的。主要问题只是让时间聚在一起做出改变,然后它并没有像我想象的那么长。问:我见过类似 DF 的其他游戏在他们的寻路算法上死亡。你使用什么以及如何保持效率? A:是的,基本算法只是其中的一部分。我们使用 A*,它当然很快,但它本身还不够好。我们不能利用这方面的一些创新(例如跳跃点),因为我们的地图变化太大了。通常,人们使用在地图顶部添加各种较大结构的方法来偷工减料,并且由于地图的变化,这些维护时间太长,否则很麻烦。所以我们的方法一直是跟踪可通过步行到达的连接组件。即使地图快速变化,这些也很容易更新,尽管它确实涉及一些洪水填充。例如,如果水将堡垒切成两半,它需要从一侧涌出并将堡垒的整个一半更新为新的索引,但是一旦完成,一般来说就很好了。这样我们就可以从游戏中删除几乎所有失败的 A* 调​​用——我们的代理只需要查询组件编号,如果组件编号相同,他们就知道调用会成功。

维护速度很快,但缺点是组件索引仅用于步行。例如,这意味着飞行生物没有与步行者有任何不同的全局寻路智能。在战斗和其他一些情况下,我们使用带有实际逻辑的短程洪水填充来给他们一些优势。但这对他们来说并不理想。我不确定我们会在这里尝试其他结构以使其工作得更好。对于我们的地图尺寸,它们都失败了,包括一些外部尝试。当然,通过真正齐心协力的努力,这可能是可能的,而且我已经看到其他游戏已经管理,例如,一些矩形叠加等等,看起来很有希望,但我不确定他们的地图有多大的波动或大.最简单的想法就是为传单添加一个新索引,但这会占用大量内存和速度,因为我们需要同时维护两个索引,而一个已经够糟糕了。更具体的叠加层可以跟踪它们的路径属性(然后您可以通过叠加层而不是图块进行路径选择),但是随着地图的变化,它们难以维护且速度缓慢。还有各种各样的其他想法,比如跟踪楼梯,或者做一些有限的路径缓存,可能会有一些收获。在代理和地图复杂性方面,我们当然处于我们目前可以支持的边缘,所以如果我们想从中获得更多,就必须付出一些努力。问:关于这一点,你同时模拟了很多东西——你如何异步管理这么多这么多的演员(或者你自己)?答:如果我们在讨论多线程中的异步,那么不,除了图形显示本身之外,我们不做任何事情。这里有很多希望,即使是微线程,社区已经帮助我解决了这个问题,但我没有时间深入研究。我没有任何经验,这是一个容易出错的事情。答:当然!过去十年左右在计算机之间迁移的副项目文件夹中包含大约 90 个项目。有的持续数天,有的持续数年。它们大多是其他游戏,几乎总是其他类型的,但也有一些 DF 辅助项目,例如神话生成器原型。没有什么可以接近日光的,但玩起来很有趣。问:在您的大约 90 个副项目中,您是否探索过其他编程语言?如果有,有什么喜欢的吗?

答:哈哈,没有!我更喜欢设计方面的面条,而不是技术。我确信有些事情确实会加速我的设计的实现,所以我可能至少应该学习一些脚本并更多地玩线程。人们甚至很乐意提供一些图书馆和东西来帮助那里,但是当我的业余项目时间用于放松时,很难阻止业余项目时间用于技术学习。问:你们有最有趣的发行说明。你最喜欢的错误是什么,是什么导致了它? A:我说起来可能很无聊,但我就是打不过醉猫虫。到目前为止,已经有一些关于它的视频。那是猫在酒馆地板上到处都是死的地方,结果证明它们在清洁爪子时摄入了溢出的酒精。一个数字在清理时摄入代码中被关闭,它让他们经历了酒精中毒的所有症状(我们在整理有毒生物时添加了这一点。)标签:矮人要塞,独立开发者,电子游戏