Inheritance
Inheritance lets an entity use another entity as a base for shared component data. It is useful for prototypes, presets, and categories where many entities share the same default values.
An entity can inherit from one base. That base must be marked with the built-in
Abstract component.
Create A Base
Section titled “Create A Base”ecs_entity_t base = ecs_new();ecs_set(base, Health, { 100 });ecs_set(base, Speed, { 4.0f });ecs_add(base, Abstract);struct Health { int value; };struct Speed { float value; };
auto base = ecs::entity::create();base.set(Health{ 100 });base.set(Speed{ 4.0f });base.abstract();Create the base with ecs_new(), add the default component values, then add
Abstract.
Once an entity has Abstract, normal component add, set, and remove operations
on that entity are rejected. Put component data on the base before marking it
abstract.
Use a normal entity instead of an abstract one when you need to keep mutating its components directly.
Create An Instance
Section titled “Create An Instance”Create another entity and link it to the abstract base with ecs_is_a():
ecs_entity_t instance = ecs_new();ecs_is_a(instance, base);auto instance = ecs::entity::create().is_a(base);ecs_is_a(instance, base) moves instance to an archetype table whose
type records base as its inheritance target. Local component data already on
the instance is kept.
An entity has one base at a time. Calling ecs_is_a() again with the same base
is a no-op. Calling it with another abstract base replaces the inheritance
target in the entity type and moves the entity to the matching archetype table.
The target must be alive, abstract, and different from the instance. Cycles are rejected, so an entity cannot inherit from itself or from one of its descendants.
Inherit Components
Section titled “Inherit Components”Inherited components are shared from the base. If the base owns Health and the
instance does not, read-only queries can still see Health on the instance:
ecs_entity_t base = ecs_new();ecs_set(base, Health, { 100 });ecs_add(base, Abstract);
ecs_entity_t instance = ecs_new();ecs_is_a(instance, base);struct Health { int value; };
auto base = ecs::entity::create();base.set(Health{ 100 });base.abstract();
auto instance = ecs::entity::create().is_a(base);In this example, instance inherits Health from base. The inherited value is
not copied into the instance. It remains stored on base, and all instances that
inherit it read the same shared value.
Override Components
Section titled “Override Components”Add or set a component on the instance to override the inherited value:
ecs_set(instance, Health, { 75 });instance.set(Health{ 75 });After this, instance owns its own Health. Queries read the instance value
instead of the base value, and writes affect only the instance:
const Health *base_health = ecs_get(base, Health);const Health *instance_health = ecs_get(instance, Health);const auto *base_health = static_cast<const Health *>( ecs_get_cid(base.id(), ecs::component<Health>()));const auto *instance_health = static_cast<const Health *>( ecs_get_cid(instance.id(), ecs::component<Health>()));Local components are independent from inherited components. Adding
ecs_set(instance, Mana, { 20 }) does not change the base, and changing
the base does not rewrite components already owned by instances.
Inheritance Checks
Section titled “Inheritance Checks”Use ecs_is() to test inheritance:
if (ecs_is(instance, base)) { /* instance inherits from base */}if (instance.is(base)) { // instance inherits from base}ecs_is() checks whether the entity is directly linked to the target.
Query Matching
Section titled “Query Matching”Queries can match inherited components for read-only terms. A query with
ecs_in(Health) matches an instance that inherits Health from its base:
ecs_query_id_t q = ecs_query({ .terms = { ecs_in(Health) },});
ecs_iter_t it = ecs_query_iter(q);while (ecs_iter_next(&it)) { Health *health = ecs_field(&it, 0);
for (uint32_t i = 0; i < it.count; i++) { if (ecs_field_is_shared(&it, 0)) { /* health points to the base value, shared by the whole batch */ printf("%d\n", health->value); } else { /* health points to the entity-owned component array */ printf("%d\n", health[i].value); } }}ecs::query().require<Health>().each([](const Health &health) { // A const callback can read owned or inherited Health. printf("%d\\n", health.value);});When a field is inherited, it.field_kinds[index] is EcsFieldShared. The
pointer returned by ecs_field() points to the component stored on the base, not
to an array with one element per entity.
When a field is owned by the matched entities, it.field_kinds[index] is
EcsFieldOwned. The pointer is a normal component array indexed with i.
The inheritance rules for query fields are:
| Term | Inherited component behavior |
|---|---|
ecs_in(T) | Matches owned or inherited T; inherited fields are EcsFieldShared. |
ecs_out(T) and ecs_inout(T) | Match only entities that own T locally. |
ecs_in_optional(T) | May return an inherited shared field when T exists on a base. |
ecs_inout_optional(T) | Ignores inherited shared fields and returns NULL unless T is owned locally. |
Writable Queries
Section titled “Writable Queries”Writable terms do not match shared inherited data. A query with
ecs_inout(Health) only matches entities that own Health locally:
ecs_query_id_t q = ecs_query({ .terms = { ecs_inout(Health) },});ecs::query().each([](Health &health) { // Mutable terms match only entity-owned Health. health.value += 1;});This keeps writes unambiguous. If an instance only inherits Health, it is
skipped by this query. If the instance overrides Health with ecs_set(), it
matches and the query writes to the instance-owned value.
Optional writable terms also ignore inherited shared values:
ecs_query_id_t q = ecs_query({ .terms = { ecs_inout_optional(Health) },});// The C++ query builder has no optional writable-term helper yet.ecs_query_id_t q = ecs_query({ .terms = { ecs_inout_optional(ecs::component<Health>()) },});For an entity that only inherits Health, this field is EcsFieldNone and
ecs_field() returns NULL.
Restrict A Query To A Base
Section titled “Restrict A Query To A Base”Queries can be restricted to entities that inherit from a base with
ecs_query_desc_t.is_a:
ecs_query_id_t q = ecs_query({ .is_a = base, .terms = { ecs_in(Position) },});ecs::query() .is_a(base.id()) .require<Position>() .each([](const Position &position) { (void)position; });This matches entities that inherit from base and also satisfy the component
terms. The base relationship is transitive: if Knight inherits from Player
and Player inherits from Character, a query with .is_a = Character matches
Knight.
Common Pattern
Section titled “Common Pattern”Use inheritance for shared defaults, then override only the values that differ:
ecs_entity_t enemy = ecs_new();ecs_set(enemy, Health, { 100 });ecs_set(enemy, Speed, { 2.5f });ecs_add(enemy, Abstract);
ecs_entity_t boss = ecs_new();ecs_is_a(boss, enemy);ecs_set(boss, Health, { 500 });struct Health { int value; };struct Speed { float value; };
auto enemy = ecs::entity::create();enemy.set(Health{ 100 });enemy.set(Speed{ 2.5f });enemy.abstract();
auto boss = ecs::entity::create().is_a(enemy);boss.set(Health{ 500 });boss inherits Speed from enemy, but owns its own Health.