为什么Godot不基于ECS的游戏引擎?

2021-02-27 09:06:24

为什么Godot不使用ECS的话题经常出现,因此本文将解释其背后的设计决策,并阐明Godot的工作原理。

ECS是视频游戏中常用的一种设计模式(尽管在软件行业的其余部分中很少使用),它包含一个基础实体(一个容器对象)和可以添加的组件。组件提供了数据以及与整个世界进行交互的方式。最后,系统独立工作,并对每个相似的组件执行操作。

这种设计在2010年代初期在游戏引擎和库中变得很普遍。主要的吸引力(除体系结构之外)是可以将组件数据放置在连续的内存中,从而改善缓存访问的事实。这是面向数据的优化的常见形式。

从结构上讲,ECS旨在通过支持组合来代替继承,类似于接口或多重继承在OOP中的工作方式。 ECS的主要优势在于组件是动态的(可以在运行时添加或删除)。

Godot通过提供既包含数据又包含逻辑的节点来使用更传统的OOP。它还大量使用继承。

那么,如果ECS提供更多好处,为什么还要使用这种类型的架构?答案需要分为两个部分:架构原因和优化原因。

但是,我认为这个问题应该重新表述为:"合成所提供的增加的灵活性是否值得额外付费?

这里的问题变得更加主观(因此,取决于每个开发人员的经验)。以我的个人经验,它更符合以下方面:

我发现继承要简单得多,尤其是在需要多态的情况下。扩展比组成需要更少的精力和整体代码。因此,Godot还通过继承来编写脚本。

继承似乎也更容易理解。这可以在Godot与其他技术的两种常见情况中得到证明:添加新节点:节点列表很整洁,并且通过继承很好地进行了组织,这可能是由于隐含了这种关系。

看一看场景树:节点仅做一件事,因此仅查看场景就很容易理解场景的作用。

综上所述,这并不意味着Godot由于使用继承而没有那么灵活。通过添加子节点,在Godot中仍然可以完美地进行合成,这与ECS中的合成类似。 Node类是轻量级的,可以扩展以执行所需的任何操作。

迁移到Godot的大多数用户推荐都强调,使用它的方式感觉更井井有条,缠着头也更容易。同样,这并不意味着Godot的处事方式比其他技术要好。这仅意味着,由于这个事实,大量尝试Godot的用户只是喜欢它。

ECS的另一个重要方面是声称它更易于实现面向数据的设计(由于数据和逻辑之间的分离)。这样做更快吗?绝对地!它要快得多。

再一次,应该问的真正问题是"值得吗?根据引擎和游戏架构的不同,差异很大。对于Godot的具体情况,答案取决于我们所讨论的领域:

Godot在物理,渲染,音频等方面使用了大量面向数据的优化。但是,它们是独立的系统,并且是完全隔离的。

利用ECS的大多数(如果不是全部)技术都在核心引擎级别上做到这一点,方法是用作基础架构并在其上构建其他所有内容(物理,渲染,音频等)。

相反,Godot,这些子系统都是独立且隔离的(并且适合服务器内部)。我发现这使代码更简单,更易于维护和优化(证明这是微小的Godot的代码库与其他游戏引擎相比,同时提供相似级别的功能)。

与传统的ECS系统相比,Godot(节点)中的场景系统通常级别很高。大多数情况都是通过信号回调发生的(例如,对象碰撞,需要重新粉刷某些东西,按下按钮等)。需要从用户端在Godot的每个帧中处理某些事情的情况非常少见,因为引擎将在内部进行管理,从而使用户摆脱了复杂性。

换句话说,作为引擎的Godot试图减轻用户的处理负担,而是着重于确定事件发生时的处理方式。这确保了用户不必为了编写大量游戏代码而进行的优化减少,并且成为Godot试图传达的观点的一部分,该观点应构成一种易于使用的游戏引擎。

在游戏逻辑方面使用ECS时,某些类型的游戏仍然(虽然到目前为止还不是大多数)在性能上有所提高。

这些通常是游戏,需要处理成千上万个对象上的游戏逻辑,因此必须进行面向数据的优化,因为移入CPU缓存的页面数量增加了几个数量级,从而严重影响性能(以及电池消耗)。移动设备)。

再次强调一点,虽然这种用例很少(相反,大多数游戏通常最多只有数百个对象,但是对于这些对象,如果不进行缓存优化,则其内存访问频率远远不够用),游戏确实需要缓存优化的事实确实存在。

一些策略游戏(虽然不是大多数,但可以同时使用成千上万个游戏单元)。

这是否意味着ECS是解决这些性能限制的唯一方法?不总是。同样,它取决于许多因素。

很多时候,您仍然可以依靠聪明的优化。有没有想过SimCity如何在Commodore 64上运行?它通过交替处理每帧要处理的图块来实现,因此不需要同时处理数千个图块(在6502 CPU上是不可能的)。

从不处理所有帧中的所有内容到为处理可见的(或靠近相机)的所有内容提供更高的优先级,优化的方式可能有所不同。通常,这些优化将需要处理的对象数量减少几个数量级,并且仍然比完全面向数据的性能更好。

您实际上是否需要ECS来编写面向数据的游戏逻辑?在许多情况下,将所有内容自己放入结构中并使用for循环进行迭代(或使用诸如OpenMP之类的线程扩展来迭代多个线程)更为简单。沙盒就是一个很好的例子,这种方法通常更好。

通常,与使用基于CPU的优化相比,GPU Compute可能是更好的选择。如今,大多数设备都支持它,而且这种支持将很快得到普遍支持。计算很容易编程,并且可以实现比CPU高出数倍的并行性能。

有效地开发OOP是困难的,并且需要很多年才能适应,但是其他任何体系结构也是如此。除了游戏之外,当今(如果不是大多数)的大量企业软件是通过利用面向对象的体系结构开发的,该体系结构已经被充分理解并证明能够用于任何规模的项目和团队(因此,请不要盲目相信人们告诉您OOP不好,或者它不能扩展)。

传统上,游戏是在非常受限的环境中运行的(与现代台式机相比,控制台是非常有限的硬件),这迫使程序员在资源使用方面非常保守。

如今,情况已不再如此,并且出现了一些游戏开发技术,这些技术可以牺牲一些这种效率,以换取更易用和更舒适的开发体验。

然而,结果仍然存在着许多关于更传统的游戏软件开发方法的神话或半数真理,随着时间的流逝,它们将逐渐消失。

旨在同时解决多个问题的体系结构和设计模式对于程序员来说非常诱人,而Godot的开发则采用了非常务实的方法。

尽管ECS确实可以带来很多好处,但是可以客观地证明对Godot目标有好处的案例非常有限,因此(至少对我们而言)很难证明更改架构是合理的,因为它们是例外,而不是通用规则(并且通常可以通过其他更简单的方法来实现优化)。

也就是说,如果您喜欢或需要使用ECS,我强烈建议检查Andrea Catania关于Godex的出色工作,该工作旨在为Godot带来高性能的ECS实施。 此外,我们正在讨论游戏能够使用当前的节点/场景系统选择性地分离数据/逻辑以实现更高性能的方法(理论上,这没有必要一定是ECS的唯一优势),因此请继续 敬请期待进一步的工作和更新!