偏心ECS-第1部分

在使用Unity了很长时间(自v3x开始)并了解其功能和复杂性之后,我决定尝试构建自己的游戏引擎。

为什么?

好吧,如果您真的要问我这个问题,我会开玩笑地说:“好吧,为什么不呢?”但是,总的来说,我引擎的复杂性和功能着迷了,这是我想知道的理由关于,尝试创建和理解。 现在,我不期望以与Godot,Unity和Unreal相同的质量来创建完整的引擎,因此这是我为自己准备的一些指导原则/目标:

A.面向数据的设计-在大学时,我首先通过Noel Llopis的博客文章“面向数据的设计(或者为什么您可能会用OOP自杀)”被介绍给面向数据的设计,最终使我进入了Mike Acton的CPPCon主题演讲。 从那时起,我就一直以一种主要面向功能和面向数据的方式工作,因此这对我来说很有意义,我想突破自己的知识范围。

B.保持简单—再次重申一下,这是我探索复杂的引擎架构并能够对此进行推理的沙箱。 我不会为顶级游戏疯狂,而是一些小而简单的事情,最有可能是子弹般的地狱游戏,因此我可以利用面向数据的设计。

C.低级语言-我的大部分工作是用C#和Python完成的,距我真正接触C ++或另一种低级语言已经差不多一年了,所以这对我来说是一个很好的实践。 (C ++ 17)

D.多线程— CPU上的多个内核很有用,如何有效利用它们?

E.为了理智起见,进行单元/集成测试! 我将使用Catch2,因为它是一个非常有表现力的测试框架,易于设置。

因此,事不宜迟,让我们开始在这个由多部分组成的博客文章系列中介绍Eccentric ECS的全部含义。

第1部分。什么是实体组件系统

实体组件系统(ECS)是一种体系结构系统模式,其重点在于数据的构成和数据的操作,而不是对数据和行为进行建模以对现实世界进行建模。

类似地,面向数据的设计,当然还有实体组件系统,可以认为是一个巨大的电子表格,其中每一列代表数据的一个组成部分,每一行代表组成一个实体的一组相关数据。 我们的系统可以是对数据的列和行进行操作的函数。

第2部分:表示实体

以我的电子表格示例为基础,实体只是类似于行的数值(例如,行1、2、3、4等)的唯一ID。 在偏心ECS中,实体表示如下:

  结构实体{ 
unsigned int id ;

实体(){ id = 0; };
实体( 未签名的int id):实体(){ -> id = id; };

const bool运算子 ==( const实体其他) const {
返回ID ==其他。 id ;
}

const bool运算符 !=( const实体other) const {
返回id !=其他。 id ;
}
};

在我的情况下,它存储的只是一个无符号整数,因为我真正关心的是为实体分配唯一的ID。

通常,所有实体都是通过EntityManager管理的,该实体负责:

  1. 实体的创建/销毁
  2. 确定实体的存在

这样,我们可以这样表示一个EntityManager:

  class entity_manager { 

公众
实体create_entity();

void create_entities(size_t大小,实体entity []);
void destroy_entities(entity []实体);
布尔存在(实体e);

entity_manager();
〜entity_manager();
私人的
std :: vector current_entities ;
unsigned int last_entity ;
};

创建一个实体很简单,我们会跟踪last_entity并在需要创建新实体时将其递增。 这样最多可支持2³²个实体 。 现在,如果我要创建2³²+ 1个实体,则会收到一个溢出错误。 考虑到规模 ,我可能想重用某些已经释放了ID的实体,或者使用64位整数(而不是32位整数)进行查询。

在当前的标头中,我表达了元素数组的用法,而不是用于创建和销毁单个参数的用法。 最好是,我希望处理批量数据,而不是单独处理单个元素。 现在,根据这些操作的性能分析(带或不带多线程),此内容可能会更改。

一些事后思考

在当前的entity_manager中,我使用一个STL向量,目前它解决了管理实体的问题。 我一直在研究创建自己的自定义分配器,以便可以在这些块中分配一块内存和插槽实体。 这样做的好处是,我可以按组而不是单个地释放内存,并限制malloc调用的次数。 作为通用内存分配器的Malloc必须工作于任何大小(1 KB,1GB等),这可能效率很低。

稍后,我将在另一篇博客文章中重新讨论该主题!