我们从基本的系统-组件-实体方法开始。
让我们创建组合(源自长期本文章)仅仅是出于对信息类型的组件。它是在运行时动态完成的,就像我们将组件一个接一个地添加/删除到实体中一样,但是让我们更精确地命名它是因为它仅涉及类型信息。
然后,我们构造为每个实体指定集合的实体。创建实体后,它的组合是不可变的,这意味着我们无法直接对其进行修改,但是仍然可以获得现有实体对本地副本的签名(以及内容),对其进行适当的更改,然后创建一个新实体它的。
现在是关键概念:每创建一个实体,就会将其分配给一个名为assemblage bucket的对象,这意味着具有相同签名的所有实体都将位于同一容器中(例如,在std :: vector中)。
现在,系统只是遍历他们感兴趣的每个环节并完成工作。
这种方法有一些优点:
- 组件存储在几个(精确地:存储桶数)连续的内存块中-这提高了内存友好性,并且更容易转储整个游戏状态
- 系统以线性方式处理组件,这意味着改进了缓存一致性-再见字典和随机存储器跳转
- 创建新实体就像将组合映射到存储桶并将所需组件推回其向量一样容易
- 删除一个实体就像调用std :: move一样容易,将最后一个元素与删除的元素交换,因为此时顺序并不重要
如果我们有很多具有完全不同的签名的实体,那么缓存一致性的好处就会减少,但是我认为大多数应用程序都不会发生这种情况。
重新分配向量后,指针失效也存在问题-可以通过引入以下结构来解决:
struct assemblage_bucket {
struct entity_watcher {
assemblage_bucket* owner;
entity_id real_index_in_vector;
};
std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;
//...
};
因此,只要出于游戏逻辑中的某种原因,我们想要跟踪一个新创建的实体,就在存储桶中注册一个entity_watcher,并且一旦在删除期间必须将该实体std :: move移开,我们便会查找其观察者并进行更新他们real_index_in_vector
的新价值。在大多数情况下,这对每个实体删除都仅施加了一个字典查找。
这种方法还有其他缺点吗?
尽管很明显,为什么没有提到解决方案?
编辑:我正在编辑问题以“回答答案”,因为评论不足。
您将失去可插拔组件的动态特性,该特性是为摆脱静态类构造而专门创建的。
我不。也许我没有足够清楚地解释它:
auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket
就像获取现有实体的签名,对其进行修改并再次将其作为新实体上传一样简单。可插拔,动态性质?当然。在这里,我想强调的是,只有一个“组合”和一个“桶”类。存储桶由数据驱动,并在运行时以最佳数量创建。
您需要遍历可能包含有效目标的所有存储桶。没有外部数据结构,冲突检测可能同样困难。
好,这就是为什么我们拥有上述外部数据结构。解决方法很简单,只需在System类中引入一个迭代器即可检测何时跳转到下一个存储桶。的跳跃是纯粹透明的逻辑。