在 EnTT 框架中, 注册表 用于存储和管理 实体 及其 组件
基本注册表 basic_registry 让用户自行决定用于表示 实体 的最佳类型, 由于 std::uint32_t 对于大多数情况都已经够用, 所以 EnTT 提供了枚举类 entt::entity, 它封装了 std::uint32_t, 并提供了别名 entt::registry, 相当于 entt::basic_registry<entt::entity>
实体 是由 标识符 所表示的, 一个实体的 标识符 包含了
- 实体的唯一 ID
- 实体的版本信息
除了默认的实体标识符类型 entt::entity 之外, 用户也可以使用自定义的实体类型, 可以是枚举类或具有 entity_type 成员的类, 例如
struct CustomEntity {
using entity_type = std::uint64_t; // 定义实体类型为 uint64_t
};
using CustomRegistry = entt::basic_registry<CustomEntity>;创建和销毁实体
注册表同时用于创建和销毁实体
// constructs a naked entity with no components and returns its identifier
auto entity = registry.create();
// destroys an entity and all its components
registry.destroy(entity);也可以通过传入两个迭代器来一次性生成或销毁一组实体
std::vector<entt::entity> entities(10); // 容器存储实体
registry.create(entities.begin(), entities.end()); // 批量创建 10 个实体
// destroys all the entities in a range
auto view = registry.view<a_component, another_component>();
registry.destroy(view.begin(), view.end());destroy 在销毁实体之前, 会去查询 组件池, 找到和该实体相关联的所有 组件 一并销毁
而如果已经知道要销毁的实体是 孤立实体, 即不存在任何附加组件, 那我们可以省下查询 组件池 的开销, 使用 release 方法
registry.release(entity); // 释放孤立实体
registry.release(entities.begin(), entities.end());不管是哪种情况, 一旦实体标识符被释放, 注册表 就可以在内部重新利用它, 具体来说, 标识符的版本部分会 增加 (用户也可以手动重载这个行为, 比如手动指定版本号, 而不是默认的递增)
注册表提供了 valid 方法用于验证实体标识符是否仍然有效, current 用于查询某个标识符最新的版本号
// returns true if the entity is still valid, false otherwise
bool b = registry.valid(entity);
// gets the actual version for the given entity
auto curr = registry.current(entity);也可以用原样解析方法来提取存储的标识符中的信息, 例如
// gets the version contained in the entity identifier
auto version = entt::to_version(entity);组件
添加
组件 可以动态的添加到实体, 或从实体上删除, 注册表的 emplace 模版函数用于为实体分配一个指定类型的组件,并对组件进行初始化
// registry.emplace<ComponentType>(entity, args...);
// 为实体添加一个 position 组件,并用参数 (0., 0.) 初始化
registry.emplace<position>(entity, 0., 0.);
// 为实体添加一个 velocity 组件,稍后手动赋值
auto &vel = registry.emplace<velocity>(entity);
vel.dx = 0.;
vel.dy = 0.;它默认会检测 组件 内部的 聚合类型, 并在可能的情况下使用 可变参数 进行 聚合初始化, 因此没有必要为每种类型定义一个 构造函数
insert 方法用于批量操作
- 为所有实体分配相同的组件
可以为范围内的所有实体分配同类型的组件, 组件可以是默认初始化的, 也可以是用户自定义的
// 默认初始化的组件
registry.insert<position>(first, last);
// 使用用户定义的实例
registry.insert(from, to, position{0., 0.});- 为每个实体分配不同的组件 (组件和实体范围要匹配)
如果每个实体需要分配不同的组件实例,可以提供一个组件实例的范围, 组件范围的长度必须与实体范围一致
// 为实体范围指定组件实例范围
registry.insert<position>(first, last, instances);更新
当需要更新实体的组件时, 可以使用以下方法:patch, replace 和 emplace_or_replace
patch 用于就地修改已有的组件内容, 当确认实体拥有某组件时, 可以用它修改组件的部分属性, 该方法接收一个回调函数, 回调函数的参数是组件的引用, 可以直接修改其属性
registry.patch<position>(entity, [](auto &pos) {
pos.x = pos.y = 0.;
});replace 用于完全替换已有的组件实例, 当确认实体拥有组件且希望用一个新的实例替换它
registry.replace<position>(entity, 0., 0.);emplace_or_replace 则无论实体是否已经拥有组件, 都可以使用, 如果实体没有该组件, 会创建并初始化一个新组件, 如果已有该组件, 则直接替换
registry.emplace_or_replace<position>(entity, 0., 0.);
// 用于替代以下代码
if (registry.all_of<velocity>(entity)) {
registry.replace<velocity>(entity, 0., 0.);
} else {
registry.emplace<velocity>(entity, 0., 0.);
}查询
all_of 和 any_of 用于检查实体是否拥有特定的组件
all_of 检查实体是否拥有指定集合中的 所有组件, 如果实体同时拥有所有指定的组件, 则返回 true 否则返回 false
bool all = registry.all_of<position, velocity>(entity);any_of 检查实体是否拥有指定集合中的 至少一个组件, 如果实体拥有至少一个指定的组件, 则返回 true 否则返回 false
bool any = registry.any_of<position, velocity>(entity);删除
erase, remove 和 clear 提供组件删除功能
erase 移除指定实体上的特定组件, 如果实体不拥有该组件, 调用会引发运行时错误, 所以需要确定实体拥有被删除的组件
registry.erase<position>(entity);remove 用于安全地移除组件, 如果实体拥有该组件, 则移除它, 否则无操作并安全返回, 所以它会多一次检查是否拥有组件
registry.remove<position>(entity);clear 用于 除所有实体中的指定组件 或 销毁注册表中的所有实体
// 销毁所有实体上的 position 组件
registry.clear<position>();
// 销毁注册表中的所有实体
registry.clear();访问
可以通过 get 和 try_get 方法方便地访问实体的组件
get 方法返回实体的组件 引用, 如果实体没有指定的组件, 会引发运行时错误, 它支持同时访问一个或多个组件
// 单组件
auto &renderable = registry.get<renderable>(entity);
const auto &crenderable = cregistry.get<renderable>(entity); // const 版本
// 多组件
auto [pos, vel] = registry.get<position, velocity>(entity);
const auto [cpos, cvel] = cregistry.get<position, velocity>(entity); // const 版本try_get 与之类似, 如果实体拥有指定组件, 返回指向组件的指针, 否则返回 nullptr
auto *renderable = registry.try_get<renderable>(entity);
if (renderable) {
// 安全访问组件
renderable->visible = true;
}try_get 不支持同时访问多个组件, 如果需要访问多个组件, 可分别调用 try_get
auto *pos = registry.try_get<position>(entity);
auto *vel = registry.try_get<velocity>(entity);
if (pos && vel) {
pos->x += vel->dx;
pos->y += vel->dy;
}