Tags: bevy, rust, ecs, commands, queries, resources, systems, spawning, game-engine Last updated: 2026-06-27

Bevy (Rust ECS) Cheatsheet

Quick Reference

ConceptKey Pattern
App setupApp::new().add_plugins(DefaultPlugins)
Components#[derive(Component)] struct X { ... }
Resources#[derive(Resource)] struct X { ... } / insert_resource()
Systemsfn sys(query: Query<&T>, ...) / add_systems(Update, ...)
QueriesQuery<&T> / With<T> / Without<T> / Changed<T>
Commandscommands.spawn(...) / commands.entity(e).despawn()
EventsEventWriter<T> / EventReader<T> / add_event::<T>()
Pluginsimpl 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