Bevy Version: | 0.14 | (current) |
---|
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 be able to access. Queries will match
only those entities in the ECS World that fit your specification. You are
then able to use the query in various different ways, to access the relevant
data from such entities.
Query Data
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 access 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 the query, to access the component values of every entity that matches:
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 &mut query {
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!
}
}
}
Instead of iterating in a for
loop, you can also call .for_each(…)
with a
closure to run for each entity. This syntax often tends to be optimized better
by the compiler and may lead to better performance. However, it may be less
flexible (you cannot use control flow like break
/continue
/return
/?
)
and more cumbersome to write. Your choice.
fn enemy_pathfinding(
mut query_enemies: Query<(&Transform, &mut EnemyAiState)>,
) {
query_enemies.iter_mut().for_each(|(transform, mut enemy_state)| {
// TODO: do something with `transform` and `enemy_state`
})
}
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
You cannot query for a bundle!
Bundles are only a convenience to help you set up your entities with the correct set of components you'd like to have on them. They are only used during spawning / insert / remove.
Queries work with individual components. 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.
Query Joining
You can combine two queries to get a new query to access only those entities that would match both queries, yielding the combined set of components.
This works via QueryLens
, just like transmutation.
fn query_join(
mut query_common: Query<(&Transform, &Health)>,
mut query_player: Query<&PlayerName, With<Player>>,
mut query_enemy: Query<&EnemyAiState, With<Enemy>>,
) {
let mut player_with_common:
QueryLens<(&Transform, &Health, &PlayerName), With<Player>> =
query_player.join_filtered(&mut query_common);
for (transform, health, player_name) in &player_with_common.query() {
// TODO: do something with all these components
}
let mut enemy_with_common:
QueryLens<(&Transform, &Health, &EnemyAiState), With<Enemy>> =
query_enemy.join_filtered(&mut query_common);
for (transform, health, enemy_ai) in &enemy_with_common.query() {
// TODO: do something with all these components
}
}
Note: the resulting query cannot access any data that the original queries
were not able to access. If you try to add With
/Without
filters,
they will not have their usual effect.