我最近为我的游戏引擎实现了一个实体组件系统。每个实体都会保留一个这样的组件映射:
Component.h
enum COMPONENT_INFO {
COMPONENT_POSITION = 0,
COMPONENT_PHYSICS,
COMPONENT_RENDERABLE
};
Entity.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.我只是为了测试目的而编写这些组件。
答案 0 :(得分:2)
首先,你的方法getComponent正在使用operator [],它不仅检索具有给定键的实例,而且如果不存在,则插入一个元素。
http://www.cplusplus.com/reference/map/map/operator%5B%5D/
在给定的实现中,您将自己限制为实体中每种类型的一个组件,这可能不是最佳选择。例如,您可能希望为一个实体提供一些可渲染组件,以执行各种操作,如粒子,网格渲染等。
Map不保证对象按顺序放置在内存中,这可能会导致逐个处理时出现缓存未命中。例如,使用std :: vector会更有效,并将COMPONENT_INFO保留在组件对象本身中。
另一种解决方案是向系统类添加可创建特定类型组件的方法,同时对每个组件实例进行一些操作。然后,系统可以保留每种组件类型的向量,并对这些组件进行批处理。
希望这会有所帮助。
编辑:
这个想法是当你添加/创建新的Collision组件时,组件本身会在系统中注册,提供类型和指向自身的指针。对于可渲染对象等也是如此。您可以为系统中的每种组件类型提供向量映射。当您需要解析collsion时,您只需检索所有可碰撞物(引用向量)。你进行一次log(n)查找,然后迭代所有可碰撞物(由于在向量中的指针在内存中按顺序排列,使其更加缓存友好)。
您是否也在发布版本中测试性能?
答案 1 :(得分: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
}
或者,如果您想拥有更多组件类型并希望保持设计灵活,您可能希望将它们存储在向量中,使用枚举值对其进行索引。这将完全避免地图查找,并为您提供更好的性能。
答案 2 :(得分:2)
由于COMPONENT_INFO
的最高数量已得到修复,您可以使用array<Component*>
(或vector<>
并使用例如myArray[COMPONENT_INFO::COMPONENT_PHYSICS]
进行寻址。您可能需要检查nullptr,虽然。