如何在实体组件系统中避免“斑点系统”?


10

目前,我面临以下问题:

我正在尝试通过使用实体组件系统(ECS)来编写pong克隆。我自己写了“框架”。因此,有一个类管理具有所有组件的实体。然后是组件类本身。最后是我的系统,它仅获取具有系统所需组件的所有实体。

因此,例如,我的运动系统会查找具有位置分量和运动分量的所有实体。位置分量仅保持位置,而运动分量则保持速度。

但是实际的问题是我的碰撞系统。这个类就像一个逻辑blob。我在这堂课上有很多特殊情况。

例如:我的桨可能会与边框碰撞。如果发生这种情况,其速度将设置为零。我的球也可能与边界碰撞。但是在这种情况下,它的速度仅反映在边界的法线上,因此可以反映出来。为此,我给球添加了一个额外的物理组件,它告诉:“嘿,这件事并没有停止,它反映了这一点。” 因此,实际上,物理组件没有实际数据。它是一个空类,仅用于告诉系统对象是否反射或停止。

然后是这样:当球与球拍或边框碰撞时,我想渲染一些粒子。因此,我认为球必须具有另一个组件,该组件告诉碰撞系统在碰撞时创建粒子。
然后,我想要一个可以与桨碰撞但不能与边界碰撞的力量提升器。如果发生这种情况,通电将消失。因此,我需要更多的情况和组件(以告诉系统某些实体只能与某些其他实体发生碰撞,即使某些其他实体确实可以发生碰撞,机器人也不能与所有实体发生碰撞,此外,碰撞系统还必须对这些实体施加动力桨等,等等。)。

我看到实体组件系统是一件好事,因为它很灵活,并且继承没有问题。但是我现在完全陷入困境。

我觉得太复杂了吗?我该如何应对?

当然,我必须创建实际上负责“碰撞后”的系统,因此碰撞系统只会告诉“是的,我们在最后一帧发生碰撞”,然后有许多“碰撞后”系统所有这些都需要不同的(组合)组件,然后更改组件。例如,将有一个运动后碰撞系统,该系统停止发生碰撞时必须停止的东西。然后是一个反映事物等的物理后碰撞系统。

但这似乎也不适合我,因为例如:

  1. 我的运动碰撞后系统将需要具有位置分量,运动分量和碰撞分量的实体。然后,它将实体的速度设置为零。
  2. 物理后碰撞系统将需要具有位置成分,运动成分,碰撞成分和物理成分的实体。然后它将反映速度矢量。

问题很明显:运动后碰撞需要实体,这些实体是物理后碰撞系统中实体的子集。因此,两个碰撞后系统将对相同的数据进行操作,其结果是:尽管实体具有物理成分,但碰撞后速度为零。

这些问题一般如何在实体组件系统中解决?这些问题甚至很常见吗?如果是,那么应该怎么做以及如何做呢?

Answers:


11

是的,您认为太复杂了。

听起来,您可以使用消息传递系统和一些其他属性来解决许多问题,这些属性允许您指定一些过滤器,最后不必担心对实体/组件的要求如此严格。

消息传递将在某些方面帮助您,例如触发粒子,启动等。例如,您可能有一个世界对象,该对象订阅粒子事件并在事件中描述的位置创建粒子。

过滤器将在碰撞方面为您提供很多帮助。过滤器可以定义一个对象是否与另一个对象碰撞以及它将产生什么样的响应。您可以在物理组件中添加一些属性,这些属性定义了它是什么类型的物理物体,它与之碰撞的其他类型的物理物体以及响应应该是什么。例如,一个球物理对象与一个桨物理对象碰撞,并以反射和粒子响应。

最后,不要对您的实现如此严格。如果您可以找到一种使之运行的方法,但它并不是真正的EC系统,请执行此操作。就像上面的示例一样,粒子完全不需要由系统或EC系统的一部分进行管理。完成游戏比严格遵循已经定义不充分的方法更为重要。


非常感谢你。我想通过使用ECS来构建游戏,只是看它如何缩放以及在我阅读文章和教程时是否真的好用。我的问题是我想:“我现在有一个ECS,必须对此进行所有管理。” 因此,我还计划编写与ECS相关的粒子系统。另外,我在一些文章中读到,每个组件实际上应该只具有一些基本数据,仅此而已。这通常是我的问题...我认为太复杂了。
M0rgenstern

我想通过使用ECS来构建游戏,只是看它如何缩放以及在我阅读文章和教程时是否真的好用。如果这是您的目标,则建议您查看现有的组件/实体系统,而不要构建自己的组件/实体系统。下载Unity3D,它可能是“尽可能获得的纯组件”,然后在此玩转。恕我直言,见解快得多。
Imi 2013年

3
@lmi:尽管Unity是基于组件的,但它不是实体组件系统。除了简单地拥有和使用游戏对象组件外,ECS还有一些相当严格的指导原则(从未将模式视为规则)。由于出现了一系列文章,因此ECS在某些游戏开发人员中现在很流行,因此有很多关于ECS的问题,而不是一般基于组件的设计。
肖恩·米德迪奇

12

您使事情变得过于复杂。我要说的是,即使使用基于组件的设计对于这样一个简单的游戏来说也是过大的。以使您的游戏快速且易于开发的方式来做事。组件可以在行为和游戏对象配置多种多样的大型项目中帮助迭代,但是它们对于如此简单的定义明确的游戏的好处却值得怀疑。去年,我做了一个演讲:如果您专注于制作游戏而不是遵循体系结构,那么您可以在几个小时内构建有趣的小游戏。当您拥有100个甚至20种不同类型的对象时,继承会中断,但如果只有少数几个对象,继承就可以正常工作。

