我正在使用ECS框架开发自己的游戏引擎。 这是我使用的ECS框架:
但是我对系统应该如何更新组件成员并通知其他系统感到困惑。 例如,我有一个这样的TransformComponent:
struct TransformComponent
{
Vec3 m_Position;
Float m_fScale;
Quaternion m_Quaternion;
};
很明显,如果更改了可渲染实体的TransformComponent的任何成员,则RenderSystem还应在渲染下一帧之前更新着色器统一的“ worldMatrix”。 因此,如果我在系统中执行“ comp-> m_Position = ...”,那么RenderSystem应该如何“通知” TransformComponent的更改?我想出了3种解决方案:
在更新成员后发送UpdateEvent,并在相关的System中处理该事件。这很丑陋,因为一旦系统修改了组件数据,它就必须发送这样的事件:
{
...;
TransformComponent* comp = componentManager.GetComponent<TransformComponent>(entityId);
comp->m_Position = ...;
comp->m_Quaternion = ...;
eventDispatcher.Send<TransformUpdateEvent>(...);
...;
}
使成员成为私有成员,并为每个组件类编写一个具有set / get方法(在set方法中包装事件发送)的相关系统。这将带来很多繁琐的代码。
不要更改任何内容,而是添加“可移动”组件。 RenderSystem将使用Update()方法中的“可移动”组件迭代地进行可渲染实体的更新。这可能无法解决其他类似问题,并且我不确定性能。
我无法提出一种解决此问题的优雅方法。我应该更改设计吗?
答案 0 :(得分:2)
我认为在这种情况下,最简单的方法将是最好的方法:您可以在读取/写入它的组件中保留指向Transform
组件的指针。
我认为使用事件(或其他间接方式,例如观察者)不能解决这里的任何实际问题。
Transform
组件非常简单-在开发过程中不会更改。对它的抽象访问实际上将使代码更加复杂且难以维护。
Transform
是一个组件,很多对象都会经常更改它,甚至大多数对象都会在每一帧对其进行更新。每次发生变化时发送事件都会产生成本-可能比将矩阵/向量/四元数从一个位置复制到另一个位置要高得多。
我认为使用事件或其他某种抽象方法无法解决其他问题,例如多个组件更新同一个Transform
组件,或者使用过时的转换数据的组件。
通常,渲染器仅在每一帧复制所有渲染对象的矩阵。将它们缓存在渲染系统中没有任何意义。
经常使用诸如Transform
之类的组件。在引擎的许多不同部分中,使它们变得过于复杂可能是一个问题,而使用最简单的解决方案(指针)将为您提供更大的自由度。
顺便说一句,还有一种非常简单的方法来确保RenderComponent
在转换完成后(例如PhysicsComponent
将读取转换 )-您可以将工作分为两部分步骤:
Update()
,系统可以在其中修改组件,并且
PostUpdate()
,其中系统只能从组件读取数据
例如,PhysicsSystem::Update()
可以将转换数据复制到各个TransformComponent
组件,然后RenderSystem::PostUpdate()
可以从TransformComponent
读取,而不会使用过时的数据。
答案 1 :(得分:1)
我认为这里有很多事情要考虑。我将分几部分,首先讨论您的意见。
关于您的解决方案1.考虑,您可以对布尔值执行相同的操作,或者分配一个用作标签的空组件。很多时候,在ECS中使用事件会使系统架构复杂化。至少,我倾向于避免这种情况,特别是在较小的项目中。请记住,充当标签的组件基本上可以认为是一个事件。
您的解决方案2遵循我们在1中讨论的内容。但是它揭示了有关此一般方法的问题。如果要在多个系统中更新TransformComponent,则直到最后一个系统对其进行更新之前,您都无法知道TransformComponent是否真的发生了更改,因为一个系统可以将其向一个方向移动,而另一个系统可以将其向后移动,让它移动就像你开始时一样。您可以通过在一个系统中仅更新一次TransformComponent来解决此问题。
这看起来像您的解决方案3.但也许反过来。您可能要在多个系统中更新MovableComponent,然后在ECS管道中只有一个系统,读取MovableComponent并写入TransformComponent。在这种情况下,仅允许一个系统在TransformComponents上进行写入非常重要。那时,如果有一个布尔值指示是否已移动它,就可以完美地完成工作。
直到这里,我们已经将性能(因为在TransformComponent不变的情况下避免在RenderSystem上进行某些处理)与内存进行了交换(因为我们正在以某种方式复制TransformComponent的内容。
// In your RenderSystem... if (renderComponent.lastTransformUpdate == transformComponent) { continue; } renderComponent.lastTransformUpdate = transformComponent; render(renderComponent);
最后一个是我的首选解决方案。但这还取决于系统的特性和您的关注点。与往常一样,不要盲目地优化性能。首先测量,然后比较。