Bevy Hierarchy
Bevy stores both entities and components in completely flat arrays and uses indexes to link identities. This can make it difficult to represent parent/child relationships.
In other engines like Godot these relationships are expressed in the way the data structures lay them out in memory. But in an ECS everything is completely flat.
Bevy provides this ability by letting you tag entities with Parent
and Children
components provided from the bevy_hierarchy
crate.
Parents and 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 something has a Parent
it changes its Transform
to be positioned relative to its parent's. This can be useful for having something follow the player around.
These components are used to propagate properties through to different levels of our entity hierarchy.
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]);
}
Each of these entity commands will do two things:
- Update the
Parent
component of ourchild
entity to be theparent
- Add the
child
entity to theparent
entity'sChildren
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.
fn remove_children(mut commands: Commands) {
let parent = commands.spawn_empty().id();
let child = commands.spawn_empty().id();
// 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();
// Despawn the entity and its children
commands.entity(entity).despawn_recursive();
}
Removing 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.