Tainted \\ Coders

Bevy Hierarchy

Last updated:

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:

  1. Parent
  2. 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:

  1. The Parent component gets removed from the child
  2. The child is removed from the Children of the parent.

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 a parent and child each containing an entity
  • ChildRemoved which has parent and child each containing an entity
  • ChildMoved which has a child, previous_parent and new_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.

Read more