Bevy Systems Queries and Resources Explained Simply
Bevy is an entity component system: entities are ids, components are data, systems are plain functions. See Query, Res, ResMut, Commands and schedules with a small runnable example and the ordering rules.

Bevy asks you to stop thinking about objects and start thinking about tables. Data lives in components, behavior lives in functions that query for that data, and the engine does the matching and scheduling. Once the model clicks, the API is small. This post covers the four ideas that make up most Bevy code. It compiles on Rust 1.90 with edition 2024 and Bevy 0.16; Bevy releases often change names, so check the migration guide for your version.
The pieces
| Concept | In code | Meaning |
|---|---|---|
| Entity | Entity | An id with a set of components |
| Component | #[derive(Component)] struct Health(i32); | A piece of data attached to an entity |
| System | fn heal(mut q: Query<&mut Health>) {} | A function run by the scheduler |
| Resource | #[derive(Resource)] struct Score(u32); | Global singleton data |
| Schedule | Startup, Update, FixedUpdate | When systems run |
A complete example
use bevy::prelude::*;
#[derive(Component)]
struct Position(Vec2);
#[derive(Component)]
struct Velocity(Vec2);
#[derive(Component)]
struct Enemy;
#[derive(Resource, Default)]
struct Score(u32);
fn setup(mut commands: Commands) {
commands.spawn((Position(Vec2::ZERO), Velocity(Vec2::new(1.0, 0.5))));
commands.spawn((Position(Vec2::new(5.0, 5.0)), Velocity(Vec2::new(-1.0, 0.0)), Enemy));
}
fn movement(time: Res<Time>, mut query: Query<(&mut Position, &Velocity)>) {
for (mut pos, vel) in &mut query {
pos.0 += vel.0 * time.delta_secs();
}
}
fn count_enemies(enemies: Query<&Position, With<Enemy>>, mut score: ResMut<Score>) {
score.0 = enemies.iter().count() as u32;
}
fn report(score: Res<Score>) {
if score.is_changed() {
println!("enemies: {}", score.0);
}
}
fn main() {
App::new()
.add_plugins(MinimalPlugins)
.init_resource::<Score>()
.add_systems(Startup, setup)
.add_systems(Update, (movement, count_enemies, report).chain())
.run();
}The program runs headless with MinimalPlugins (no window or renderer), spawns two entities at startup and runs three systems each frame, in order because of .chain(). It loops forever; stop it with Ctrl-C. Nothing in the code names the entities: movement receives every entity that has Position and Velocity, wherever it came from.
Queries
Query<&Position>: read-only access to a component.Query<&mut Position>: write access. Iterate withfor mut p in &mut q.Query<(Entity, &Health)>: include the entity id (to despawn or reference it later).With<T>andWithout<T>: filters that test presence without reading data.Option<&T>: match entities with or without an optional component.Changed<T>andAdded<T>: only entities whose component changed or was just added.
The borrow checker's rules are enforced at the system level. A system with Query<&mut Position> and another Query<&Position> in the same function conflicts, and Bevy panics on startup with a clear message. Use Without filters to prove two queries are disjoint, or ParamSet to access them one at a time.
Resources
Res<T> gives read access and ResMut<T> write access to a global value. Common built-ins: Time, ButtonInput<KeyCode>, Assets<T>. Use Option<Res<T>> when the resource may not exist yet. A resource suits things that exist once; if you find yourself wanting several, it is a component on an entity.
Commands and deferred changes
fn spawn_bullet(mut commands: Commands, keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Space) {
commands.spawn((Position(Vec2::ZERO), Velocity(Vec2::X * 10.0)));
}
}
fn cleanup(mut commands: Commands, q: Query<(Entity, &Position)>) {
for (e, pos) in &q {
if pos.0.x > 100.0 {
commands.entity(e).despawn();
}
}
}Changes to the world's structure (spawn, despawn, insert or remove a component) go through Commands and take effect at a later sync point, which lets systems run in parallel without invalidating each other's queries. The consequence: do not expect the new entity in the same system.
Ordering and parallelism
Bevy builds a dependency graph from each system's parameters. Two systems that only read the same data run in parallel; one that writes a component conflicts with any other that touches it and is serialized. Add explicit ordering when one system's output feeds another (.after(movement)), put related systems in sets, and use FixedUpdate for physics-style logic that should run at a fixed rate regardless of frame time. For where this design pays off in performance terms, see data oriented design.


