← Cheatsheets
Tags: bevy, rust, ecs, commands, queries, resources, systems, spawning, game-engine
Last updated: 2026-06-27
Bevy (Rust ECS) Cheatsheet
Quick Reference
| Concept | Key Pattern |
| App setup | App::new().add_plugins(DefaultPlugins) |
| Components | #[derive(Component)] struct X { ... } |
| Resources | #[derive(Resource)] struct X { ... } / insert_resource() |
| Systems | fn sys(query: Query<&T>, ...) / add_systems(Update, ...) |
| Queries | Query<&T> / With<T> / Without<T> / Changed<T> |
| Commands | commands.spawn(...) / commands.entity(e).despawn() |
| Events | EventWriter<T> / EventReader<T> / add_event::<T>() |
| Plugins | impl Plugin for X { fn build(&self, app) { ... } } |
App Boot & Plugins
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(MyGamePlugin)
.run();
}
struct MyGamePlugin;
impl Plugin for MyGamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup)
.add_systems(Update, (move_player, check_collisions));
}
}
Components
#[derive(Component)] struct Position { x: f32, y: f32 }
#[derive(Component)] struct Velocity { x: f32, y: f32 }
#[derive(Component)] struct Health { current: i32, max: i32 }
#[derive(Component)] struct Player; // Marker — no data
#[derive(Component)] struct Enemy { kind: EnemyKind }
#[derive(Component)] struct Name(String);
Spawning & Despawning
fn spawn_player(mut commands: Commands) {
commands.spawn((
SpriteBundle::default(),
Player,
Position { x: 0.0, y: 0.0 },
Velocity { x: 0.0, y: 0.0 },
Health { current: 100, max: 100 },
Name("Hero".into()),
));
}
fn spawn_enemy(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
SpriteBundle {
texture: asset_server.load("enemy.png"),
..default()
},
Enemy { kind: EnemyKind::Goblin },
Health { current: 30, max: 30 },
));
}
// Despawn
fn despawn_dead(mut commands: Commands, query: Query<(Entity, &Health)>) {
for (entity, health) in &query {
if health.current <= 0 {
commands.entity(entity).despawn();
}
}
}
// Fine-grained entity commands
commands.entity(player_entity)
.insert(Health { current: 50, max: 100 }) // Add component
.remove::<Velocity>() // Remove component
.with_children(|parent| { parent.spawn(Text2dBundle::default()); });
Queries
// Immutable
fn read_positions(query: Query<&Position>) {
for pos in &query { println!("({},{})", pos.x, pos.y); }
}
// Mutable
fn apply_velocity(mut query: Query<(&mut Position, &Velocity)>) {
for (mut pos, vel) in &mut query {
pos.x += vel.x; pos.y += vel.y;
}
}
// Optional
fn debug_name(query: Query<(&Position, Option<&Name>)>) {
for (pos, name) in &query {
let n = name.map(|n| n.0.as_str()).unwrap_or("Unnamed");
}
}
// Filters
Query<&Position, With<Player>> // Only players
Query<&Position, Without<Player>> // Not players
Query<&Health, Changed<Health>> // Only changed
Query<&Enemy, Added<Enemy>> // Newly added
// Single entity
let player = query.single();
let player = query.get_single(); // Returns Result
Resources
#[derive(Resource)]
struct GameState { score: u32, level: u32, is_paused: bool }
// Insert at startup
fn setup(mut commands: Commands) {
commands.insert_resource(GameState { score: 0, level: 1, is_paused: false });
}
// Read (immutable)
fn display_score(state: Res<GameState>, mut text: Query<&mut Text>) {
for mut text in &mut text {
text.sections[0].value = format!("Score: {}", state.score);
}
}
// Write (mutable)
fn add_score(mut state: ResMut<GameState>) { state.score += 10; }
// Optional
fn maybe_state(state: Option<Res<GameState>>) {
if let Some(state) = state { println!("Level: {}", state.level); }
}
Systems & Scheduling
app.add_systems(Startup, (spawn_camera, spawn_player))
.add_systems(Update, (
handle_input,
apply_velocity,
detect_collisions,
render_ui,
).chain()) // Run in order — one after another
.add_systems(PostUpdate, cleanup);
// Run conditions
use bevy::ecs::schedule::run_condition::run_if;
fn is_paused(state: Res<GameState>) -> bool { state.is_paused }
app.add_systems(Update,
(pause_menu, handle_pause_input).run_if(is_paused)
);
Events
#[derive(Event)]
struct DamageEvent { target: Entity, amount: i32 }
// Register (in Plugin build)
app.add_event::<DamageEvent>();
// Send
fn attack(mut events: EventWriter<DamageEvent>, query: Query<Entity, With<Enemy>>) {
for enemy in &query {
events.send(DamageEvent { target: enemy, amount: 10 });
}
}
// Read
fn receive(mut events: EventReader<DamageEvent>, mut query: Query<&mut Health>) {
for event in events.read() {
if let Ok(mut health) = query.get_mut(event.target) {
health.current -= event.amount;
}
}
}
// Events are double-buffered — drained each frame
Tips
- Order doesn't matter for system parameters — Bevy figures out access.
- Use marker components (
struct Player;) to tag entities without data overhead.
- Query filters (
With<T>, Without<T>, Changed<T>, Added<T>) narrow results efficiently.
- Resources replace globals/singletons — fully parallel-safe.
.chain() systems to force sequential execution when order matters.
- Prefer
EventWriter/EventReader over direct component mutation for cross-system communication.
#[derive(Bundle)] is deprecated in Bevy 0.15+ — use tuples of components directly with commands.spawn().
- Use
Res<Time> for delta_seconds() — frame-rate-independent movement.