About bevy-ecs-queries
A skill for working with Bevy 0.19 ECS queries, covering component access in systems, entity filters, and parallel iteration. It focuses on updated query bounds, iteration patterns, and common error handling.
Key features
- 01Read and mutate entity components
- 02Filter by components and change state
- 03Parallelize iteration with par_iter_mut
- 04Borrow query subsets with lenses
- 05Explain Bevy 0.19 query bounds
Use cases
- 01Implement query logic in Bevy systems
- 02Filter or process large entity sets in parallel
- 03Resolve query compile errors after upgrading
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ chrisgliddon/ bevy-skills --skill bevy-ecs-queriesAfter installing, type/bevy-ecs-queriesin Claude Code to confirm it loaded.
Details
From the project READMEBevy 0.19 — ECS Queries
When to use this skill
- Reading or writing components from a system.
- Filtering by presence (
With/Without), alternation (Or), or change detection (Changed/Added). - Parallelising over a large entity set with
par_iter_mut. - Borrowing a subset of a query via a lens (
transmute_lens). - Compiler error mentioning
IterQueryDataorSingleEntityQueryDataafter upgrading to 0.19.
Canonical pattern
use bevy::prelude::*;
#[derive(Component, Default)]
struct Health(f32);
#[derive(Component, Default)]
struct Velocity(Vec3);
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Enemy;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, (
move_things,
on_health_changed,
damage_visible_enemies,
integrate_in_parallel,
))
.run();
}
// Sequential iteration. `&` for read, `&mut` for write.
fn move_things(time: Res<Time>, mut q: Query<(&Velocity, &mut Transform)>) {
let dt = time.delta_secs();
for (vel, mut tf) in &mut q {
tf.translation += vel.0 * dt;
}
}
// Change detection. `Changed<T>` triggers on insert OR mutation.
// `Added<T>` triggers only on insert.
fn on_health_changed(q: Query<(Entity, &Health), Changed<Health>>) {
for (entity, hp) in &q {
info!("entity {:?} now has {} hp", entity, hp.0);
}
}
// Combined filters. `With`/`Without` constrain entities;
// `Or<(...)>` alternates over filters (not components).
fn damage_visible_enemies(
mut q: Query<&mut Health, (With<Enemy>, Without<Player>, Or<(Added<Enemy>, Changed<Transform>)>)>,
) {
for mut hp in &mut q {
hp.0 -= 1.0;
}
}
// Parallel iteration. Use when N is large (>10k) and per-entity work is non-trivial.
// Cannot use `Commands` or external mutable state — task pool runs items in parallel.
fn integrate_in_parallel(mut q: Query<(&Velocity, &mut Transform)>) {
q.par_iter_mut().for_each(|(vel, mut tf)| {
tf.translation += vel.0 * 0.016;
});
}
// Query lens: temporarily view a query as a narrower one. Useful for
// passing a stricter query into a helper without re-binding the system's
// SystemParam list.
#[allow(dead_code)]
fn use_lens(mut q: Query<(&mut Transform, &Velocity)>) {
// Read-only narrowed view of just the Transform column.
let mut lens = q.transmute_lens::<&Transform>();
let _read_only: Query<&Transform> = lens.query();
}
Bevy 0.19 gotchas
- Nested query access added bounds in 0.19. Generic mutable iteration usually needs
D: IterQueryData; single-entity access, query transmutation/join, traversal, and sorting may needD: SingleEntityQueryData. Read-only iteration retains the broader bounds. - Non-iterable query data uses lending iteration. Keep
D: QueryData, calllet mut iter = query.iter_mut(), then repeatedly calliter.fetch_next()when a nested query item may access several entities. EntityMut::get_components_mut::<(&mut A, &mut B)>()is the safe way to grab two&muts out of one entity and now carries aSingleEntityQueryDatabound. It returnsResult; do not reach for unchecked aliasing.Query::get/get_mutreturnsResult, notOption. The error type carries the entity, so don't swallow it with.ok()if you actually need to know why a lookup missed.Or<(With<A>, With<B>)>—Oralternates over filters, not raw component types.Or<(A, B)>does not compile.Changed/Addedare tick-based. They compare against the system's last run, so ordinary run conditions do not lose an intervening change. They report mutation, not semantic inequality; use observers/messages when every occurrence or old/new value matters.- Don't pair
par_iter_mutwithCommands. Spawn/despawn from a sequential system that consumes aResourcequeue written by the parallel one.
See also
bevy-ecs-components— declaring the components queried here.bevy-ecs-systems— using queries insideSystemParamand run conditions.bevy-migration-0-17-to-0-18—EntityMut::get_components_mutand tick-type move.bevy-migration-0-18-to-0-19— nested-query trait bounds and resources in queries.
Comments
Markdown supported