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

vmpqdwk3  于 2023-05-02  发布在  其他
关注(0)|答案(2)|浏览(139)

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

  • Entity:只是一个连接其组件的ID。
  • 组件:一个存储纯数据的结构体,根本没有方法(所以我可以写。xsd来描述组件并自动生成C++结构代码)。
  • 系统:处理游戏逻辑。
  • EventDispacher:将事件分派给订阅方(系统)

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

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

显然,如果可渲染实体的TransformComponent的任何成员被更改,则RenderSystem还应在渲染下一帧之前更新着色器统一“worldMatrix”。所以如果我做“comp-〉m_Position = ...”,那么RenderSystem应该如何“注意到”TransformComponent的变化呢?我提出了三个解决方案:
1.更新成员后发送UpdateEvent,并在相关System中处理该事件。这是丑陋的,因为一旦系统修改组件数据,它必须发送这样的事件:

{
    ...;
    TransformComponent* comp = componentManager.GetComponent<TransformComponent>(entityId);
    comp->m_Position = ...;
    comp->m_Quaternion = ...;
    eventDispatcher.Send<TransformUpdateEvent>(...);
    ...;
}

1.将成员设为私有,并为每个组件类编写一个具有set/get方法的相关系统(将事件发送 Package 在set方法中)。这会带来很多繁琐的代码。
1.不做任何更改,但添加“可移动”组件。RenderSystem将在Update()方法中使用“Movable”组件迭代更新可渲染实体。这可能不能解决其他类似的问题,我不确定性能如何。
我想不出一个优雅的方法来解决这个问题。我应该改变我的设计吗?

rta7y2nd

rta7y2nd1#

我认为在这种情况下,最简单的方法将是最好的:你可以在读/写Transform组件的组件中保存一个指向它的指针。
我不认为使用事件(或其他一些间接方式,如观察者)解决了这里的任何真实的问题。

  1. Transform组件非常简单--它不会在开发过程中被更改。对它的抽象访问实际上会使代码更加复杂,更难维护。
  2. Transform是一个组件,它会频繁地为许多对象更改,甚至你的大多数对象会在每帧更新它。每次有变化时发送事件都有成本-可能比简单地将矩阵/向量/四元数从一个位置复制到另一个位置要高得多。
    1.我认为使用事件或其他一些抽象不能解决其他问题,比如多个组件更新同一个Transform组件,或者组件使用过时的转换数据。
    1.通常,渲染器仅在每帧复制渲染对象的所有矩阵。在渲染系统中缓存它们是没有意义的。
    Transform这样的组件经常使用。使它们过于复杂可能是引擎的许多不同部分的问题,而使用最简单的解决方案,指针,将给予您更大的自由。
    顺便说一句,还有一种非常简单的方法可以确保RenderComponent在更新后读取transform *(例如:例如通过PhysicsComponent)-您可以将工作分为两个步骤:
  3. Update(),其中系统可以修改组件,以及
  4. PostUpdate(),其中系统只能从组件读取数据
    例如,PhysicsSystem::Update()可能会将转换数据复制到相应的TransformComponent组件,然后RenderSystem::PostUpdate()可以只从TransformComponent读取,而不会有使用过时数据的风险。
xxb16uws

xxb16uws2#

我认为这里有很多事情要考虑。我将分成几部分,首先讨论你的问题。
1.关于您的解决方案1.考虑一下,你可以用布尔值做同样的事情,或者分配一个空的组件作为标记。很多时候,在ECS中使用事件会使系统架构过于复杂。至少,我倾向于避免它,特别是在较小的项目中。请记住,作为标记的组件基本上可以被认为是一个事件。
1.您的解决方案2是我们在1中讨论的后续。但它揭示了这种一般方法的一个问题。如果你在几个系统中更新你的TransformComponent,你不能知道TransformComponent是否真的改变了,直到最后一个系统更新了它,因为一个系统可以把它移到一个方向,另一个系统可以把它移回来,让它在你的滴答开始。你可以通过在一个系统中更新一次TransformComponent来解决这个问题。..
1.看起来像你的解决方案3。但也许反过来。您可以在多个系统中更新MovableComponent,然后在ECS管道中,使用单个系统阅读MovableComponent并写入TransformComponent。在这种情况下,只允许一个System在TransformComponents上写入是很重要的。在那个时候,有一个布尔值指示它是否已经被移动,将完美地完成这项工作。
在此之前,我们已经用性能(因为当TransformComponent没有改变时,我们避免了在RenderSystem上的一些处理)来换取内存(因为我们以某种方式复制了TransformComponent的内容)。

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

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

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

相关问题