遥不可及:大火如何燃烧和蔓延

2020-12-16 14:05:42

几年前,我有机会在《孤岛惊魂2》中设计并编写火警蔓延系统。当时,这是一项艰巨的任务,它吓坏了我。幸运的是,结果足够好。

随着即将发布的《孤岛惊魂3》,最近有几个人问我该系统如何工作。我意识到自己从来没有花时间写下来。因此,在我忘记之前,也因为它可能对外面的人有用,这里是其内部运作的高级概述。包括精美的程序员艺术作为奖励。

免责声明:尽管《孤岛惊魂3》使用的是我编写的相同系统,但我没有参与该项目。他们可能已经或可能未更改/改编或修改了算法。我在下面描述的内容适用于《孤岛惊魂2》。

从根本上讲,火灾蔓延系统非常简单。由于游戏性很重要,因此我们牺牲了一些逼真的乐趣。 《孤岛惊魂2(Far Cry 2)》和《孤岛惊魂3(Far Cry 3)》中的火势蔓延具有足够的真实感,足以维持玩家的怀疑度,但肯定不足以在短期内发布。因为我喜欢保持简单,所以它不涉及任何复杂的数学,物理学或流体动力学。这具有快速仿真和易于理解的附加优势。

秘制酱是等距网格。对于草,物体以及树木都是如此。唯一的区别是我们为草使用2D网格,为物体和树木使用3D网格。

网格的每个像元在世界上都有位置,半径和生命值。单元具有过多的属性,但这三个是传播火力的最低要求。

游戏引擎会跟踪游戏中造成的所有损坏,可能是子弹射击,撞击或火灾损坏。当游戏实体损坏时,会通过事件通知它。损坏事件包括造成的损坏程度,损坏的种类和原因。如果这种损坏是基于火的,并且实体是易燃的,那么至少会发生两件事:

首先,为受损实体动态创建防火网格。我们动态创建它们是因为我们不希望这些网格无缘无故地存在。这将占用内存,磁盘空间等,因此我们在进行过程中创建它们。但是,一旦创建,它就会一直存在,只要游戏实体存在即可。

其次,我们找出网格中哪个单元最靠近损坏源。然后该细胞受到伤害,其生命值相应降低。

那就是事情变得有趣的地方–当一个单元被大火损坏并失去所有的生命力时;它着火了。

燃烧时,细胞本身成为破坏者。它会对网格上的相邻单元造成损害;与之共享优势的单元。这样,它降低了其生命值,而当这些单元又失去所有生命值时,它们会着火。这就是传播的创建方式。

最后,来自网格的电池寿命有限。否则,它将永远燃烧。可以将其视为燃烧的材料所提供的能量。例如,一张纸最有可能比木头原木燃烧得快,因此可以得到较短的使用寿命值。

而已。一个功能完备的火焰传播系统,足以应付AAA游戏!

模拟湿丛林VS干草如何模拟干丛林和干草?他们的行为有所不同。很容易!增大火场的生命值,很难使其着火并且传播缓慢。减少其燃烧寿命,大火很快消失。在那里,您只是模拟了潮湿的丛林环境。

草地/陆地对于草地野火,将实时构建与轴线对齐的2D网格并将其投影在3D地形上。对于每一个牢房,我们都会仔细确定牢房位置是在水下还是在岩石或建筑物等物体下方。在这种情况下,我们将禁用该单元,它不再会着火。我们不想让火在岩石下传播,是吗?

对象对于对象,它有点复杂。首先,我们创建一个完全包围对象的AABB。其次,我们某种程度上需要检测该物体的形状。毕竟,据我们所知,它可能是椅子,油桶或整座房子。为此,将边界框划分为等距的多维数据集。每个多维数据集的大小取决于对象的大小,我们想要的发射器数量,性能,内存等。

然后,迭代算法遍历所有这些多维数据集,并针对对象的碰撞形状测试其位置。如果测试返回阳性结果,则保留此多维数据集,否则将其丢弃。最后,我们有一个多维数据集集合,它们近似表示要燃烧的对象的形状。那就是我们的传播网格。

风是造成野火的重要破坏因素,它为玩家增加了极大的真实感。在这里,可能会过度考虑设计并选择非常复杂的系统。毕竟,这是一个活跃的研究领域,有关该主题的几篇论文可以在线获得。

幸运的是,到目前为止,我们已经解释了这一点,如果我们接受一些捷径,就很容易模拟。

