这篇文章是系列文章的一部分,我在其中记录我从头开始构建ECS游戏引擎的经验。 请查看 该项目 的 主页,以 获取更多帖子,信息和源代码。
我从读者那里收到了一些有关发动机各部分如何装配在一起的信息。 从理论上讲,我们已经介绍了这一点,但是当涉及到实际代码时,就缺少了一个部分-世界。
这个世界(主要是)是我们正在应用于ECS的一个细节(类似于我们在上一篇文章中添加的句柄)。 它实际上并没有太多功能,它主要在某些情况下提供通信,例如:
- 系统想要检索实体的一些组件
- 所有系统都需要通知某些事情(例如,添加或删除组件/实体)
- 需要在所有系统上运行一个方法(例如
update()
)
提供这些功能的方法有几种,但是我选择使用的模型是“世界”。 世界在ECS的所有不同部分之间提供了互操作性-组件管理器,系统和实体管理器。 可能有多个世界,每个世界都有自己的组件管理器和系统,但是在较简单的情况下,可能只需要一个世界(我们将在以后的文章中介绍具有多个世界的场景)。
在进一步介绍之前,我想简要说明构建此引擎时的主要目标之一:我想尽可能多地抽象出ECS / Engine的底层实现,并让引擎的用户仅通过简单的方式与之交互和知名的场地。 整个世界通过路由很多行为来帮助实现这一目标。 例如,世界没有直接调用实体管理器来创建实体,而是“拥有”实体管理器并公开了一种方法,该方法调用实体管理器并将返回的实体包装在句柄中。 另外,当我们将来处理多线程时,我们将在其中管理一些多线程。
从这个阶段开始,为了简单起见,我们将在概念上将一个世界视为“拥有”系统和组件管理器。 这样,我们具有以下层次结构:
显然,这造成了所有系统在世界范围内进行呼叫的瓶颈,但是由于没有向世界添加任何功能,因此我们可以假设这不会对性能造成巨大的影响。
世界范围的界面将使您真正了解其实际需要涵盖的内容:
因为世界坚持使用对所有系统以及所有组件管理器的引用,所以我们可以为引擎用户提供简单的界面。 我将在下面讨论细节。
该实现是我的引擎结构与我看到的许多其他引擎不同的地方之一。 我将在另一篇博客文章中讨论我选择该设计的逻辑以及探索替代方案。 现在,我们将深入研究我的实现。
核心思想是,世界始终知道给定实体在任何给定时间具有哪些组件。 请注意,此实现假定给定实体上每种类型最多包含一个组件。 世界也知道每个系统在乎哪些组件,并保留一个“掩码”,该掩码表示当前在实体上的哪些组件。 当掩码更改时,将对照系统掩码检查实体掩码,以查看系统是否应处理该实体。 此图像可能有助于清除故障:
在上面的示例中,像Movement系统这样的系统需要对具有Transform
和Motion
实体进行操作。 因此,系统掩码将为110000
。 同样,处理玩家输入的系统可能需要Motion
和Joystick
,在这种情况下,系统掩码为010010
。
ComponentMask结构的实现如下:
这就是我们在世界上使用它的方式:
这看起来像一个令人生畏的代码,但实际概念非常简单。 希望在文本描述,图像和代码之间都有意义。 与往常一样,如果有任何不清楚的地方,请随时发表评论或给我发送电子邮件。
我留给读者一些方法来实现,特别是destroyEntity
和addSystem
。
尽管世界上没有游戏逻辑,功能也很少,但它为Nomad引擎的许多模块间功能提供了一个中心枢纽。 通过将所有这些逻辑放在一个位置,可以更轻松地对整个引擎进行推理。
您可能期望在引擎上的下几篇文章将讨论有关可能的世界范围实现的讨论,然后是有关ECS引擎(特别是Nomad)的事件系统的帖子。 如果引擎的任何部分令人困惑,请随时告诉我!