首页 > 解决方案 > 关于纯 ECS(实体组件系统)和更新系统的问题

问题描述

我已经写了一个 ECS,但我对更新阶段有一些疑问。(在系统中)我读过很多文章,但没有找到对这类问题的引用。

为了从 ECS 中受益(例如缓存友好),它们是以下要求:

因此,每个系统中应用的逻辑都很好,并且在它们不是“用户代码”时都可以正常工作。但是,当我们处理用户代码时(例如,用户可以将 C++ 代码附加到对象(如 Unity、Unreal)),问题就来了:

  1. 由于组件只包含数据,当用户修改本地位置时,世界位置不会更新(世界位置将在Transform System处理每个Transform Component时计算。所以如果用户在修改后询问世界位置它的本地位置,它将获得以前的世界位置,而不是实际位置。
  2. 当一个实体被移除时,它的孩子必须被移除。由于组件仅包含数据而不包含逻辑,因此不会删除子组件(它将在下一次父系统更新中)。所以我们有一些“延迟”(孩子仍然可以访问,但将在下一次父系统更新时被删除)。
  3. 假设我们有实体 A、B、C。B 是 A 的子代。在用户代码(实体附加的 c++ 代码)中,用户设置 B 的父代有 C,然后删除实体 A。当父系统将更新,它会检测到 A 已被移除,(它也可以检测到实体 A 的父级已更改)但是系统如何知道实体 A 是否在实体 B 的父级更改之后或之前已被删除?

在组件中添加逻辑将破坏纯 ECS 的优势(以缓存友好的方式对所有相同的组件执行相同的操作),所以恕我直言,这不是一个解决方案。

有人有解决方案吗?我想知道您如何处理 ECS 实施中的此类问题。

谢谢!

标签: c++architecturegame-enginegame-developmententity-component-system

解决方案


我和你有同样的问题。

阅读后我模拟了我的解决方案(这是必须诚实阅读的):

Gamasutra:将面向数据的 ECS 与有状态的外部系统同步

一个可能的解决方案是设置一些关于读写组件的规则。

我遵循从组件数据中读取总是可以的规则,但是如果您必须将数据写入外部系统内的组件(它不是组件接口系统的一部分),您必须始终使用转换系统功能。

例如:

有一个像这样的转换组件:

struct transform {
    glm::vec2 position = glm::vec2(0);
    glm::vec2 scale = glm::vec2(1);
    float rot_radians = 0.0f;
    glm::mat3 ltp = glm::mat3(1);
    glm::mat3 ltw = glm::mat3(1);
    entt::entity parent = entt::null;
    std::vector<entt::entity> children;
};

我将定义一些系统来对其进行更改,如下所示:

void set_position(entt::registry& r, entt::entity e, glm::vec2 position);
void set_rotation(entt::registry& r, entt::entity e, float rot_radians);
void set_scale(entt::registry& r, entt::entity e, glm::vec2 scale);
void set_parent(entt::registry& r, entt::entity to, entt::entity parent = entt::null);

在这些函数中,您可以自由地读取/写入转换组件数据。

我使用 ECS 的次数越多,我就越倾向于认为自己是在用 C 编程。你拥有可以更改这些数据的数据和函数。当然,您可以直接更改组件数据,但我意识到花时间试图避免这种情况是不值得的,如果您在更新数据后需要更多东西的组件中进行操作,这只是一个错误或糟糕的编程。


推荐阅读