通常如何在面向组件的系统中构建物理或图形组件?


19

我花了最后48个小时来阅读对象组件系统,并觉得我已经准备好开始实施它。我已经创建了基本的Object和Component类,但是现在我需要开始创建实际的组件,我有点困惑。当我用HealthComponent或基本上只是属性的东西来考虑它们时,这是很有意义的。当它作为物理/图形学组件更为通用时,我会有些困惑。

到目前为止,我的Object类是这样的(如果您注意到我应该进行的任何更改,请告诉我,这仍然是新的)...

typedef unsigned int ID;

class GameObject
{
public:

    GameObject(ID id, Ogre::String name = "");
    ~GameObject();

    ID &getID();
    Ogre::String &getName();

    virtual void update() = 0;

    // Component Functions
    void addComponent(Component *component);
    void removeComponent(Ogre::String familyName);

    template<typename T>
    T* getComponent(Ogre::String familyName)
    {
        return dynamic_cast<T*>(m_components[familyName]);
    }

protected:

    // Properties
    ID m_ID;
    Ogre::String m_Name;
    float m_flVelocity;
    Ogre::Vector3 m_vecPosition;

    // Components
    std::map<std::string,Component*> m_components;
    std::map<std::string,Component*>::iterator m_componentItr;
};

现在,我遇到的问题是,普通人群将对诸如物理/图形学之类的组件投入什么?对于Ogre(我的渲染引擎),可见对象将包含多个Ogre :: SceneNode(可能多个)以将其附加到场景; Ogre :: Entity(可能多个)以显示可见的网格,依此类推。最好只是将多个GraphicComponent添加到Object中,然后让每个GraphicComponent处理一个SceneNode / Entity,还是有需要每个组件之一的想法?

对于物理学,我感到更加困惑。我想也许是创建一个RigidBody并跟踪质量/ interia /等。会很有意义。但是我在思考如何将细节实际放入组件时遇到了麻烦。

一旦我完成了这些“必需”组件中的几个,我认为它将变得更加有意义。截至目前,尽管我仍然有些困惑。

Answers:


32

首先,当你构建基于组件的系统,你不具备采取转向的方法成组件。实际上,您通常不应该-这是新手错误。以我的经验,在基于组件的体系结构中将渲染和物理系统结合在一起的最佳方法是使这些组件只不过是用于真实渲染或物理原始对象的哑容器。

这具有使绘制系统和物理系统与组件系统分离的附加优点。

例如,对于物理系统,物理模拟器通常会保留大量的刚体对象,它们在每次滴答时都会进行操作。您的组件系统不会对此产生混乱-物理组件仅存储对刚体的引用,该引用表示该组件所属对象的物理方面,并将来自组件系统的所有消息转换为对刚体的适当操作。

与渲染类似,您的渲染器将具有代表要渲染的实例数据的内容,而可视组件仅保留对它的引用。

这是相对简单的-不管出于什么原因,它似乎会使人们感到困惑,但这通常是因为他们考虑得太过分了。那里没有很多魔术。而且由于完成工作而不是为完美的设计苦恼是最重要的,因此您应该强烈考虑一种方法,如momboco所建议的那样,至少某些组件已定义接口并且是游戏对象的直接成员。这是同样有效的方法,并且倾向于更容易理解。

其他的建议

这与您的直接问题无关,但是这些是我在查看代码时注意到的事情:

为什么要在游戏对象中混合Ogre :: String和std :: string,这在表面上是一个非图形对象,因此不应依赖Ogre?我建议删除对Ogre的所有直接引用,但在渲染组件本身中则应进行转换。

update()是一种纯虚函数,它意味着必须将GameObject子类化,实际上与您要使用组件体系结构的方向完全相反。使用组件的主要目的是更喜欢功能的聚合而不是继承。

您可以从中受益const(特别是通过参考返回的地方)。另外,我不确定您是否真的希望速度成为标量(或者是否应该以您似乎想要的那种过于通用的组件方法将其和位置出现在游戏对象中)。

您使用大型组件图和update()游戏对象中的调用的方法不是很理想(这对于那些首先构建这类系统的人来说是一个常见的陷阱)。它使更新期间的高速缓存一致性非常差,并且不允许您利用并发性和趋势来实现一次性处理大量数据或行为的SIMD风格的过程。通常最好使用一种设计,其中游戏对象不更新其组件,而是负责组件本身的子系统一次更新所有组件。可能值得一读。


这个答案很好。我仍然没有找到在评论中间隔段落的方法(输入键只是提交),但是我将解决这些问题。您对对象/组件系统的描述很有道理。因此,要弄清楚在PhysicsComponent的情况下,在该组件的构造函数中,我可以仅调用PhysicsManager的CreateRigidBody()函数,然后在该组件中存储对此的引用?
艾丹·奈特

