为什么将方法存储在实体和组件中是一个坏主意?(以及其他一些有关实体系统的问题。)


16

这是我对这个问题的回答,但是我回答了一个更具体的主题。

这个答案使我比本文更好地理解了实体系统。

我读了 有关实体系统的文章(是的),它告诉我以下内容:

实体只是一个id和一组组件(文章说,将实体存储在组件中不是一种好方法,但没有提供替代方法)。
组件是数据片段,它们指示可以对特定实体执行的操作。
系统是“方法”,它们对实体上的数据进行操作。

在许多情况下,这似乎确实可行,但是有关组件仅仅是数据类的部分让我感到困扰。例如,如何在实体系统中实现Vector2D类(位置)?

Vector2D类保存数据:x和y坐标,但是它也具有方法,这对于其有用性至关重要,并且可以将类与仅包含两个元素的数组区分开。实施例的方法有:add()rotate(point, r, angle)substract()normalize(),和所有其他的标准,有用的和绝对需要的方法该位置(其是对的Vector2D类的实例)应该有。

如果该组件只是一个数据持有者,它将无法使用这些方法!

一种可能弹出的解决方案是在系统内部实现它们,但这似乎非常违反直觉。这些方法是我现在要执行的事情,要使它们完整并可以使用。我不想等待MovementSystem读取一些昂贵的消息,这些消息指示它对实体的位置进行计算!

并且,该文章非常清楚地指出,只有系统才应该具有任何功能,而对此的唯一解释是“避免OOP”。首先,我不明白为什么我应该避免在实体和组件中使用方法。内存开销实际上是相同的,并且当与系统耦合时,它们应该非常容易实现并以有趣的方式组合。例如,系统只能向知道实现本身的实体/组件提供基本逻辑。如果您问我-这基本上是从ES和OOP那里获得的好处,根据本文的作者所说,这是无法完成的,但是对我来说,这似乎是一种好习惯。

这样想吧;游戏中有许多不同类型的可绘制对象。普通的旧图像,动画(update()getCurrentFrame()等),这些原始类型的组合以及所有这些都可以简单地为draw()渲染系统提供一种方法,然后该方法无需关心实体的sprite是如何实现的,关于界面(绘制)和位置。然后,我只需要一个动画系统,该系统将调用与渲染无关的特定于动画的方法。

还有另一件事……关于存储组件,真的有数组的替代方法吗?除了实体类中的数组,我看不到要存储组件的其他地方。

也许这是一个更好的方法:将组件存储为实体的简单属性。例如,位置组件将粘贴到entity.position

唯一其他方法是有某种奇怪的查找表的内部系统中,引用不同的实体。但是,这似乎非常低效的,更复杂的比在实体只需存储组件来开发。


亚历山大(Alexandre)您是否正在进行大量修改以获取另一个徽章?因为那是顽皮的顽皮,所以它不断碰撞大量古老的线索。
6

Answers:


25

我认为使用简单的方法来访问,更新或操作组件中的数据完全没问题。我认为应该不包含在组件中的功能是逻辑功能。实用程序功能很好。请记住,实体组件系统只是一个准则,而不是您需要遵循的严格规则。不要全力以赴地跟随他们。如果您认为以一种方式进行操作更有意义,请以这种方式进行操作:)

编辑

要澄清,您的目标不是避免OOP。在当今使用的大多数通用语言中,这将非常困难。您正在尝试最小化继承,这是OOP的重要方面,但不是必需的。您要摆脱对象->移动对象->生物->双足->人类类型继承。

但是,可以继承一些东西!您正在处理一种受继承影响很大的语言,很难不使用任何一种。例如,您可以具有Component所有其他组件扩展或实现的类或接口。与您的System班级一样。这使事情变得容易得多。我强烈建议您看一下Artemis框架。它是开源的,并且有一些示例项目。打开这些东西,看看它是如何工作的。

对于Artemis,实体存储在一个简单的数组中。但是,它们的组件存储在一个或多个数组中(与实体分开)。顶层阵列按组件类型将底层阵列分组。因此,每种组件类型都有其自己的数组。下级数组由实体ID索引。(现在我不确定是否会那样做,但这就是在这里完成的方式)。Artemis重用实体ID,因此最大实体ID不会大于您当前的实体数,但是如果该组件不是经常使用的组件,您仍然可以使用稀疏数组。无论如何,我不会太挑剔。这种用于存储实体及其组件的方法似乎有效。我认为这将是实现自己的系统的第一步。

实体和组件存储在单独的管理器中。

您提到的使实体存储自己的组件(entity.position)的策略有点违背实体组件的主题,但是如果您觉得最有意义,那是完全可以接受的。


1
嗯,那大大简化了情况,谢谢!我以为发生了一些不可思议的“您以后会后悔”的事情,而我只是看不到它!
jcora

1
不,我在实体组件系统中完全使用了它们。我什至有一些继承自共同父母gasp的组件。我认为您唯一会后悔的事情是,如果您尝试解决使用此类方法的问题。这是关于做对您来说最有意义的事情。如果使用继承或在组件中放置一些方法有意义,那就去做吧。
MichaelHouse

2
我从关于这个问题的最新答案中学到了东西。免责声明:我并不是说这是这样做方法。:)
MichaelHouse

