1

我最近一直在为我的游戏引擎实现一个实体组件系统。每个实体都有一个这样的组件映射:

组件.h

enum COMPONENT_INFO {
    COMPONENT_POSITION = 0, 
    COMPONENT_PHYSICS,
    COMPONENT_RENDERABLE
 };

实体.h

class Entity {
public:
    std::bitset<NUM_COMPONENTS> componentBits;
    std::map<COMPONENT_INFO, Component*> components;
    ..
    ..

    Component *getComponent(COMPONENT_INFO inf) {
        return components[inf];
    }
 };

我的 System 类会像这样更新每帧的每个实体:

void update(Entity *e, float delta) { 
        PositionComponent *cmp=(PositionComponent*)e->getComponent(COMPONENT_INFO::COMPONENT_PHYSICS);
        //x += 1.0f;
        cmp->x += 1.0f;
    }

一切都按预期工作,但我在访问地图时遇到了巨大的性能问题。当我创建 10000 个实体并迭代它们(系统实际上迭代)时,我得到 80 FPS 和蓝色空白系统(没有视觉效果,只是纯屏幕)。但是当我注释掉访问部分并只使用x += 1.0f;时,FPS 会惊人地增加到 1000。就像这样:

void update(Entity *e, float delta) { 
        x += 1.0f; //btw the system has a local x value.
    }

所以问题只是通过地图访问组件。我还能在这样的系统上使用什么?或者我在访问地图时做错了什么?

重要编辑:这只是一个测试驱动器,我的意思是每个实体可能有很多组件,而不仅仅是 3 个。我写这些只是为了测试目的。

4

3 回答 3

2

由于您的最高数量COMPONENT_INFO是固定的,您可以使用array<Component*>(或vector<>并使用例如myArray[COMPONENT_INFO::COMPONENT_PHYSICS]用于寻址。不过,您可能必须检查 nullptr。

于 2013-07-29T09:51:37.463 回答
2

首先,您的方法 getComponent 正在使用运算符 [] ,它不仅检索具有给定键的实例,而且如果不存在,则插入一个元素。

http://www.cplusplus.com/reference/map/map/operator%5B%5D/

在给定的实现中,您将自己限制为实体中每种类型的一个组件,这可能不是最佳选择。例如,您可能希望为一个实体提供一些可渲染组件,以执行各种操作,例如粒子、网格渲染等。

Map 不保证对象按顺序放置在内存中,这可能会在逐个处理时导致缓存未命中。例如,使用 std::vector 并将 COMPONENT_INFO 保留在组件对象本身中会更有效。

另一种解决方案是向您的系统类添加方法,这些方法将创建特定类型的组件,并在这样做的同时对每个组件实例进行一些预订。然后系统可以保留每种组件类型的向量并对它们进行批处理。

希望这会有所帮助。

编辑:

这个想法是,当您添加/创建新的碰撞组件时,组件本身会在系统中注册,并提供指向自身的类型和指针。可渲染对象等也是如此。您可以为系统中的每种组件类型提供矢量映射。当您需要解决碰撞时,您只需检索所有可碰撞对象(对向量的引用)。您进行一次查找,即 log(n),然后遍历所有可碰撞对象(由于位于向量中的指针在内存中按顺序排列,使其对缓存更加友好)。

您还在发布版本中测试性能吗?

于 2013-07-29T09:16:21.827 回答
2

由于每个实体的组件数量似乎是固定的,我会放弃映射并使用 Entity 中的成员变量来保存每个组件以及一个 getter。这也将避免您的更新功能中的演员:

class Entity {
private:
    PositionComponent* m_positionComponent;
    PhysicsComponent* m_physicsComponent;
    RenderableComponent* m_renderableComponent;
public:
    // initialize the components in the constructor
    PositionComponent* getPositionComponent() {
        return m_positionComponent;
    }

    // more getters for the other components
}

或者,如果您想拥有更多组件类型并希望保持设计的灵活性,您可能希望将它们存储在一个向量中,使用枚举值对其进行索引。这将完全避免地图查找,并为您提供更好的性能。

于 2013-07-29T09:17:51.447 回答