Inheritance
Inheritance lets an entity use another entity as a base for reusable component defaults. Components are owned by inheriting entities by default; components that should remain shared can opt into shared inheritance.
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”Components use owned inheritance by default. If the base owns Health, linking
an instance copies the effective value into 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 owns its copied Health. Later writes to the
instance do not affect the base, and the base value is not synchronized back to
the instance.
Use EcsInheritShared when a component should remain stored on the base:
ECS_COMPONENT_DEFINE(Material, .inheritance = EcsInheritShared);For a native C++ component, use the component registration options:
ecs::component<Material>({ .inheritance = EcsInheritShared,});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 = base.get<Health>();const auto &instance_health = instance.get<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)) { std::puts("inherits");}ecs_is() checks whether the entity inherits from the target, including
transitive inheritance through intermediate bases.
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({ .components = { ecs_in(Health) },});
ecs_iter_t it = ecs_query_iter(q);while (ecs_iter_next(&it)) { const 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); } }}#include <cstdio>
ecs::query().require<Health>().each([](const Health &health) { printf("%d\n", health.value);});When a component is configured as shared and inherited, ecs_field_kind(&it, index) returns 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, ecs_field_kind(&it, 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; shared inherited fields are EcsFieldShared. |
ecs_out(T) and ecs_inout(T) |
Match owned T, including components materialized through owned inheritance. |
ecs_in_optional(T) |
May return an inherited shared field when T exists on a base. |
ecs_inout_optional(T) |
Ignores shared inherited fields and returns NULL unless T is owned. |
Writable Queries
Section titled “Writable Queries”Writable terms do not match shared inherited data. A query with
ecs_inout(Health) matches entities that own Health, including instances
where Health was materialized through the default owned inheritance:
ecs_query_id_t q = ecs_query({ .components = { ecs_inout(Health) },});ecs::query().each([](Health &health) { health.value += 1;});An instance configured with a shared Health inheritance is skipped by this
query. An explicit ecs_set() also creates an instance-owned override and the
query writes to that value.
Optional writable terms also ignore inherited shared values:
ecs_query_id_t q = ecs_query({ .components = { ecs_inout_optional(Health) },});ecs::query().each([](ecs::optional<Health> health) { if (health) health->value += 1;});For an entity that only inherits shared 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, .components = { ecs_in(Position) },});ecs::query() .is_a(base.id()) .require<Position>() .each([](const Position &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.