How should I build a hierarchy in bevy_ecs?

I decided to not use Bevy Engine and just bevy_ecs, so I had to do hierarchy another way. Is this good enough or should I replace spawn_child by spawn_children and put a ChildrenComponent into the parent to optimize the .children() method?

//! Hierarchy traits for Entities.

use crate::ecs::{

pub trait SpawnChild {
    fn spawn_child(&self, bundle: impl Bundle) -> EntityMut<'static>;

impl SpawnChild for Entity {
    fn spawn_child(&self, bundle: impl Bundle) -> EntityMut<'static> {
        let mut child = crate::application::world_mut().spawn(bundle);

pub trait Parent {
    fn parent(&self) -> Option<Entity>;

impl Parent for Entity {
    fn parent(&self) -> Option<Entity> {
        crate::application::world().get::<ParentComponent>(*self).map(|c| c.0)

pub trait Children {
    fn children(&self) -> Vec<Entity>;

    /// Despawns all children entities from an entity.
    fn despawn_children(&self) {
        for child in self.children() {

impl Children for Entity {
    fn children(&self) -> Vec<Entity> {
        let mut r: Vec<Entity> = vec![];
        for ent in crate::application::world().iter_entities() {
            let Some(c) = ent.get::<ParentComponent>() else {
            if c.0 == *self {

struct ParentComponent(Entity);

The truth is Bevy's ECS doesn't really have support for hierarchies. Parent/Child in bevy are mostly for its own purposes, and aren't even recommended if you need your own hierarchies.

Making immediate changes based on world_mut() is probably fine.

I've found that managing hierarchies using Commands is tricky and unreliable in ECS, because spawning and despawning commands are typically queued and delayed by a frame. This makes it almost impossible to ensure consistency of the hierarchy, because when you inspect the current state of the hierarchy, you may have out-of-date information, because there may already be commands queued adding/removing/changing objects you've just inspected (e.g. "delete all children" when new children are to be spawned next frame, or "add grandchild for every child" if the children are being deleted next frame).

1 Like

Small correction: they'll get delayed until the next sync point, which can happen before the end of the frame and can also be manually inserted. Though they are still unintuitive if you're not familiar with them.