1
是的,我知道学习新的范式可能有多么艰巨。幸运的是,您可以使用旧范式的各个方面来简化事情!我已经用存储信息更新了我的答案。如果您查看Artemis,请查看,EntityManager因为那是存储内容的地方。
MichaelHouse

1
真好!完成后,这将是一个非常不错的引擎。祝你好运!感谢您提出有趣的问题。
MichaelHouse

10

“那篇文章”不是我特别同意的一篇文章,因此我认为我的回答将很关键。

在许多情况下,这似乎确实可行,但是有关组件仅仅是数据类的部分让我感到困扰。例如,如何在实体系统中实现Vector2D类(位置)?

这个想法不是要确保程序中的任何东西都不是实体ID,组件或系统,而是要确保实体数据和行为是通过组合对象创建的,而不是使用复杂的继承树或更糟糕的尝试将所有可能的功能整合到一个对象中。为了实现这些组件和系统,您肯定会拥有正常的数据,例如向量,在大多数语言中,向量都最好表示为一个类。

忽略文章中暗示这不是OOP的内容-与其他任何方法一样,它也是OOP。当大多数编译器或语言运行时实现对象方法时,基本上与任何其他函数一样,除了有一个称为this或的隐藏参数self,它是指向内存中存储该对象数据的位置的指针。在基于组件的系统中,实体ID可用于查找给定实体的相关组件(以及数据)的位置。因此,实体ID等效于this / self指针,概念基本相同,只是重新排列了一下。

而且,该文章非常清楚地指出,只有系统才应该具有任何功能,而对此的唯一解释是“避免OOP”。首先,我不明白为什么我应该避免在实体和组件中使用方法。

好。方法是组织代码的有效方法。摆脱“避免OOP”想法的重要事情是避免在各处使用继承来扩展功能。取而代之的是,将功能分解为可以组合以完成相同操作的组件。

这样想吧;游戏中有许多不同类型的可绘制对象。普通的旧图像,动画(update(),getCurrentFrame()等),这些原始类型的组合以及所有这些都可以简单地向渲染系统提供draw()方法[...]

基于组件的系统的想法是,您不会为此拥有单独的类,而只有一个Object / Entity类,并且图像将是具有ImageRenderer的Object / Entity,Animations将是Object /具有AnimationRenderer等的实体。相关系统将知道如何渲染这些组件,因此不需要任何具有Draw()方法的基类。

[...]然后,它不需要关心实体的精灵是如何实现的,只需关心接口(绘制)和位置。然后,我只需要一个动画系统,该系统将调用与渲染无关的特定于动画的方法。

当然可以,但这不适用于组件。您有3种选择:

  • 每个组件都实现此接口并具有Draw()方法,即使没有绘制任何内容。如果您对所有功能都进行了此操作,则组件将看起来非常难看。
  • 只有具有绘制内容的组件才能实现该接口-但是谁来决定调用Draw()的组件呢?系统是否必须以某种方式查询每个组件以查看支持什么接口?这在某些语言中容易出错,并且可能难以实现。
  • 组件只能由其拥有的系统处理(链接文章中的想法)。在这种情况下,该接口是无关紧要的,因为系统可以准确地知道其使用的类或对象类型。

还有另一件事……关于存储组件,真的有数组的替代方法吗?除了实体类中的数组,我看不到要存储组件的其他地方。

您可以将组件存储在系统中。数组不是问题,但组件存储在哪里。


+1感谢您的另一个观点。在处理这样一个模棱两可的话题时,多拿一些是好事!如果要在系统中存储组件,这是否意味着只能由一个系统修改组件?例如,绘图系统和运动系统都将访问位置组件。您将其存储在哪里?
MichaelHouse

好吧,他们只会存储指向这些组件的指针,只要我担心,它们就可以在某个地方 ……而且,为什么还要在系统中存储组件?这有好处吗?
jcora 2012年

我正确吗,@ Kylotan?我就是这么做的,这似乎合乎逻辑……
jcora 2012年

在Adam / T-Machine的示例中,其目的是每个组件有1个系统,但是系统肯定可以访问和修改其他组件。(这阻碍了组件的多线程优势,但这是另一回事。)
Kylotan

1
在系统中存储组件可以为该系统提供更好的参考位置-仅该系统(通常)可以使用该数据,那么为什么要遍历整个计算机的内存从一个实体到另一个实体来获取它呢?它还可以帮助并发,因为您可以将整个系统及其数据放在一个内核或处理器(甚至在MMO中​​甚至是一台单独的计算机)上。同样,当1个系统访问一种以上类型的组件时,这些好处也会减少,因此在决定将组件/系统职责划分到哪里时应考虑到这一点。
Kylotan

2

向量是数据。这些函数更像是实用程序函数-它们并不特定于该数据实例,它们可以独立地应用于所有向量。考虑它的一种好方法是:这些函数可以重写为静态方法吗?如果是这样,那只是实用程序。


我知道,但是问题是调用方法更快,可以由系统或可能需要操纵实体位置的任何其他方法现场完成。我解释说,也请检查一下,我认为,这个问题所涉及的不仅仅是此问题。
jcora 2012年
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.