假设您想继续使用组件进行学习,那么您的方法中就会出现一些明显的问题。

首先,不要使组件这么小。没有理由拥有诸如“运动”之类的细粒度组件。您的游戏中没有通用动作。您有桨,其运动与输入或AI紧密相关(并且实际上并没有使用速度,加速度,恢复力等),并且您的球具有明确定义的运动算法。只要有一个PaddleController组件和一个BouncingBall组件或类似的东西。如果/当您获得更复杂的游戏时,则可以担心拥有更通用的PhysicsBody组件(在“真实”引擎中,该组件基本上只是游戏对象与Havok / PhysX / Bullet / Box2D / etc。)处理各种各样的情况。

即使“职位”部分也有问题,尽管这种情况并不少见。物理引擎通常对对象在哪里有自己的内部观念,图形可能具有插值表示,而AI可能在不同状态下具有相同数据的另一种表示。最好让每个系统在系统自己的组件中管理自己的转换思想,然后确保系统之间的顺畅通信是有利的。请参阅有关事件流BitSquid博客文章

对于自定义物理引擎,请记住,允许在组件上存储数据。也许一个通用的Pong物理组件具有指示其可以在哪些轴上移动的数据(例如vec2(0,1)对于只能在Y轴上运动的球拍的乘数,vec2(1,1)但是对于表示其可以运动的球的乘数),则一个标志或浮点数指示反弹(球通常在1.0,桨在0.0),加速度特性,速度等。对于每个高度相关的数据,试图将其分解为成千上万个不同的微型组件,这与ECS最初的意图背道而驰。尽可能将使用在一起的东西放在同一个组件中,只有在每个游戏对象使用该数据的方式有很大差异时才将它们拆分。有一种观点认为,对于乒乓球来说,球和球拍之间的物理特性足以区别为单独的组件,但是对于较大的游戏,没有理由尝试制作20个组件来完成1-3的工作。

请记住,如果/当您的ECS版本遇到问题时,请执行实际制作您的游戏所需的操作,而忘记坚持遵循设计模式/架构。


1
真的很感谢你!我知道,对于像乒乓球这样的小型游戏,ECS不能很好地扩展。但是我只是用它来了解这种事情是如何实现的以及它是如何工作的。我使组件如此之小,因为这是我在某些文章中主要阅读的内容。要具有许多组件,并且每个组件仅包含基本数据。那么,我是否理解正确,建议您在继承和ECS之间混合使用?正如您所说的那样,“球和桨板的区别足以使它们成为独立的组件”。因此,例如,我给他们提供了“运动/位置”部分(也许是一个部分)和
M0rgenstern 2013年

1
无论如何。专注于制作游戏。从字面上看,我只是想拥有一个组件Ball,它包含了球的所有逻辑,例如运动,弹跳等,还有一个Paddle需要输入的组件,但这就是我。任何对您来说最有意义的事情,都会让您摆脱困境,让您制作游戏是做事的“正确方法”。
肖恩·米德迪奇

3
我实际上只是有一个名为Ball的组件,它包含了球的所有逻辑,如运动,弹跳等,还有一个Paddle组件,它需要输入,但这就是我。这就是为什么每个程序员都对“什么是组件系统”有一种看法。我建议您不要像这样的建议那样做,除非您完全在经典的Entity系统中进行思考,并被迫使用Component系统,但又不想查看实际的差异。
Imi 2013年

2
@lmi:曾经研究过一些具有组件的大型游戏/引擎,并亲眼看到了为什么我们使用组件,不,过于细化的组件所带来的麻烦远远超过了它们的价值。组件不是万能的。它们是游戏开发者工具箱中众多工具之一。以一种有帮助的方式使用它们,而不是以仅仅增加系统精神和运行时开销的方式使用它们。如果唯一具有球物理特性的是球,则将其与其他球属性分开将没有任何优势。如果并且何时改变,则将其分开。
肖恩·米德迪奇

1
我同意务实的原则,不要让某个特定的模式陷入困境。但是,如果ECS无法无偏差地处理这个琐碎的游戏,那么大型游戏的希望何在。我也试图学习如何有效地使用ECS,但是我试图尽可能地接近ECS理念,否则,一旦我开始出现例外和特殊情况,我就会陷入无法维护的困境。
肯(Ken

-2

在我看来*),您使用组件的最大问题是:组件不会在这里告诉其他人该怎么做。组件在这里做事。您没有一个组件仅用于保存某些事物的内存,然后让其他组件对此进行操作。您需要用它们获得的数据填充的组件。

如果您看到自己正在测试是否存在其他组件(然后在其中调用函数),那么这是一个明显的信号,表明以下两种情况之一是正确的:

  • 您实际上想反转依赖关系:另一个组件应侦听事件/消息/广播/挂钩/如何命名它们,以响应当前组件而执行其逻辑。当前组件甚至不必知道存在“其他”组件。这是最常见的情况,如果您发现自己调用的组件(甚至在其他else / case块中)具有与当前方法未真正连接的功能。考虑所有这些Invalidate()SetDirty()对其他组件的调用。
  • 您可能有太多的组件。如果两个组件彼此之间无法生存,并且不断需要检索数据并彼此调用方法,则只需合并它们。显然,他们提供的功能是如此纠结,以至于实际上它只是一件事。

顺便说一下,这些方法不仅适用于所有类型的系统,不仅适用于实体/组件系统,还适用于具有简单“ GameObject”甚至是库函数的经典继承。

*)真的只有我的。关于Whats Da Real Component Systemz(TM)的意见分歧很大

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.