Queries

Relevant official examples: ecs_guide.


Queries let you access components of entities.

Use the Query system parameter, where you can specify the data you want to access, and optionally additional filters.

Think of the types you put in your Query as a "specification" for selecting what entities you want to access. Queries will match only those entities in the ECS World that fit your specification. You are then able to access the relevant data from any such entities.

The first type parameter for a query is the data you want to access. Use & for shared/readonly access and &mut for exclusive/mutable access. Use Option if the component is not required (you want to find entities with or without that component. If you want multiple components, put them in a tuple.

Iterating

The most common operation is to simply iterate to access the component values of every entity that matches the query:

fn check_zero_health(
    // access entities that have `Health` and `Transform` components
    // get read-only access to `Health` and mutable access to `Transform`
    // optional component: get access to `Player` if it exists
    mut query: Query<(&Health, &mut Transform, Option<&Player>)>,
) {
    // get all matching entities
    for (health, mut transform, player) in query.iter_mut() {
        eprintln!("Entity at {} has {} HP.", transform.translation, health.hp);

        // center if hp is zero
        if health.hp <= 0.0 {
            transform.translation = Vec3::ZERO;
        }

        if let Some(player) = player {
            // the current entity is the player!
            // do something special!
        }
    }
}

If you want to know the entity IDs of the entities you are accessing, you can put the special Entity type in your query. This is useful if you need to later perform specific operations on those entities.

// add `Entity` to `Query` to get Entity IDs
fn query_entities(q: Query<(Entity, /* ... */)>) {
    for (e, /* ... */) in q.iter() {
        // `e` is the Entity ID of the entity we are accessing
    }
}

Accessing Specific Entities

To access the components from one specific entity only, you need to know the Entity ID:

if let Ok((health, mut transform)) = query.get_mut(entity) {
    // do something with the components
} else {
    // the entity does not have the components from the query
}

If you want to access the data from several entities all at once, you can use many/many_mut (panic on error) or get_many/get_many_mut (return Result). These methods ensure that all the requested entities exist and match the query, and will produce an error otherwise.

#[derive(Resource)]
struct UiHudIndicators {
    // say we have 3 special UI elements
    entities_ui: [Entity; 3],
    entities_text: [Entity; 3],
}

fn update_ui_hud_indicators(
    indicators: Res<UiHudIndicators>,
    query_text: Query<&Text>,
    query_ui: Query<(&Style, &BackgroundColor)>,
) {
    // we can get everything as an array
    if let Ok(my_texts) = query_text.get_many(indicators.entities_text) {
        // the entities exist and match the query
        // TODO: something with `my_texts[0]`, `my_texts[1]`, `my_texts[2]`
    } else {
        // query unsuccessful
    };

    // we can use "destructuring syntax"
    // if we want to unpack everything into separate variables
    let [(style0, color0), (style1, color1), (style2, color2)] =
        query_ui.many(indicators.entities_ui);

    // TODO: something with all these variables
}

Unique Entities

If you know that only one matching entity is supposed to exist (the query is expected to only ever match a single entity), you can use single/single_mut (panic on error) or get_single/get_single_mut (return Result). These methods ensure that there exists exactly one candidate entity that can match your query, and will produce an error otherwise.

You do not need to know the Entity ID.

fn query_player(mut q: Query<(&Player, &mut Transform)>) {
    let (player, mut transform) = q.single_mut();

    // do something with the player and its transform
}

Combinations

If you want to iterate over all possible combinations of N entities, Bevy provides a method for that too. Be careful: with a lot of entities, this can easily become very slow!

fn print_potential_friends(
    q_player_names: Query<&PlayerName>,
) {
    // this will iterate over every possible pair of two entities
    // (that have the PlayerName component)

    for [player1, player2] in q_player_names.iter_combinations() {
        println!("Maybe {} could be friends with {}?", player1.0, player2.0);
    }
}

fn apply_gravity_to_planets(
    mut query: Query<&mut Transform, With<Planet>>,
) {
    // this will iterate over every possible pair of two planets

    // For mutability, we need a different syntax
    let mut combinations = query.iter_combinations_mut();
    while let Some([planet1, planet2]) = combinations.fetch_next() {
        // TODO: calculate the gravity force between the planets
    }
}

Bundles

Queries work with individual components. If you created an entity using a bundle, you need to query for the specific components from that bundle that you care about.

A common beginner mistake is to query for the bundle type!

Query Filters

Add query filters to narrow down the entities you get from the query.

This is done using the second (optional) generic type parameter of the Query type.

Note the syntax of the query: first you specify the data you want to access (using a tuple to access multiple things), and then you add any additional filters (can also be a tuple, to add multiple).

Use With/Without to only get entities that have specific components.

fn debug_player_hp(
    // access the health (and optionally the PlayerName, if present), only for friendly players
    query: Query<(&Health, Option<&PlayerName>), (With<Player>, Without<Enemy>)>,
) {
    // get all matching entities
    for (health, name) in query.iter() {
        if let Some(name) = name {
            eprintln!("Player {} has {} HP.", name.0, health.hp);
        } else {
            eprintln!("Unknown player has {} HP.", health.hp);
        }
    }
}

This is useful if you don't actually care about the data stored inside these components, but you want to make sure that your query only looks for entities that have (or not have) them. If you want the data, then put the component in the first part of the query (as shown previously), instead of using a filter.

Multiple filters can be combined:

  • in a tuple to apply all of them (AND logic)
  • using the Or<(…)> wrapper to detect any of them (OR logic).
    • (note the tuple inside)

Query Transmutation

If you want one function with a Query parameter to call another function with a different (but compatible) Query parameter, you can create the needed Query from the one you have using something called QueryLens.

fn debug_positions(
    query: Query<&Transform>,
) {
    for transform in query.iter() {
        eprintln!("{:?}", transform.translation);
    }
}

fn move_player(
    mut query_player: Query<&mut Transform, With<Player>>,
) {
    // TODO: mutate the transform to move the player

    // say we want to call our debug_positions function

    // first, convert into a query for `&Transform`
    let mut lens = query_player.transmute_lens::<&Transform>();
    debug_positions(lens.query());
}

fn move_enemies(
    mut query_enemies: Query<&mut Transform, With<Enemy>>,
) {
    // TODO: mutate the transform to move our enemies

    let mut lens = query_enemies.transmute_lens::<&Transform>();
    debug_positions(lens.query());
}

Note: when we call debug_positions from each function, it will access different entities! Even though the Query<&Transform> parameter type does not have any additional filters, it was created by transmuting via QueryLens, and therefore it can only access the entities and components of the original Query that it was derived from. If we were to add debug_positions to Bevy as a regular system, it would access the transforms of all entities.

Also note: this has some performance overhead; the transmute operation is not free. Bevy normally caches some query metadata across multiple runs of a system. When you create the new query, it has to make a copy of it.