实体组件系统体系结构对象是否按定义定向?


20

按照定义,实体组件系统体系结构是否面向对象?对我来说,这似乎更具程序性或功能性。我的观点是,它不会阻止您以OO语言实现它,但是以坚定的OO方式实现它并不是惯用的。

似乎ECS会将数据(E&C)与行为(S)分开。作为证据

这个想法是在实体中没有嵌入任何游戏方法。

该组件包含用于特定目的的最少数据集

系统是具有一组具有特定组件的实体的单一目的功能


我认为这不是面向对象的,因为面向对象的很大一部分是将您的数据和行为结合在一起。 作为证据

相反,面向对象的方法鼓励程序员将数据放置在程序其余部分无法直接访问的位置。而是通过调用专门编写的函数(通常称为方法)来访问数据,这些函数与数据捆绑在一起。

另一方面,ECS似乎只不过是将数据与行为分开。

Answers:


21

介绍


实体组件系统是一种面向对象的架构技术。

关于术语的含义,与面向对象编程相同,尚无共识。但是,很明显,实体组件系统专门用作继承的体系结构替代。继承层次结构很自然地表达对象什么,但是在某些类型的软件(例如游戏)中,您宁愿表达对象的作用

它是与“类和继承”不同的对象模型,您最常使用C ++或Java来熟悉它。实体就像类一样具有表现力,就像JavaScript或Self中的原型一样,所有这些系统都可以彼此实现。

 

例子


比方说,Player是一个实体PositionVelocityKeyboardControlled组件,它们做明显的事情。

entity Player:
  Position
  Velocity
  KeyboardControlled

我们知道Position必须受到VelocityVelocity的影响KeyboardControlled。问题是我们如何建模这些效果。

 

实体,组件和系统


假设组件之间没有相互引用;外部Physics系统遍历所有Velocity组件并更新Position相应实体的;一个Input系统遍历所有的KeyboardControlled组件和更新Velocity

          Player
         +--------------------+
         | Position           | \
         |                    |  Physics
       / | Velocity           | /
  Input  |                    |
       \ | KeyboardControlled |
         +--------------------+

满足条件:

  • 实体未表达任何游戏/业务逻辑。

  • 组件存储描述行为的数据。

系统现在负责处理事件并制定组件描述的行为。它们还负责处理实体之间的交互,例如冲突。

 

实体和组件


但是,假设组件之间确实存在相互引用。现在,实体只是一个构造函数,它创建一些组件,将它们绑定在一起,并管理其生命周期:

class Player:
  construct():
    this.p = Position()
    this.v = Velocity(this.p)
    this.c = KeyboardControlled(this.v)

实体现在可以将输入和更新事件直接分派到其组件。Velocity将响应更新,并KeyboardControlled响应输入。这仍然满足我们的标准:

  • 实体是一个“哑”容器,仅将事件转发到组件。

  • 每个组件都执行其自己的行为。

在这里,组件交互是显式的,不是系统从外部强加的。描述行为的数据(什么是速度量?)和实现该行为的代码(什么是速度?)是耦合的​​,但是是自然的。可以将数据视为行为的参数。而且某些组件根本不起作用-这Position在某个地方的行为。

交互可以在实体级别(“当PlayerEnemy… 碰撞时”)或在各个组件的级别(“当一个实体与LifeStrength… 碰撞时”进行处理)。

 

组件


实体存在的原因是什么?如果它仅仅是一个构造函数,那么我们可以返回一个函数替换它组件。如果以后我们要按实体类型查询实体,我们也可以有一个Tag组件让我们做到这一点:

function Player():
  t = Tag("Player")
  p = Position()
  v = Velocity(p)
  c = KeyboardControlled(v)
  return {t, p, v, c}
  • 实体尽可能的愚蠢-它们只是组件集。

  • 组件像以前一样直接响应事件。

互动必须现在由抽象查询处理,完全脱钩实体类型的事件。没有更多的实体类型可查询— Tag与游戏逻辑相比,任意数据可能更适合用于调试。

 

结论


实体不是功能,规则,参与者或数据流组合者。它们是模拟具体现象的名词,也就是说,它们是对象。就像Wikipedia所说的那样,实体组件系统是用于对通用对象进行建模的软件体系结构模式。