在我们的系统中,火灾是通过损坏网格上的相邻单元来传播的。人们普遍认为,火在风向传播比在风向传播更快?然后,考虑到这一点,我们可以创建一条规则,即如果燃烧的邻居的邻居电池朝向风向,则对其燃烧的邻居电池造成更大的损害。

我们通过使风向矢量与相邻单元的方向之间的点积损坏来实现。如果结果大于零,则对该节点造成更大的损害。同样,如果结果为负数,则该节点不受风影响,应受到的损害较小。为方便起见,将损坏程度与点积结果内插,如下图所示。

仅凭该规则,您将获得一个漂亮的钟形火前风,该风前风沿风向传播。简单但令人信服,足以获得玩家的认可。

电池燃烧时,它会向游戏事件管道发送``我着火了,我在这个半径内燃烧了这么多''消息。该事件被该区域中的对象,AI和其他游戏系统捕获。他们以自己的特定方式对此消息做出反应。 AI吓坏了,易燃物品损坏,最终着火。而且,对于动态对象也是如此。例如,如果一个燃烧的油桶在地图上飞来飞去,它的每一步都会发出“我在燃烧”的信息,周围的许多事物都可能听到。

这会引起连锁反应,树木,爆炸物,物体和草丛会彼此着火。它完全是系统性的,因此更加可信。它在玩家的娱乐仪表上的得分也很高,因为它的表现符合人们的预期,但实际上,它有时甚至完全失控了。

在理想的世界中,我们将有足够的内存来容纳无限数量的粒子发射器,并且我们可以在屏幕上显示无限数量的粒子。不幸的是,现实情况是这些数字实际上很低。

为了用更少的钱做更多的事情,我们必须将发射器放到真正重要的位置。进入植发策略!在《孤岛惊魂2(Far Cry 2)》中,消防员不断地被传送,最重要的是,从摄像机的背面到正面。玩家的视点始终受到监控,看不见的发射器被移到也需要它们的更好位置。

此外,发射器的密度在靠近播放器的位置较高,而在距离增加时较低。如果可以的话,这是一种火灾LOD。如果情况变糟并且我们仍然需要更多的发射器,但没有可用的发射器,则可以增加粒子大小以使其具有更大的体积并在屏幕上填充更多的空间。

使用这种策略,以及其他一些策略,我们可以模拟几米宽的野火,并且粒子发射器的数量相对较少。

事件流水线由于所描述的系统试图避免所有复杂的数学运算,因此通常来说,在被CPU绑定之前,我们将被GPU绑定。

话虽如此,事件管道可能是瓶颈。当您只有几个电池着火时,它会很好地工作。但是,当您成千上万的人将他们的状态燃烧并将其广告宣传给全世界时,这是另一个故事。这可能会阻塞CPU的动脉。

我的诀窍是将正在燃烧的细胞重新分组为AABB组。这些团体将不断合并,分裂和改变形状以跟随火势的发展。然后将按AABB而不是每个单元发送事件,从而节省了大量的处理能力。此外,事件将分散在几个帧中,以分配负载并避免帧速率峰值。

保持控制。在您的游戏中,如果您不以某种方式限制传播,它将

有许多方法可以解决此问题,而我的方法不一定是最好的。我再次尝试找到最简单的解决方案。

我设计了一个闭环系统,在该系统中,火势蔓延,蔓延开来。这是一个例子。玩家将莫洛托夫扔进一块干燥的草丛中。例如,莫洛托夫给这片草地提供了60个铺展点。通过消耗草火,网格的每个火单元需要8个散布点。因此,火在死亡前可在7个细胞上传播。如果玩家投掷2个莫洛托夫而不是1个,它将产生更多的热量和更多的能量,从而为草丛提供120个传播点。这么多的积分足以消耗15个火力电池。然后它将传播得更远。

这并不完美,但它的行为足以使玩家满意,同时又能使开发人员保持理智并在预算范围内。

这样就完成了《孤岛惊魂》中火传播系统的高级概述。 我知道这听起来有些陈词滥调,但我觉得我只是在划伤表面。 我没有谈论粒子发射器,仅此而已就值得一整篇博客文章。 您也可能会发现该系统在真空状态下单独运行良好,但是当您将其投放到世界上时,情况就不一样了。 突然之间,它必须与构成游戏的所有其他组件完美融合。 它带来了挑战。 如果您达到了这一点,谢谢阅读。 希望您发现这些信息有用,并希望将其用于开发更大的产品! 如果是这样,请告诉我。