ECS体系结构之所以出色,是因为它在内存局部性方面表现非常出色,可以处理大量数据,并且可以将其自身用于多线程—这实际上只是在表面上体现了它的优势。 难怪Unity去年推出了新的ECS,Lumberyard也从他们从CryEngine继承的体系结构转移到了自己的ECS上。
但是有时您实际上并不想浏览大量数据。 有时候,在一千个对象中,您只想处理其中的几个。
我们在Mana Engine中发现的是,大多数时候您想对某些而非全部组件进行工作,它可以与以下三个事件之一联系在一起:创建,更新和销毁。
因此,Mana Engine支持知道何时新建,更改或更新组件。 许多代码库可能通过委托(即OnComponentCreated
)或需要设置处理程序和各种样板代码的消息传递系统来执行此操作。
在Mana Engine中,每个有权访问给定组件类型的AuthorizedSystem
本质上都可以访问已创建,更改或销毁的组件列表。 别大惊小怪。 没有设置。 没有钩子或处理程序。 没有回调。 只是已经提供给您的EntityIds
哈希集。
它的工作原理如下:
类MySystem:公共AuthorizedSystem
{
无效Update()
{
//获取对所需组件的访问权限。
自动worldProxy = GetWorldProxy ();
//对于创建的集合中的每个ID ...
for(EntityId id:GetCreatedSet ())
{// ^魔术在这里发生。
//通过此ID查找所有组件。
EntityView ev = worldProxy.Get(id);
//如果它是一个完整的EntityView ...
如果(ev)
{
//对实体进行处理。
}
}
}
};
让我们分解一下代码。
- 我们拥有一个对
MyComp1
和MyComp2
都具有写入权限的AuthorizedSystem
。 - 在
Update()
调用中,我们获得两个组件的WorldProxy
。 我们想知道在此帧中是否有任何新创建的MyComp1
组件,因此我们调用GetCreatedSet
并对其进行迭代。 - 在循环内部,我们只想处理完整的 EntityView,这意味着它们存在所有组件。 因此,我们调用
worldProxy.Get(id)
并在使用前检查EntityView's
完整性。EntityView
有一个布尔运算符,使我们可以使用语法if(ev)
来检查其完整性。 - 最后,我们使用
EntityView
。
您可以使用OnChangedSet
和OnDestroyedSet
进行类似的操作。
Mana Engine自动执行所有簿记。 但是,有时记账可能很昂贵。 有时,您只想遍历任何内容(更改或未更改)。 在定义允许您这样做的组件时,有一个关键字。
其中一部分是一种哲学,即不为不需要的东西付钱,并具有在不妨碍自己的方式实现自己想要的东西的灵活性。
这是一个很好的例子。
在我们的测试游戏中,压力测试之一是渲染100k +个运动对象。 当我们这样做时,我们可以假设对象的每一帧在其变换数据中都会有一个新值。 我们不想保留这样的事实,即这些转换中有100k +发生了变化。 相反,我们只是假设它们会相应地更改和更新其GPU缓冲区。
但是,在另一种情况下,我们要渲染数千个静态对象和几个动态对象。 在这种情况下,静态对象几乎不会移动。 因此,我们只希望根据已知发生的更改来更新它们,而不是每帧更新它们的GPU缓冲区。
为此,我们有两个组成部分。 StaticModel
和动态模型。 它们几乎相同,但是行为不同。
struct StaticModel
{
CHANGED_LIST_COMPONENT_DEFINITION();
矩阵变换
ModelId模型;
};构造DynamicModel
{
COMPONENT_DEFINITION();
矩阵变换
ModelId模型;
};
请注意, StaticModel
用CHANGED_LIST_COMPONENT_DEFINITION()
标记,而DyanmicModel
用COMPONENT_DEFINITION()
标记。
更好的是,由于它成为组件上的constexpr static bool
,因此,如果您要访问不存在的更改集,我们可以使用if constexpr
语句和static_asserts
在编译时知道。 像这样:
无效Update()
{
自动wp = GetWorldProxy ();
for(自动&& id:GetChangedSet ())
{// ^编译时错误。
//用它做点什么...
}
}
请注意,名称“静态”和“动态”表示这些对象的变换数据,与它们的实际着色器或网格中的顶点无关。 StaticModel可以引用永无动画的对象,例如岩石。 它也可以指的是动画(通常)不会改变其位置,方向或比例,例如风中摇曳的树木。
在撰写本文时,检查创建,更改和销毁组件的能力还很原始。 我们计划扩展它,并使该语法更易于用于您经常需要在一起的更复杂的行为。
作为一个简单的示例,在上面的代码中,我们获得了CreatedSet
的MyComp1
,但是我们更有可能想知道EntityView
完成。 这意味着我们必须首先查看是否有任何新的MyComp1
组件,然后检查是否有任何新的MyComp2
组件,并且如果任何一个都有新的组件,请检查整个实体是否存在。 这就是很多我们可以打包和重复使用的样板代码。
诸如此类的核心功能是注定要成为Mana Engine的基础。 继续跟踪以了解是什么!