2
基于类的OO的主要替代方案,即基于原型的OO,似乎也将数据和行为耦合在一起。实际上,它似乎与ECS有所不同,就像基于类的OO一样。那么,您能否详细说明OO的含义?

要补充@delnan的问题,您是否不同意我引用的OO维基百科文章的摘要?
Daniel Kaplan 2013年

@tieTYT:Wikipedia引用正在谈论封装和信息隐藏。我认为没有证据表明数据与行为的耦合是必需的,只是它很普遍。
乔恩·普迪

@delnan:我不是OO的意思。对我来说,面向对象的编程恰好就是它所说的:使用“对象”(与功能,规则,参与者,数据流组合器等相对)进行编程,其中对象的特定定义是实现定义的。
乔恩·普迪

1
@tieTYT:我只是在描述我在野外看到的实现,以传达它是一个广义术语,与Wikipedia描述并不矛盾,但肯定会更广泛。
乔恩·普迪

20

没有。我很惊讶有多少人投票反对!

范例

它是面向数据的,也就是数据驱动的,因为我们在谈论的是体系结构,而不是其编写的语言体系结构是编程风格或范例的实现,通常不建议以给定的语言来解决。


有功能吗?

您与功能 / 过程编程的比较是有意义且有意义的比较。但是请注意,“功能” 语言与“过程” 范式不同。您可以使用人们已经做过的功能语言(例如Haskell)实施ECS 。


发生凝聚力的地方

你的观察是相关的,现货上

“ ... [ECS]不会阻止您使用OO语言来实现它,但是以坚定的OO方式来实现它并不是惯用的。”


ECS / ES不是EC / CE

基于组件的体系结构“实体组件”和“实体组件系统”之间存在差异。由于这是一个不断发展的设计模式,因此我已经看到这些定义可以互换使用。“ EC”或“ CE”或“实体组件”体系结构将行为放入组件中,而“ ES”或“ ECS”体系结构将行为纳入系统中。以下是一些ECS文章,这两篇文章都使用了误导性的命名法,但却使您了解了总体思路:

如果您想在2015年理解这些术语,请确保有人对“实体组件系统”的引用并不意味着“实体组件体系结构”。


1
这是正确的答案。ECS不太适合OOP范式,因为ECS完全是将数据和行为分开,而OOP则相反。
Nax'vi-vim-nvim'16年

“虽然OOP恰好相反”,否则对OOP的含义没有公认的定义,除非像SmallTalk这样的无用的学术定义从未在实践中使用过。
吉恩-迈克尔Celerier

10

可以根据系统的定义,以OOP或功能方式对实体组件系统(ECS)进行编程。

面向对象的方式:

我从事过游戏,其中实体是由各种组件组成的对象。实体具有更新功能,该功能通过依次在其所有组件上调用update来修改对象。显然,这是OOP风格-行为与数据相关联,并且数据是可变的。实体是具有构造函数/析构函数/更新的对象。

更多功能方式:

另一种选择是使实体成为没有任何方法的数据。该实体可以单独存在,也可以只是链接到各个组件的id。通过这种方式,可能(但不常做)完全起作用,并具有不可变的实体和生成新组件状态的纯系统。

从个人经验看,后一种方式正在获得更多的关注,这是有充分理由的。将实体数据与行为分开可以产生更灵活和可重用的代码(imo)。特别地,使用系统来批量更新组件/实体可以更加高效,并且完全避免了困扰许多OOP ECS的实体间消息传递的复杂性。

TLDR:您可以用任何一种方式来做,但是我认为良好的实体组件系统的好处来自于它们的更多功能。


尤其是因为组件的全部重点是要摆脱棘手的OOP层次结构,因此更好地吸引了人们的注意。
Patrick Hughes 2014年

2

面向数据的实体组件系统可以与面向对象的范例共存:-组件系统使其具有多态性。-组件既可以是POD(普通旧数据)又可以是ALSO对象(带有类和方法),并且整个组件仍然是“面向数据的”,前提是组件类方法只能操作本地对象拥有的数据。

如果选择此路径,建议您避免使用虚拟方法,因为有了虚拟方法,您的组件将不再是纯粹的组件数据,而且这些方法的调用成本更高-这不是COM。通常,使组件类清除对外部任何内容的任何引用。

