-
Notifications
You must be signed in to change notification settings - Fork 0
Home
This first page is a quick reference for when you're looking to jog your memory. See table of contents to the right for a more detailed look.
- create
- query
- events
- extras
#include <entt/entity/registry.hpp>
Create a new entity.
entt::entity entity = registry.create();
- Entities are of type
std::uint32_t
per default, they don't store any data. Instead, they associate data; seeassign
.
Examples
// Typical usage
auto entity = registry.create();
// Serialise entities via to_integer
auto entity = registry.create();
auto serialised = entt::to_integer(entity); // default is std::uint32_t
// Create in bulk..
std::vector<entt::entity> entities(10);
registry.create(entities.begin(), entities.end());
// ..with pre-assigned component
std::vector<entt::entity> entities(10);
registry.create<Position, Orientation>(entities.begin(), entities.end());
Delete an entity, and all of its components.
registry.destroy(entity);
Examples
auto entity = registry.create();
registry.assign<Position>(entity);
registry.destroy(entity);
// entity and the assigned Position component was destroyed
#include <entt/entity/registry.hpp>
Create a new component.
registry.assign<Position>(entity);
- Components are initialised on assignment
- Initialiser arguments are optional, but recommended
- Only one component type may exist per entity
- Calling
assign
with an already existing type throws an exception (in Debug) - Use
replace
to replace a component - Or
remove
followed byreplace
to re-trigger theon_construct
handler -
replace
triggers theon_replace
handler, noton_construct
(despite also being constructed)
Examples
struct Position { float x, y, z; };
auto entity = registry.create();
registry.assign<Position>(entity, 1.0f, 2.0f, 4.0f);
The first argument is the entitiy with which to associate this new component, the remaining arguments are passed to the constructor of the component. It is the equivalent of:
Position position { 1.0f, 2.0f, 4.0f };
registy.assign<Position>(entity, position);
Passing no arguments default constructs the component.
registry.assign<Position>(entity); // Default constructed
The opposite of assign
, remove a single component from an entity.
registry.remove<Position>(entity);
Examples
auto entity = registry.create();
registry.assign<Position>(entity);
registry.remove<Position>(entity);
Replace an existing component.
Trying to replace a component not already assigned is undefined
registry.replace<Position>(entity, 1.0f, 5.0f);
Examples
auto entity = registry.create();
registry.assign<Position>(entity, 5.0f, 2.0f);
registry.replace<Position>(entity) // default constructed
Just assign, no matter what.
registry.assign_or_replace<Position>(entity);
Examples
If you're assigning in a loop, you may consider doing..
// somewhere..
registry.assign<Position>(entity);
// ..elsewhere
while (true) {
registry.replace<Position>(entity);
// other things..
}
However is Position
is removed for whatever reason during other things..
you run into trouble. And whilst you could..
while (true) {
if (!registry.has<Position>(entity)) {
registry.assign<Position>(entity);
} else {
registry.replace<Position>(entity);
}
// other things..
}
A more readable option might be..
while (true) {
registry.assign_or_replace<Position>(entity);
// other things..
}
Remove all components of any or all types.
registry.reset<Position>(); // all Position components
registry.reset(); // all components (and entities, because what good are they now?)
Examples
Useful for managing selection.
registry.reset<Selected>();
if (some_condition) {
registry.assign<Selected>(entity);
}
Or whether something is hovered.
void mouseMoveEvent(const MouseEvent& event) {
registry.reset<Hovered>();
registry.view<Position, Shape>().each([&](auto entity, const auto& pos, const auto& shape) {
if (contains(pos, shape, event.position())) {
registry.assign<Hovered>(entity);
}
});
}
Components can be explicitly queried from an entity.
auto pos = registry.get<Position>(entity);
Examples
auto entity = registry.create();
registry.assign<Position>(entity);
auto pos = registry.get<Position>(entity);
You typically won't get
components, instead you should use view
to iterate on components in bulk.
void on_position_created(auto entity) {
auto ori = registry.get<Orientation>(entity);
}
Iterate over a single or combination of components.
for (auto entity : registry.view<Position>()) {
// ...
}
This is the most common method with which to interact with entities and components.
Examples
Iterate over all entities with one specific type of component assigned
auto entity = registry.create();
registry.assign<Position>(entity);
for (auto entity : registry.view<Position>()) {
auto& pos = registry.get<Position>(entity);
pos.x += 1.0f;
}
Iterate over all entites that have a combination of components.
for (auto entity : registry.view<Name, Position, Color>()) {
const auto& name = registry.get<Name>(entity);
const auto& col = registry.get<Color>(entity);
auto& pos = registry.get<Position>(entity);
printf("Adding the red color to the x position of %s", name);
pos.x += col.r();
}
For convenient, you can also return two or more components from a single .get<>
.
for (auto entity : registry.view<Name, Position, Color>()) {
const auto& [name, col] = registry.get<Name, Color>(entity);
auto& pos = registry.get<Position>(entity);
printf("Adding the red color to the x position of %s", name);
pos.x += col.r();
}
Combine view
and get
into a single query with each
.
registry.view<Position>().each([](auto entity, auto& pos) {
pos.x += 1.0f;
});
Examples
If you don't need to access the entity
, then don't include it.
registry.view<Name, Position, Color>().each([](const auto& name, auto& pos, const auto& col) {
printf("Adding the red color to the x position of %s", name);
pos.x += col.r();
});
A view returns entities that carry all of the specified components. Like a filter. Sometimes it may make sense to use components strictly for filtering.
struct IsAlive {};
// ...
registry.view<IsAlive, Position>().each([](const auto& isalive, auto& pos) {
printf("An entity at (%d, %d) is alive!\n", pos.x, pos.y);
});
As an empty struct doesn't provide any information and serve no purpose inside of your loop, they would not be missed if not included. And that's what less
is for.
registry.view<IsAlive, Position>().less([](auto& pos) {
printf("An entity at (%d, %d) is alive!\n", pos.x, pos.y);
});
See how much more readable that is?
Check whether an entity has a component with registry::has
if (entt::has<Position>(entity)) {
// do work..
}
Example
Iterating over components is both branchless and guaranteed to never hand you a nullptr
. This is where view
is more pleasant to work with than get
, but in the few cases where you need get
, how can you be sure the component you're asking for exists?
auto id = registry.try_get<Id>(entity);
if (id) {
id.name = "Alan";
}
// Equivalent to
if (registry.has<Id>(entity)) {
auto id = registry.get<Id>(entity);
id.name = "Alan";
}
Call a function or method when a component has been constructed.
entt::on_construct<Position>(&onPositionConstructed);
Example
In object-oriented programming, types have a constructor. Constructors come in handy when there is more to initialisation than just de-nullifying initial values. With on_construct
you can achieve similar benefit, but in an ECS way.
struct Position {
float x, y, z;
};
void on_position_created(entt::entity ent, entt:registry& reg, Position& pos) {
pos.x = 0.5;
pos.y = pos.x / 2.0;
pos.z = pos.x + pos.z;
}
// Attach event handler
registry.on_construct<Position>().connect<&on_position_created>();
auto player = registry.create();
registry.assign<Position>(player); // <-- called here (after creation)
Alternatives
// Equally valid argument signatures; less arguments is faster
void on_position_created_1(entt::entity ent, entt:registry& reg, Position& pos) {}
void on_position_created_2(entt::entity ent, entt:registry& reg) {}
void on_position_created_3(entt::entity ent) {}
#include <entt/entity/observer.hpp>
Where on_construct
executes a callback on the exact moment of a component being constructed, an entt::observer
can defer a callback until explicitly called.
entt::observer newPositions { registry, entt::collector.group<Position>() };
auto entity = registry.create();
registry.assign<Position>(entity);
for (const auto e : newPositions) {
assert(e == entity);
}
Member | Description |
---|---|
entt::collector |
A.k.a. "rule", when to consider an event related to a given observer . Name inherited from the Entitas project |
entt::collector.group |
EnTT - Fast and Reliable ECS (Entity Component System)
Table of contents
Examples
Blog
- RAII
- Polymorphism
- Shared Components
- Intent System
- Input Handling
- Undo
- Operator Stack
- State
- Resources
- Interpolation
Resources
Extras