实体组件系统的设计

时间:2014-05-18 12:01:03

标签: c++11 theory entity-component-system

我想知道如何在C ++中实现最快版本的实体组件系统(从现在开始的ECS)。

首先,关于术语:

  • 场景实体的容器(在某些实现中 Systems
  • 组件是一个简单的数据存储(如位置,碰撞盒,渲染图像等)
  • 系统在符合系统要求的组件上执行逻辑(这可能是物理,播放器输入,简单渲染等)
  • 实体包含构成最终行为的几个组件

我列出了我们在下面提出的所有设计。


1。 “天真”的方式

场景包含所有无序的实体 随着系统更新,每个系统都必须遍历所有实体并检查每个实体是否包含所有必需的组件,然后对这些实体执行更新。

显然,当拥有大量系统和/或许多实体时,这种方式并不太高效。


2。使用“位掩码枚举”和映射

每个组件都包含位掩码形式的类型标识符(例如1u << 5 /二进制[0...]100000)。 然后,每个实体可以组成所有Component的类型标识符(假设所有typeID在实体内都是唯一的),所以它看起来像

1u << 5 | 1u << 3 | 1u << 1
binary [0...]101010

场景包含某种地图 系统可以轻松查找实体:

MovementSystem::update() {
    for (auto& kv : parent_scene_) { // kv is pair<TypeID_t, vector<Entity *>>
        if (kv.first & (POSITION | VELOCITY))
            update_entities(kv.second); // update the whole set of fitting entities
    }
}

优点

  • 比天真的方式更快

缺点

  • 系统必须每次都查找适当的实体
  • 位掩码(枚举)限制为多个位(uint32_t为32,unsigned long long至少为64),在某些情况下,您可能需要比位掩码允许的更多组件。

3。不使用系统

此方法由Danvil中的answer below描述。

赞成

  • 彻底摆脱位掩码。
  • 可能比设计#2更快。

缺点

  • 依靠dynamic_cast查找组件,而设计#2可以直接查找组件,然后安全static_cast

4。使用备用套件

skypjack中的answer below描述了此方法。他非常详细地解释了他的方法,所以我建议你阅读他的答案。

2 个答案:

答案 0 :(得分:2)

我发现另一种很有前途的方法,我在我的项目中使用过(请参阅GitHub上的EnTT)基于sparse sets。 我在组件池中使用它们,以跟踪哪个实体具有关联的组件以及其插槽的内容。

主要好处是:

  • 您可以免费获得具有特定组件的所有实体的小数组(有关详细信息,请参阅here),这样可以在迭代它们时提高性能。

  • 它将实际分配的组件数量保持在最低限度。而且,组件在存储器中都保持紧凑。

  • 缓存未命中率至少降低,因为您只需为使用的内容付费。换句话说,您只获得实际分配给实体的那些组件,并且它们在内存中彼此接近,根本没有 hole

您可以以额外数组的价格获得它,其最大长度等于每个组件池的实体数量(请注意,在现实世界的软件中,这些数组通常较小)。

基准测试显示,性能远远优于基于位掩码描述符的众所周知的实体组件系统(请参阅上面的链接以获取更多详细信息)。我还验证了内存压力或多或少相同,因为你摆脱了位掩码描述符数组,但你在组件池中引入了一组迷你数组。

查找多个组件时对实体集的迭代也可以通过一个技巧得到很大改进:找到较短的集合并迭代其实体(一个非常快的操作),然后验证第n个实体是否具有其他组件并最终归还。
基准测试证明它仍然比密集集上的基于位掩码的设计更快(其中每个实体都具有所有组件)。如果集合不那么密集(这是对现实世界软件的合理假设),性能肯定比基于位掩码的解决方案更好。

最后,与解决方案#4不同,在这种情况下不需要动态强制转换。

整件事给你一些我称之为实体组件注册表的东西。系统可以定义为lambdas,用于捕获可以传递注册表的注册表或函子。没有必要使用注册表本身注册系统。

我希望你明白这个实施背后的想法 如果您需要更多详细信息,请随时提出。

答案 1 :(得分:0)

我会说你称之为&#34;系统&#34;实际上是一个组件。渲染的示例:有一个组件Pose(用于3D位置旋转)和一个组件Mesh(保存顶点缓冲区)。现在,不是使用检查它是否可以呈现特定实体的函数,而是添加组件Renderer。此组件连接到PoseMesh组件。 &#34;系统&#34;渲染现在只需要与组件Renderer进行通信。并且每个实体都是可渲染的,或者现在是,每次都不需要检查组件,并且所有渲染工作都是作为组件收集的。


代码示例:

struct Pose : public Component { float x,y; };

struct Mesh : public Component { std::vector<Vertex> vertices; };

struct Renderer : public Component {
   Entity* entity;
   void render() {
       if(!mesh|| entity->componentsChanged) {
           mesh = entity->getComponent<Mesh>();
           if(!mesh) throw error;
       }
       if(!entity->pose) throw error;
       glTranslate(entity->pose->x, entity->pose->y);
       ...
   }
private:
   Mesh* mesh;
};

struct Entity {
    std::vector<Component*> components;
    bool componentsChanged;
    template<typename C> C* getComponent() const {
        for(Component* c : components) {
            C* cc = dynamic_cast<C>(c);
            if(cc) return cc;
        }
        return NULL;
    }
    // "fast links" to important components
    Pose* pose;
    Renderer* renderer;
    PhysicsStuff* physics;
};

struct Rendering
{
private:
    void render(const std::vector<Entity*>& entities) {
        for(Entity* e : entities) {
            if(!e->renderer) continue;
            e->renderer->render();
        }
    }
};