例如vec2或vec3,这是一个数据容器,其中包含一些可以触摸该数据的方法,仅此而已。


2
这篇文章很难阅读(文字墙)。您介意将其编辑为更好的形状吗?同样,如果您向读者解释为什么他们可能会发现链接的博客文章与所问的问题相关且有用,这也会有所帮助...
gnat 2015年

...如果您与该博客有某种联系(是吗?),则还希望披露从属关系
t

是的,那是我的博客,我与我的公共博客紧密联系,该博客公开了基于面向数据的设计原则的面向对象的实体组件系统的详细信息,我认为这是相关且可能有用的,尽管如此,我还是删除了指向消除任何偏见。
荷马

2

我认为ECS从根本上不同于OOP,并且倾向于以与您相同的方式来看待它,因为它与功能性或程序性性更接近,并且数据与功能性非常不同。处理中央数据库的编程也有一些相似之处。当然,在形式化定义方面,我是最糟糕的人。我只关注事物的发展趋势,而不是概念上定义的事物。

我假设一种ECS,其中组件聚合数据字段并使其可公开/全局访问,实体聚合组件,而系统提供该数据的功能/行为。这就导致了通常我们称为面向对象的代码库的极其困难的体系结构特征。

当然,人们在设计/实施ECS的方式上存在一些界限的模糊,并且一开始就究竟是什么构成ECS的争论。但是,用我们称为功能或过程语言编写的代码中的此类界限也变得模糊。在所有这些模糊性之中,与OOP相比,ECS的基本常数具有将数据与功能分离的功能,对我而言,它似乎更接近于功能或过程编程。

我认为将ECS属于OOP类别并没有帮助的主要原因之一是,与OOP相关的大多数SE做法都围绕着公共接口的稳定性,而不是数据的公共接口建模功能。基本思想是,大多数公共依赖项流向抽象功能,而不是具体数据。因此,OOP往往会导致更改基本设计行为的成本很高,而更改具体细节(例如实现功能所需的数据和代码)的成本却非常低廉。

在这方面,ECS的根本不同之处在于,当大量公共依赖项流向具体数据时(从系统到组件),事物之间如何耦合。结果,与ECS相关的任何SE做法都将围绕数据稳定性,因为最公开和广泛使用的接口(组件)实际上只是数据。

因此,即使DX和GL引擎都提供了DX引擎,ECS仍可以很轻松地完成诸如将OpenGL渲染引擎替换为DirectX的操作,即使这两个实现的功能截然不同,也不会共享相同的设计。可以访问相同的稳定数据。同时,这将非常昂贵,并且需要重写一堆系统来更改,例如a的数据表示MotionComponent

这与我们传统上与OOP关联的东西非常相反,至少在耦合特性以及“公共接口”与“私有实现细节”的构成方面。当然,在两种情况下,“实施细节”都很容易更改,但是在ECS中,更改数据的设计成本很高(在ECS中数据不是实施细节),而在OOP中,更改功能的设计成本很高(功能的设计不是OOP中的实现细节)。因此,这是“实施细节”的非常不同的概念,从维护的角度来看,ECS的主要吸引力之一就是在我的领域内,做事所需的数据比我们可以使用该数据做的所有各种事情(一经客户改变主意并不断涌现新的用户建议时,它会一直改变)更容易一劳永逸地进行稳定和正确地预先设计。结果,当我们开始将依赖关系从抽象函数转移到原始的中央数据时,我发现维护成本直线下降(但尽管在概念上所有数据都在考虑,但仍要注意哪些系统可以访问哪些组件以将变量保持合理程度)全球访问)。

至少就我而言,带有API和所有组件的ECS SDK实际上是用C实现的,与OOP没有任何相似之处。考虑到ECS架构固有的OO 不足以及希望拥有可被最广泛的语言和编译器使用的插件架构的渴望,我已经发现C足以满足上述目的。由于C ++使事情变得非常方便,并且系统对大部分复杂性进行了建模,因此该系统仍在C ++中实现,我发现其中许多事情可能会被认为更接近OOP,但这只是为了实现细节。建筑设计本身仍然非常类似于程序C。

因此,我认为,至少说ECS定义为OO至少有些令人困惑。至少,基本原理与许多通常与OOP相关的基本原理相比,完成了180度的旋转,从封装开始,到可能以所需的耦合特性结束。

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.