我读过几篇关于实体组件风格编程的文章。提出的常见问题之一是如何表达组件之间的依赖性以及与同一实体相关的组件如何通信。
在我看来,解决此问题的一种简单方法是使每个依赖项成为其依赖项的虚拟基类。
这样,当某个组件包含在实体中(通过虚拟继承)时,所有从属组件都将仅包含一次。此外,组件依赖的所有功能都将在其成员函数中可用。
class C_RigidBody : public virtual C_Transform {
public void tick(float dt);
};
class C_Explodes : public virtual C_Transform {
public void explode();
};
class E_Grenade : public virtual C_RigidBody, public virtual C_Explodes {
//no members
};
Run Code Online (Sandbox Code Playgroud)
有没有理由没有人这样做?
(我认识到,由于“钻石问题”,通常不赞成多重继承,但是无论如何,这个问题都是组件必须处理的。(想象有多少个组件将取决于实体在游戏世界中的位置))
小智 0
这个答案很多都是基于猜测,但由于没有人尝试过,所以这是我的尝试。
组合优于继承
ECS 的核心是一种有利于审美的组合而不是继承。继承的设计首先是为了通过层次结构建立“is-a”关系的模型。虽然它当然还有更多的用途,甚至是依赖于超越它的政策类别之类的东西,但它的核心是那种“美学”。
这里的“审美”往往是从人类倾向演变而来的。在理想的世界中,务实的团队受益于更灵活的工具,并利用它们获得更大的利益。不幸的是,有时现实是,一个团队的强大程度取决于最薄弱的环节不能搞砸的事情。
突然间,当您开始继承诸如变换或位置之类的东西时,它同样为最薄弱的链接打开了大门,开始处理此类实体组件关系,就好像它们建模“is-a”关系一样(例如:动态向下转换)转换为刚体,试图通过破坏其位置来破坏手榴弹资源,并忘记使该位置的 dtor 虚拟或至少受保护且非虚拟,甚至是更模糊的情况,例如无意中切片)。
通常我们从来不会遇到这样的天真,但我已经见过太多次了,所以无法抱有非常乐观的看法。组合使此类场景成为不可能,或者至少减轻了与其中许多场景相关的影响和成本。它是一种限制性更强的工具,会降低灵活性,施加更多约束,有时在团队环境中限制这种意义上的自由和灵活性是可取的,作为避免墨菲定律的一种严厉方式。这总是有点主观,因为它基于对人类倾向的预测,而这种预测永远不会是完美的,但与深度嵌套的继承层次结构相比,人类更容易搞砸组合。它往往需要预先付出更多的努力和样板,但严重搞砸的可能性较小。
运行时可扩展性
这仅适用于某些引擎,但有时引擎希望允许在运行时对其实体进行进一步编程,包括引入新组件、扩展现有实体等,而无需静态编译过程。例如,新的组件、实体或现有实体的扩展可以通过脚本语言(例如嵌入式Lua)或专有的节点编程语言来应用,该语言允许没有强大编程背景的设计者类型组成新实体。
在这些情况下,继承变得过于硬编码的静态编译概念,无法在运行时扩展。这仅适用于选择引擎,但它演示了一种场景,其中故意避免继承实际上可以增加另一种灵活性(特别是在运行时)。
也就是说,考虑到正确的团队类型、标准和要求,我认为您的提案没有什么特别错误的地方。但是这两点可能有助于解释为什么很少使用继承来对实体组件系统进行建模。
还有一些其他潜在的问题,例如对 RTTI 的依赖性和动态转换以确定哪些组件可用、保留 ABI、vptr 开销等方面的难度增加,如果需要,我可以讨论这些问题。其中很多只是回到组合与继承的核心。