如何正确访问C ++实体组件系统中的组件?


18

(我正在描述的内容基于此设计:什么是实体系统框架?,向下滚动即可找到它)

我在用C ++创建实体组件系统时遇到了一些问题。我有我的Component类:

class Component { /* ... */ };

实际上是要创建其他组件的接口。因此,要创建自定义组件,我只需实现接口并添加将在游戏中使用的数据:

class SampleComponent : public Component { int foo, float bar ... };

这些组件存储在Entity类中,该类为Entity的每个实例提供唯一的ID:

class Entity {
     int ID;
     std::unordered_map<string, Component*> components;
     string getName();
     /* ... */
};

通过哈希组件的名称将组件添加到实体(这可能不是一个好主意)。当我添加一个自定义组件时,它被存储为组件类型(基类)。

现在,另一方面,我有一个System接口,它在内部使用Node接口。Node类用于存储单个实体的某些组件(因为系统对使用实体的所有组件不感兴趣)。当系统必须使用时update(),它仅需要遍历由不同实体创建的存储节点。所以:

/* System and Node implementations: (not the interfaces!) */

class SampleSystem : public System {
        std::list<SampleNode> nodes; //uses SampleNode, not Node
        void update();
        /* ... */
};

class SampleNode : public Node {
        /* Here I define which components SampleNode (and SampleSystem) "needs" */
        SampleComponent* sc;
        PhysicsComponent* pc;
        /* ... more components could go here */
};

现在的问题是:我通过将实体传递给SampleSystem来构建SampleNode。然后,SampleNode“检查”该实体是否具有SampleSystem要使用的必需组件。当我需要访问Entity中所需的组件时,就会出现问题:该组件存储在Component(基类)集合中,因此我无法访问该组件并将其复制到新节点上。我已经通过将Componentdown转换为派生类型来暂时解决了这个问题,但是我想知道是否有更好的方法可以做到这一点。我知道这是否意味着重新设计我已经拥有的东西。谢谢。

Answers:


23

如果要将Components一起存储在集合中,则必须使用公共基类作为存储在集合中的类型,因此,当您尝试访问Component集合中的s 时,必须将其强制转换为正确的类型。可以通过巧妙地使用模板和typeid函数来消除尝试转换为错误的派生类的问题,但是:

使用这样声明的地图:

std::unordered_map<const std::type_info* , Component *> components;

一个addComponent函数,例如:

components[&typeid(*component)] = component;

和一个getComponent:

template <typename T>
T* getComponent()
{
    if(components.count(&typeid(T)) != 0)
    {
        return static_cast<T*>(components[&typeid(T)]);
    }
    else 
    {
        return NullComponent;
    }
}

您不会误会。这是因为typeid将返回一个指向组件运行时类型(最派生类型)的类型信息的指针。由于组件是使用该类型信息作为键存储的,因此由于类型不匹配,强制类型转换可能不会引起问题。您还可以对模板类型进行编译时类型检查,因为它必须是从Component派生的类型,否则static_cast<T*>将与匹配unordered_map

但是,您无需将不同类型的组件存储在公共集合中。如果您放弃Entity包含Components 的想法,而是每个都Component存储一个Entity(实际上,它可能只是一个整数ID),那么您可以将每个派生组件类型存储在其自己的派生类型集合中,而不是作为通用基本类型,然后通过该ID 查找Component“属于”的Entity

考虑到第二个实现比第一个实现更不直观,但是它可能被隐藏为接口后面的实现细节,因此系统用户无需关心。我不会评论哪个更好,因为我没有真正使用过第二个,但是我不认为使用static_cast是第一个实现所提供的对类型的有力保证的问题。请注意,它需要RTTI,根据平台和/或哲学信念,RTTI可能是问题,也可能不是问题。


3
我已经使用C ++近6年了,但是每周我都会学到一些新技巧。
knight666

谢谢回答。我将首先尝试使用第一种方法,如果以后再使用,可以考虑使用另一种方法。但是,该addComponent()方法也不必也是模板方法吗?如果定义了addComponent(Component* c),则添加的任何子组件都将存储在Component指针中,typeid并将始终引用Component基类。
费德里科

2
Typeid将为您提供所指向对象的实际类型,即使指针是基类也是如此
Chewy Gumball

我真的很喜欢chewy的回答,所以我尝试在mingw32上实现它。我遇到了fede rico提到的问题,其中addComponent()将所有内容都存储为一个组件,因为typeid返回的是所有组件的类型。这里有人提到,即使指针指向基类,typeid也应给出所指向对象的实际类型,但我认为它可能因编译器等而异。还有其他人可以确认这一点吗?我在Windows 7上使用g ++ std = c ++ 11 mingw32。我最终只是将getComponent()修改为模板,然后将类型保存为th
shwoseph 2013年

这不是特定于编译器的。您可能没有正确的表达式作为typeid函数的参数。
耐嚼口香糖

17

Chewy正确,但是如果您使用的是C ++ 11,则可以使用一些新类型。

const std::type_info*您可以使用std::type_index请参阅cppreference.com)代替地图中的键,它是的包装std::type_info。你为什么要使用它?在std::type_index实际存储与的关系std::type_info为指针,但这是一个指针少为你操心。

如果确实使用C ++ 11,我建议将Component引用存储在智能指针中。因此,地图可能类似于:

std::map<std::type_index, std::shared_ptr<Component> > components

可以添加一个新条目:

components[std::type_index(typeid(*component))] = component

哪里component是类型std::shared_ptr<Component>。检索对给定类型的引用Component可能类似于:

template <typename T>
std::shared_ptr<T> getComponent()
{
    std::type_index index(typeid(T));
    if(components.count(std::type_index(typeid(T)) != 0)
    {
        return static_pointer_cast<T>(components[index]);
    }
    else
    {
        return NullComponent
    }
}

另请注意使用static_pointer_cast代替static_cast


1
我实际上在自己的项目中使用了这种方法。
vijoc 2013年

这实际上很方便,因为我一直在学习使用C ++ 11标准作为参考的C ++。不过,我注意到的一件事是,我在网络上发现的所有实体组件系统都使用某种cast。我开始认为,如果没有强制转换,则无法实现此功能或类似的系统设计。
费德里科

@Fede将Component指针存储在单个容器中确实需要将其强制转换为派生类型。但是,正如Chewy指出的那样,您确实可以使用其他选项,这些选项不需要强制转换。我自己在设计中使用这种类型的演员表并没有发现任何“坏”之处,因为它们相对安全。
vijoc

@vijoc由于有时会引入内存一致性问题,因此有时会认为它们不好。
akaltar '17
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.