至于“其他评论”部分,谢谢您。我混合使用字符串是因为我通常更喜欢使用所有Ogre函数作为使用Ogre的项目的基础,但是由于缺少map / pair / etc,我开始在Object / Component系统中进行切换。您说得对,但我已经将它们全部转换为std成员,以将其与Ogre分开。
艾丹·奈特

1
+1这是我一段时间以来在此阅读的有关基于组件的模型的第一个理智的答案,与过度概括化形成了很好的对比
Maik Semder 2011年

5
它提供了更好的一致性(数据和指令高速缓存均具有一致性),并为您提供了将更新几乎卸载到不同线程或工作进程(例如SPU)上的可能性。在某些情况下,甚至都不需要更新组件,这意味着您可以避免无操作调用update()方法的开销。还可以帮助您构建面向数据的系统-全部与批量处理有关,这利用了CPU体系结构的现代趋势。

2
+1代表“最重要的是完成工作而不是为完美的设计
苦恼

5

组件体系结构是一种替代方案,可以避免从同一节点继承所有元素而实现的大型层次结构,以及由此导致的耦合问题。

您正在显示具有“通用”组件的组件体系结构。您具有连接和分离“通用”组件的基本逻辑。具有通用组件的体系结构更难实现,因为您不知道哪个组件是。好处是这种方法是可扩展的。

使用定义的组件可以实现有效的组件体系结构。用定义的意思是定义接口,而不是实现。例如,GameObject可以具有IPhysicInstance,Transformation,IMesh,IAnimationController。这样的好处是您知道接口,而GameObject知道如何处理每个组件。

对术语过度概括

我认为这些概念尚不清楚。当我说组件时,并不意味着游戏对象的所有组件都必须从组件接口或类似的东西继承。但这是通用组件的方法,我认为这是您将其命名为组件的概念。

组件体系结构是运行时对象模型中存在的另一种体系结构。它不是所有情况下的唯一方法,也不是最好的方法,但是经验表明,它具有很多好处,例如低耦合。

区分组件体系结构的主要特征是将关系is-a转换为has-a。例如,对象体系结构是:

是一个架构

而不是对象体系结构具有-(组件):

具有架构

还有以属性为中心的体系结构:

例如,普通的对象体系结构是这样的:

  • 对象1

    • 位置=(0,0,0)
    • 方向=(0,43,0)
  • 对象2

    • 位置=(10,0,0)
    • 方向=(0,0,30)

以属性为中心的体系结构:

  • 位置

    • 对象1 =(0,0,0)
    • 对象2 =(10,0,0)
  • 取向

    • 对象1 =(0,43,0)
    • 对象2 =(0,0,30)

更好的对象模型架构?从性能的角度来看,最好以属性为中心,因为它对缓存更友好。

组件引用的关系是-a而不是has-a。组件可以是转换矩阵,四元数等。但是与游戏对象的关系是-a关系,游戏对象没有继承关系,因为它是继承的。游戏对象具有(关系具有-a)转换。这样,转换就是一个组成部分。

游戏对象就像一个枢纽。如果您想要一个始终存在的组件,那么该组件(例如matrix)应该在对象内部,而不是指针。

在所有情况下都没有一个完美的游戏对象,它取决于引擎以及引擎使用的库。也许我想要一台没有网格的相机。is-a体系结构的问题在于,如果游戏对象具有网格,那么从游戏对象继承的相机必须具有网格。借助componenet,摄像机可以进行转换,移动控制器以及任何您需要的东西。

有关更多信息,“ Jason Gregory”一书“ Game Engine Architecture”中的“ 14.2 Runtime Object Model Architecture”一章专门讨论了这一主题。

从那里提取UML图


我不同意最后一段,有过分概括的趋势。基于组件的模型并不意味着每个功能都必须是一个组件,仍然必须考虑功能和数据之间的关系。没有Mesh的AnimationController是无用的,因此将其放入网格中。没有转换的游戏对象不是游戏对象,根据定义它属于3D世界,因此将其作为普通成员放入游戏对象中。封装功能和数据的一般规则仍然适用。
Maik Semder 2011年

@Maik Semder:我在总体上过于笼统地同意您的观点,但是我认为该术语的组成部分尚不明确。我已经编辑了答案。阅读并给我您的印象。
momboco 2011年

@momboco好了,您不必再重复相同的观点了;)Transform和AnimationController仍被描述为组件,在我看来,这是对组件模型的过度使用。关键是定义应放入组件的内容以及不应该放入的内容:
Maik Semder 2011年

如果首先需要使游戏对象(GO)工作的东西,换句话说,如果它不是可选的而是强制性的,则它不是组件。组件应该是可选的。转换是一个很好的例子,对于GO来说根本不是选择,没有转换的GO毫无意义。另一方面,并​​非每个GO都需要物理,因此可以将其作为一个组成部分。
Maik Semder 2011年

如果AnimationController(AC)和Mesh之间的两个功能之间存在很强的关系,则无论如何都不要通过将AC压入一个组件来打破这种关系,相反,Mesh是一个完美的组件,但是AC属于进入网格。
Maik Semder 2011年
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.