实体组件系统对解耦/信息隐藏不是很可怕吗?


11

标题故意是双曲线的,可能只是我对模式的经验不足,但这是我的理由:

实现实体的“通常”或可以说是直接的方法是通过将它们实现为对象并将其子类化为常规行为。这导致了经典的问题“是或EvilTree的子类”。如果我们允许多重继承,就会出现钻石问题。我们可以代替拉的组合功能,并进一步向上层次导致神类,或者我们可以故意留出的行为在我们和类(使他们在极端情况下接口),以便可以实现本身-这会导致如果有一个代码复制。TreeEnemyTreeEnemyTreeEntityEvilTreeSomewhatEvilTree

实体组件系统试图通过将解决这一问题TreeEnemy对象到不同的组件-比如PositionHealthAI-和执行系统,例如AISystem,根据AI决定改变一个Entitiy的位置。到目前为止,EvilTree还算不错,但是如果可以启动电源并造成损害该怎么办?首先,我们需要一个CollisionSystem和一个DamageSystem(我们可能已经有了这些)。CollisionSystem与进行通信的需求DamageSystem:每次两件事冲突时,CollisionSystem都会向发送一条消息,DamageSystem以便减少健康状况。损坏也会受到通电的影响,因此我们需要将其存储在某个地方。我们是否创建一个PowerupComponent附加到实体的新商品?但是然后DamageSystem需要知道一些它宁愿一无所知的东西-毕竟,有些东西会造成无法获得能量的伤害(例如Spike)。我们是否允许PowerupSystem修改StatComponent该答案类似的用于损伤计算的?但是现在两个系统访问相同的数据。随着我们的游戏变得越来越复杂,它将变成无形的依赖图,其中组件在许多系统之间共享。在这一点上,我们可以使用全局静态变量并摆脱所有样板。

有解决此问题的有效方法吗?我曾经有过一个想法,就是让组件具有某些功能,例如,给StatComponent attack()它默认情况下只返回一个整数,但是可以在上电时进行组合:

attack = getAttack compose powerupBy(20) compose powerdownBy(40)

这不能解决attack必须保存在多个系统访问的组件中的问题,但是如果我有足够的语言来支持它,至少我可以正确键入这些函数:

// In StatComponent
type Strength = PrePowerup | PostPowerup
type Damage = Int
type PrePowerup = Int
type PostPowerup = Int
attack: Strength = getAttack //default value, can be changed by systems
getAttack: PrePowerup

// these functions can be defined in other components or in PowerupSystems
powerupBy: Strength -> PostPowerup
powerdownBy: Strength -> PostPowerup
subtractArmor: Strength -> Damage

// in DamageSystem
dealDamage: Damage -> () = attack compose subtractArmor compose hurtSomeEntity

这样,我至少可以保证系统添加的各种功能的正确排序。无论哪种方式,似乎我都在这里快速地进行函数式反应式编程,因此我要问自己是否从一开始就不应该使用它(我只是研究FRP,所以在这里我可能是错的)。我发现ECS是对复杂类层次结构的改进,但我不认为这是理想的选择。

有没有解决的办法?是否缺少我想更清楚地将ECS分离的功能/模式?FRP是否严格更适合于此问题?这些问题是由于我要编程的内在复杂性引起的吗?即玻璃钢会有类似的问题吗?



我真的很想念Eric的博客(那是关于C#的)。
OldFart

Answers:


21

ECS完全破坏了数据隐藏。这是该模式的折衷方案。

ECS 在去耦方面非常出色。一个好的ECS可以使移动系统声明它可以在具有速度和位置分量的任何实体上工作,而不必关心存在什么实体类型,或者哪些其他系统可以访问这些分量。这在解耦能力上至少等效于使游戏对象实现某些接口。

访问相同组件的两个系统是一个功能,不是问题。这是完全可以预期的,并且不会以任何方式耦合系统。的确,系统将具有隐式的依赖关系图,但是这些依赖关系是建模世界中固有的。要说损害赔偿制度不应该对加电制度有隐含的依赖性,那就是说加电不会影响损害,这可能是错误的。但是,尽管存在依赖关系,但系统并没有耦合 -您可以从游戏中移除加电系统而不影响伤害系统,因为通信是通过stat组件发生的,并且是完全隐式的。

可以在单个中央位置完成这些依赖关系和排序系统的解析,类似于DI系统中的依赖关系解析的工作方式。是的,一个复杂的游戏将具有一个复杂的系统图,但是这种复杂性是内在的,至少是包含在内的。


7

一个系统需要访问多个组件的事实几乎无法解决。为了使诸如VelocitySystem之类的东西能够正常工作,可能需要访问VelocityComponent和PositionComponent。同时,RenderingSystem也需要访问此数据。无论您做什么,渲染系统有时都需要知道将对象渲染到哪里,而VelocitySystem需要知道将对象移动到哪里。

为此,您需要的是显式依赖关系。每个系统都需要明确说明将读取哪些数据以及将写入哪些数据。当系统想要获取特定组件时,它仅需要能够明确地做到这一点。以其最简单的形式,它仅具有所需的每种类型的组件(例如RenderSystem需要RenderComponents和PositionComponents)作为其参数,并返回已更改的内容(例如,仅RenderComponents)。

这样,我至少可以保证系统添加的各种功能的正确排序

您可以在这种设计中订购。没什么可说的,对于ECS,您的系统必须独立于顺序或任何此类事物。

FRP是否严格更适合于此问题?这些问题是由于我要编程的内在复杂性引起的吗?即玻璃钢会有类似的问题吗?

使用这种实体组件系统设计和FRP并不互斥。实际上,仅执行数据转换(组件)就可以将系统视为没有状态。

FRP不会解决必须使用您所需的信息才能执行某些操作的问题。

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.