如何在ECS框架中更新组件数据并通知系统?

时间:2018-10-23 11:02:35

标签: c++ game-engine amazon-ecs

我正在使用ECS框架开发自己的游戏引擎。 这是我使用的ECS框架:

  • 实体:只是一个用于连接其组件的ID。
  • Component:一种存储纯数据的结构,根本没有任何方法(因此,我可以编写.xsd来描述组件并自动生成C ++结构代码)。
  • 系统:处理游戏逻辑。
  • EventDispacher:向订阅者(系统)分发事件

但是我对系统应该如何更新组件成员并通知其他系统感到困惑。 例如,我有一个这样的TransformComponent:

struct TransformComponent
{
    Vec3 m_Position;
    Float m_fScale;
    Quaternion m_Quaternion;
};

很明显,如果更改了可渲染实体的TransformComponent的任何成员,则RenderSystem还应在渲染下一帧之前更新着色器统一的“ worldMatrix”。 因此,如果我在系统中执行“ comp-> m_Position = ...”,那么RenderSystem应该如何“通知” TransformComponent的更改?我想出了3种解决方案:

  1. 在更新成员后发送UpdateEvent,并在相关的System中处理该事件。这很丑陋,因为一旦系统修改了组件数据,它就必须发送这样的事件:

    {
        ...;
        TransformComponent* comp = componentManager.GetComponent<TransformComponent>(entityId);
        comp->m_Position = ...;
        comp->m_Quaternion = ...;
        eventDispatcher.Send<TransformUpdateEvent>(...);
        ...;
    }
    
  2. 使成员成为私有成员,并为每个组件类编写一个具有set / get方法(在set方法中包装事件发送)的相关系统。这将带来很多繁琐的代码。

  3. 不要更改任何内容,而是添加“可移动”组件。 RenderSystem将使用Update()方法中的“可移动”组件迭代地进行可渲染实体的更新。这可能无法解决其他类似问题,并且我不确定性能。

我无法提出一种解决此问题的优雅方法。我应该更改设计吗?

2 个答案:

答案 0 :(得分:2)

我认为在这种情况下,最简单的方法将是最好的方法:您可以在读取/写入它的组件中保留指向Transform组件的指针。

我认为使用事件(或其他间接方式,例如观察者)不能解决这里的任何实际问题。

  1. Transform组件非常简单-在开发过程中不会更改。对它的抽象访问实际上将使代码更加复杂且难以维护。

  2. Transform是一个组件,很多对象都会经常更改它,甚至大多数对象都会在每一帧对其进行更新。每次发生变化时发送事件都会产生成本-可能比将矩阵/向量/四元数从一个位置复制到另一个位置要高得多。

  3. 我认为使用事件或其他某种抽象方法无法解决其他问题,例如多个组件更新同一个Transform组件,或者使用过时的转换数据的组件。

  4. 通常,渲染器仅在每一帧复制所有渲染对象的矩阵。将它们缓存在渲染系统中没有任何意义。

经常使用诸如Transform之类的组件。在引擎的许多不同部分中,使它们变得过于复杂可能是一个问题,而使用最简单的解决方案(指针)将为您提供更大的自由度。


顺便说一句,还有一种非常简单的方法来确保RenderComponent在转换完成后(例如PhysicsComponent将读取转换 )-您可以将工作分为两部分步骤:

  1. Update(),系统可以在其中修改组件,并且

  2. PostUpdate(),其中系统只能从组件读取数据

例如,PhysicsSystem::Update()可以将转换数据复制到各个TransformComponent组件,然后RenderSystem::PostUpdate()可以从TransformComponent读取,而不会使用过时的数据。

答案 1 :(得分:1)

我认为这里有很多事情要考虑。我将分几部分,首先讨论您的意见。

  1. 关于您的解决方案1.考虑,您可以对布尔值执行相同的操作,或者分配一个用作标签的空组件。很多时候,在ECS中使用事件会使系统架构复杂化。至少,我倾向于避免这种情况,特别是在较小的项目中。请记住,充当标签的组件基本上可以认为是一个事件。

  2. 您的解决方案2遵循我们在1中讨论的内容。但是它揭示了有关此一般方法的问题。如果要在多个系统中更新TransformComponent,则直到最后一个系统对其进行更新之前,您都无法知道TransformComponent是否真的发生了更改,因为一个系统可以将其向一个方向移动,而另一个系统可以将其向后移动,让它移动就像你开始时一样。您可以通过在一个系统中仅更新一次TransformComponent来解决此问题。

  3. 这看起来像您的解决方案3.但也许反过来。您可能要在多个系统中更新MovableComponent,然后在ECS管道中只有一个系统,读取MovableComponent并写入TransformComponent。在这种情况下,仅允许一个系统在TransformComponents上进行写入非常重要。那时,如果有一个布尔值指示是否已移动它,就可以完美地完成工作。

直到这里,我们已经将性能(因为在TransformComponent不变的情况下避免在RenderSystem上进行某些处理)与内存进行了交换(因为我们正在以某种方式复制TransformComponent的内容。

  • 另一种无需添加事件,布尔值或组件的相同方法是在RenderSystem中进行所有操作。基本上,在每个RenderComponent中,您都可以保留上一次更新时TransformComponent的副本(或哈希),然后进行比较。如果不一样,请渲染它并更新您的副本。
// In your RenderSystem...

if (renderComponent.lastTransformUpdate == transformComponent) {
    continue;
}
renderComponent.lastTransformUpdate = transformComponent;
render(renderComponent);

最后一个是我的首选解决方案。但这还取决于系统的特性和您的关注点。与往常一样,不要盲目地优化性能。首先测量,然后比较。