关于纯ECS(实体组件系统)和更新系统的问题

V3n*_*n0m 5 c++ architecture game-development game-engine entity-component-system

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

\n\n

为了从 ECS 中获益(例如缓存友好),需要满足以下要求:

\n\n
    \n
  • 实体必须只是一个 ID。
  • \n
  • 组件必须只是纯数据(没有逻辑的结构)。
  • \n
  • 系统包含逻辑和更新组件。
  • \n
  • 系统之间没有交互(而是系统通过将 \xe2\x80\x9cTag\xe2\x80\x9d 组件添加到实体来进行通信)。
  • \n
\n\n

因此,每个系统中应用的逻辑都很好,并且在没有 \xe2\x80\x9cuser 代码\xe2\x80\x9d 时一切正常。\n但是,当我们处理用户代码时(例如用户可以将 C++ 代码附加到一个对象(如 Unity、Unreal)),问题就来了:

\n\n
    \n
  1. 由于组件仅包含数据,因此当用户修改\n本地位置时,世界位置不会更新(当变换系统处理每个\n变换组件时,\n将计算世界位置。因此,如果用户请求世界位置\n修改其本地位置后\n,它将获得之前的世界位置\n而不是实际位置。
  2. \n
  3. 当一个实体被删除时,它的子实体也必须被删除。由于组件仅包含数据而不包含逻辑,因此子组件将不会被删除(它将在下一次父系统更新时删除)。因此,我们有一些 \xe2\x80\x9cdelay\xe2\x80\x9d (子级仍然可以访问,但将在下一次父系统更新时被删除)。
  4. \n
  5. 假设我们有实体 A、B、C。B 是 A 的子实体。在\n用户代码(附加到实体的 C++ 代码)中,用户设置 B 的父\n有 C,然后删除实体 A。当父系统将更新,它将检测到 A 已被删除(它还可以检测到实体 A 的父级已更改),但是系统如何知道实体 A 的父级更改后实体 A 是否已被删除B\也不是之前?
  6. \n
\n\n

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

\n\n

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

\n\n

谢谢!

\n

Fra*_*fer 4

我和你有同样的疑问。

读完之后我对我的解决方案进行了建模(这是必须诚实阅读的内容):

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;
};
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

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

我使用 ECS 的次数越多,我就越倾向于像用 C 语言编程一样思考。您拥有数据和更改该数据的函数。当然,您可以直接去更改组件数据,但我意识到不值得花时间试图避免这种情况,如果您在更新数据后需要更多东西的组件中执行此操作,那么这只是一个错误或糟糕的编程。