Tainted \\ Coders

Bevy Code Organization

Last updated:

A collection of interesting parts of Bevy for organizing our code.

Hierarchy

Entities can have associations to each other parent -> child Examples: rotating parent and all children

commands.spawn(SomeBundle).with_children(|child| { parent.spawn(SomeBundle) })

Most commonly, these hierarchies are used for inheriting Transform values from the Parent to its Children.

Here is how we would iterate over the hierarchy of an entity using a query:

fn system(query: Query<Entity, With<Marker>>, children_query: Query<&Children>) {
    let entity = query.single();
    for descendant in children_query.iter_descendants(entity) {
         // Do something!
    }
}

fn system(query: Query<Entity, With<Marker>>, parent_query: Query<&Parent>) {
    let entity = query.single();
    for ancestor in parent_query.iter_ancestors(entity) {
     // Do something!
    }
}


// Or despawn a parent AND its children
commands.entity(parent_entity).despawn_recursive();

  • HierarchyEvent::ChildAdded
  • HierarchyEvent::ChildRemoved
  • HierarchyEvent::ChildMoved

Shared system behaviour

Using systems with generic types can share behaviour Examples: cleaning up system state on scene transitions

fn main() {
    App::new()
    ...
    .add_systems(OnExit(AppState::MainMenu), cleanup_system::<MenuClose>)
    .add_systems(OnExit(AppState::InGame), cleanup_system::<LevelUnload>)
}

// Type arguments on functions come after the function name, but before ordinary arguments.
// Here, the `Component` trait is a trait bound on T, our generic type
fn cleanup_system<T: Component>(mut commands: Commands, query: Query<Entity, With<T>>) {
    for e in &query {
        commands.entity(e).despawn_recursive();
    }
}

Custom queries

Using #[derive(QueryData)] you can build custom queries

  • They help to avoid destructuring or using q.0, q.1, ... access pattern.
  • Adding, removing components or changing items order with structs greatly reduces maintenance burden, as you don’t need to update statements that destructure tuples, care about order of elements, etc. Instead, you can just add or remove places where a certain element is used.
  • Named structs enable the composition pattern, that makes query types easier to re-use.
  • You can bypass the limit of 15 components that exists for query tuples.
#[derive(QueryData)]
#[query_data(derive(Debug))]
struct PlayerQuery {
    entity: Entity,
    health: &'static Health,
    ammo: &'static Ammo,
    player: &'static Player,
}

fn print_player_status(query: Query<PlayerQuery>) {
    for player in query.iter() {
        println!(
            "Player {:?} has {:?} health and {:?} ammo",
            player.entity,
            player.health,
            player.ammo
        );
    }
}

Custom commands

We can implement our own custom commands to reduce the boilerplate of common actions.

pub struct SpawnBoid {
    pub position: Vec2,
}

impl SpawnBoid {
    pub fn random() -> Self {
        let mut rng = rand::thread_rng();
        let x = rng.gen_range(-200.0..200.0);
        let y = rng.gen_range(-200.0..200.0);
        Self {
            position: Vec2::new(x, y),
        }
    }
}

impl Command for SpawnBoid {
    fn apply(self, world: &mut World) {
        let assets = world.get_resource::<BoidAssets>();

        if let Some(assets) = assets {
            world.spawn((
                BoidBundle::new(self.position.x, self.position.y),
                MaterialMesh2dBundle {
                    mesh: assets.mesh.clone().into(),
                    material: assets.material.clone(),
                    ..default()
                },
            ));
        }
    }
}

fn setup(mut commands: Commands) {
    for _ in 0..100 {
        commands.add(SpawnBoid::random());
    }
}