我在UnrealEngine5的r/UnrealEngine5上发布了这条内容,但也想在这里分享一下。

你听到了什么?你说了什么?

大家好,我和我的朋友一直在开发一个犯罪主题的合作游戏,游戏的主要特点是毁灭、结构模拟和生成的建筑。我们终于在一个满意的状态下了,作为团队的唯一开发者,我想来这里分享一下它的工作原理。

快速警告,我不是一个专家程序员或物理学家。我更多地模拟了这个系统,以便它看起来和感觉良好。关于游戏开发和UE5的一切我都是自学的,所以如果我说错了或不准确的话,请告诉我!我喜欢制作游戏,总是想提高我的技能。

首先,快速解释。你在视频片段中看到的(我称之为)"结构模拟"和自定义的毁灭。当你打倒一堵墙或一根柱子时,建筑物会相应地反应。如果那些墙壁或柱子支持建筑物的其他部分,并且它们被摧毁,那么它们支持的建筑物部分也会变得不受支持并会倒塌。

自定义的毁灭是指实际上建筑物组件和道具的破碎和分裂。我们在其中使用几何集合(Geometry Collections),结合自定义系统。

这个游戏的目的就是进入这些建筑物并把它们摧毁来赚钱。然而,唯一赚钱的就是你实际上摧毁的部分。如果建筑物倒塌得不正常,你就不会因为那样的摧毁而赚钱。当然,还有其他游戏后果和细节,但我在这里不想解释整个游戏。

现在,让我们更具体一点,技术上更细致。请注意,我倾向于冗长地说话,这很可能会写成我遇到的问题和我的解决方案。

结构模拟

让我们从系统的根源开始:结构模拟。建筑物本身是有效的邻居图,节点是模块,边是建筑物部件。一个模块是建筑物中的一个房间,包括建筑物部件。建筑物部件是柱子、墙壁和地板。

每个模块都有自己的“容量”和“负荷”值。容量是模块关联建筑物部件的结构贡献的局部值,和侧向支撑的“侧向支撑”值。负荷是模块注册的建筑物部件的质量和所有上级模块的负荷。当一个模块的负荷超过其容量时,模块会倒塌。如果一个模块倒塌,任何共享的建筑物部件如果对其他模块的完整性有贡献,那么根据新的完整性,其他模块也可能会倒塌。

从这个模型中可以想象,摧毁建筑物的一楼会导致上面的模块倒塌,而摧毁建筑物的中央模块不会导致建筑物倒塌(由于侧向支撑)。

这基本上就是结构模拟的内容。当然,我简化了,负荷和容量计算中还有更多的细节。总的来说,这就是它是如何工作的。

生成

生成是基于这个数据模型的。您可以在生成的3D矩阵中创建任意数量的节点以创建任意形状的建筑物。当然,模块是方形的,但这是一种艺术选择。

现在,让我们转到摧毁上。结构模拟是很酷的,但如果建筑物部件没有破碎和没有碎片,它就会像一座巨大的乐高一样倒塌。考虑到这个问题,以及玩家自己进行摧毁的事实,我需要在实时中将每个建筑物部件切成可信的碎片。

毁灭

这个是建筑物部件的关键部分。它们有两个“详细程度”的摧毁:玩家自定义的或由模拟驱动的。在这两种情况下,建筑物部件都需要看起来受损并在被摧毁时分裂。因此,我决定实施“分块”系统。当一个建筑物部件受损时,它会被切分成多个静态网格,组合起来代表完整的建筑物部件。

当一个玩家或任何其他有碰撞的物体对一个块造成足够的伤害时,块就会被切换到一个预先编写的几何集合中,然后分裂开来,给出了高详细程度的效果。GCs在这里非常好,因为碎片只是用于详细程度。我们不需要驱动任何游戏逻辑基于它们的碰撞或其他东西。

当建筑物部件需要被摧毁时,是否通过块的死亡或结构模拟,为了节省大量性能,我们将大约85%的剩余块脱离并用一个主 Niagara Emitter 来表示,该 Emitter 在 GPU 上运行并模拟物理。5%持久并模拟物理,而剩下的10%则立即脱离。

我想专注于 GPU Emitter,因为它很酷。致谢这个 guys 在 YouTube 上的视频:https://www.youtube.com/watch?v=0R7llmGGTXA&t=750s。以前,我不知道,但您可以通过只分发一个 Emitter 并为该 Emitter 提供世界变换和任何其他简单数据来大大节省 Niagara 性能,以便在需要时批量分发 Niagara 粒子。这使得我们可以“批量”分发 Niagara 粒子并以很高的性能。

因此,我采用了这个想法,并创建了一个粒子效果,可以渲染任何块网格的任何建筑物部件,给出网格索引(要渲染的网格)、一些材料参数(该网格需要的材料)、世界位置和世界旋转。这个粒子中模拟物理,添加阻力和其他效果来卖一个物理模拟的块。

最酷的部分是,使用 GPU Emitter,我们可以实际上使用距离场碰撞来检测粒子网格的碰撞事件。因此,根据这个,我可以在粒子网格与其他物体发生碰撞时,正确地反应。发生这种事件时,我会杀死粒子,根据速度是否足够高,杀死粒子并分发另一个粒子效果,并执行其他游戏逻辑。

因此,持久的碎片块可以被归类为“英雄资产”。它们提供了持久的可视化证据,能够交互,且当它们与足够的速度发生碰撞时,会造成伤害。

所有这些结合起来都得到了一个可视化正确的崩溃。建筑物每个部分都有可视化的碎片,因此建筑物看起来像是在崩溃。我们还得到了低级详细度的摧毁效果,感谢 GCs。

性能和多人游戏细节

最后,让我们看看复制、性能和客户端一致性。即使在GPU Niagara块Emiter的性能提升下,游戏仍然因为一个原因而CPU绑定:交换建筑物部件到块。

在大规模的摧毁中,数百到数千块可以在单个tick中被生成,从而导致单个tick的巨大峰值和帧跳。因此,我添加了一个摧毁管理器,根据摧毁的位置来控制摧毁的节奏。我们选择每个tick处理的模块数量,然后延迟剩余的模块到后续的tick。然后,我们在客户端上复制应该在每个tick上可视化摧毁的模块和建筑物部件,并且客户端上有自己的客户端碎片管理器来执行摧毁的可视化,并接收本地GPU模拟的碰撞事件。这给了我们一个“波纹”式的摧毁效果,幸运的是看起来不太糟糕。

这个系统保证每个客户端在同一tick上看到相同的摧毁效果。每个客户端也发送服务器GPU碰撞数据,服务器验证并执行游戏逻辑。它还保证了一个基本的性能,因为客户端只会处理服务器告诉它的建筑物部件数量。

最后一块 puzzle 是物理对象的复制。这个很简单,服务器模拟,复制转换,客户端模拟并添加力矩以纠正。

思考

我希望这可以让大家了解不同问题和考虑因素的摧毁游戏所涉及的。这个不是一个全面解释的实现,但它涵盖了每个高层次问题和我如何解决它们。通过这个经验,我可以理解为什么你很少看到非立方体摧毁游戏。希望你会在我的冗长的讲述中找到一些有趣的内容,如果你能从其中获得一些什么。现在,吃点 Ragù 回到开发吧。