我已经编写了一个 ECS,但我对更新阶段有一些疑问。(在系统中( 我读过很多文章,但没有找到这类问题的参考资料。
为了从 ECS 中获益(例如,缓存友好(,它们满足以下要求:
- 实体必须只是一个 ID。
- 组件只能是纯数据(没有逻辑的结构(。
- 系统包含逻辑并更新组件。 系统
- 之间没有交互(相反,系统通过 向实体添加"标记"组件(。
因此,每个系统中应用的逻辑都很好,并且在没有"用户代码"时都可以工作。 但是,当我们处理用户代码时(例如,用户可以将C++代码附加到对象(如Unity,Unreal((,问题来了:
- 由于组件仅包含数据,因此当用户修改 本地位置,世界位置不更新(世界位置 将在转换系统处理每个时计算 转换组件。因此,如果用户在之后要求世界位置 修改其本地位置,它将获得先前的世界位置 而不是实际的。 删除
- 实体时,必须删除其子项。自从 组件只包含数据而不是逻辑,子项将 不会被删除(它将在下一次父系统更新中(。所以我们 有一些"延迟"(孩子们仍然可以访问,但会 在下一次父系统更新时删除(。
- 假设我们有实体 A、B、C.B. B 是 A 的子代。在 用户代码(附加到实体的C++代码(,用户设置父级 的 B 有 C,然后删除实体 A。父系统何时会 更新,它将检测到 A 已被删除,(它也可以检测 实体 A 的父级已更改(,但系统如何知道 如果实体 A 在实体 B 的父级更改后被删除 还是之前?
将逻辑添加到组件中会破坏纯 ECS 的优势(以缓存友好的方式对所有相同的组件执行相同的操作(,因此恕我直言,这不是解决方案。
有人有解决方案吗?我想知道您如何处理 ECS 实施中的此类问题。
谢谢!
我和你一样有同样的问题。
我在阅读后对我的解决方案进行了建模(这是必须诚实阅读的(:
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 语言编程一样思考。您有更改该数据的数据和函数。当然,您可以直接更改组件数据,但我意识到花时间试图避免这种情况是不值得的,如果您在更新数据后需要更多东西的组件中执行此操作,那只是一个错误或糟糕的编程。