Relationships between entities are very common in games. In a traditional object oriented game engine, like Godot, these relationships are obvious and easily implemented.
But entities and their components in Bevy’s ECS are a completely flat structure. So how can we represent relationships between our entities?
Bevy provides the ability to create a parent/child hierarchy as a feature on
top of its ECS through the bevy_hierarchy
crate. It does this by providing
two components:
Parent
Children
A parent just holds an Entity
struct Parent(pub Entity);
While Children
are a vector of Entity
struct Children(pub SmallVec<[Entity; 8]>);
The SmallVec
here means that we can store up to 8 entities inline before
falling back to allocating them on the heap.
When you have a game that has planets orbiting a sun, naturally you would want it so we could move all planets relative to the sun’s position. This is what entity relationships help us achieve.
Creating a hierarchy
To create a parent/child relationship we can spawn our entities and use
add_child
on our EntityCommands
fn spawn_entities(mut commands: Commands) {
let parent = commands.spawn_empty().id();
let child = commands.spawn_empty().id();
// Add to the front of the parents Children
commands.entity(parent).add_child(child);
// Add children the end of Children
commands.entity(parent).push_children(&[child]);
// Insert children at a particular position pushing the rest back
commands.entity(parent).insert_children(0, &[child]);
// Set children by removing the old children
commands.entity(parent).replace_children(&[child]);
}
This will update the Parent
component of our child
entity to be the parent
entity. It will also add the child
entity to our parent
entity’s Children
component.
If we wanted to build them all at once instead of one at a time we can use
EntityCommands::with_children
and pass a closure:
fn spawn_child(mut commands: Commands) {
commands
.spawn_empty()
.with_children(|parent| {
parent.spawn_empty();
});
}
Removing children
We can remove children from a parent by adding or removing Parent
and changing the members of our Children
component.
// Remove all children
commands.entity(parent).clear_children();
// Remove specific children
commands.entity(parent).remove_children(&[child]);
// Removes like clear_children but from the other way
commands.entity(child).remove_parent();
Removing here means removing the Parent
component on all children and removing
the Children
component from the parent.
When you remove_parent
:
- The
Parent
component gets removed from thechild
- The
child
is removed from theChildren
of theparent
.
Iterating ancestors
When we want to enumerate over our parents, grandparents, etc, we use
iter_ancestors
on a query of Parent
.
This will iterate finding the parent and then its parent until no further
Parent
component is found.
fn iterate_ancestors(
query: Query<Entity, With<Player>>,
parent_query: Query<&Parent>
) {
let player = query.single();
for ancestor in parent_query.iter_ancestors(player) {
info!("{:?} is an ancestor of {:?}", ancestor, player);
}
}
Iterating children
From the other way, we can fetch the children using iter_descendants
.
This will perform a breadth-first search down the children, and their children etc.
fn iterate_children(
query: Query<Entity, With<Player>>,
children_query: Query<&Children>
) {
let player = query.single();
for child in children_query.iter_descendants(player) {
info!("{:?} is a descendant of {:?}", child, player);
}
}
Child changed events
Hierarchy events are automatically added to your app through the hierarchy plugin. When you add, remove or move children these events will fire allowing you to react to them in your systems.
ChildAdded
which has aparent
andchild
each containing an entityChildRemoved
which hasparent
andchild
each containing an entityChildMoved
which has achild
,previous_parent
andnew_parent
all each being an entity
Moving children relative to their parents
Transform
and Visibility
components will be effected by your hierarchies.
This is provided automatically by the propagate_transforms
system added from the
bevy_transform
crate. This crate is aware of our hierarchies (the Parent
and
Children
components).
When you have Children
their transform be local to their Parent
so long as
they both have the component. Same goes for Visibility
, if a parent is not
visible then neither will its children.
Aery
This crate provides an early implementation
of entity relationships that differs from bevy_hierarchy
in some key ways:
Aery stores Edges
component on each related entity, which has 8 empty
HashMap
in it by default. These hash maps will have TypeId
as key and
IndexSet<Entity>
as values.
This is quite different to bevy_hierarchy
where Parent
is just Entity
and
Children
has a smallvec
of four entities.
Aery is also without any ChildAdded
, ChildRemoved
etc. events.
But in exchange Aery provides breadth first searches, joins, diamond diagrams and other goodies we can use along side the bevy hierarchy crate.