Structure a Bevy game in Rust around the Entity Component System: the App and
plugins, components and resources, systems with queries, scheduling, and
frame-rate-independent updates. New examples target Bevy 0.19. If the project
already pins another release, keep that release and use its matching migration guide.
App, defining Component/Resource types, writing
systems that query entities, ordering/filtering systems, or fixing
borrow-conflict panics and frame-dependent movement.Cargo.toml depends on bevy and code calls App::new(),
add_systems, Query, or Commands.When not to use: this is the ECS core. Deep rendering, custom shaders/
pipelines, UI layout, and audio are separate concerns. For engine-agnostic AI or
procedural algorithms, pair with game-ai / procedural-gen.
Cargo.toml and Cargo.lock first. For a
new project use bevy = "0.19"; never silently migrate an existing project
across a Bevy minor release. Treat the matching docs and migration guides as truth.App. App::new().add_plugins(DefaultPlugins) gives windowing,
input, rendering, time, etc. Register systems into schedules: Startup (once)
and Update (every frame).#[derive(Component)] for
per-entity data; #[derive(Resource)] for one-of-a-kind data (score, settings,
the Time clock). In 0.19 Resource extends Component, so do not derive both.Query<...>
for entities, Res<T>/ResMut<T> for resources, Commands for deferred
spawn/despawn. Systems run in parallel when their accesses don't conflict.time.delta_secs() so speed is frame-rate independent..chain() or explicit constraints;
gate systems with run_if. Group related setup into Plugins. Build with
cargo run and read the panics — Bevy reports conflicting queries at startup.# Cargo.toml — pin the version; the API differs across minor releases.
[dependencies]
bevy = "0.19"
// main.rs
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins) // window, input, render, time, ...
.add_systems(Startup, setup) // runs once at startup
.add_systems(Update, move_players) // runs every frame
.run();
}
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Velocity(Vec2);
#[derive(Resource)]
struct Score(u32);
fn setup(mut commands: Commands) {
commands.insert_resource(Score(0));
// Camera2d is a component with required components (bundles removed in 0.16);
// spawning it pulls in Transform, Camera, etc. automatically.
commands.spawn(Camera2d);
// Spawn an entity as a tuple of components.
commands.spawn((
Player,
Velocity(Vec2::new(150.0, 0.0)),
Transform::from_xyz(0.0, 0.0, 0.0),
));
}
// Iterate every entity that has BOTH Velocity and Transform; mutate Transform.
fn move_players(time: Res<Time>, mut query: Query<(&Velocity, &mut Transform)>) {
for (velocity, mut transform) in &mut query {
// delta_secs() is f32 seconds (renamed from delta_seconds() in 0.16).
transform.translation += velocity.0.extend(0.0) * time.delta_secs();
}
}
// Only entities tagged Player (the Player component itself isn't read).
fn aim_player(mut q: Query<&mut Transform, With<Player>>) { /* ... */ }
// Disjoint two mutable Transform queries so they don't conflict at runtime.
fn separate(
mut players: Query<&mut Transform, With<Player>>,
mut enemies: Query<&mut Transform, Without<Player>>,
) { /* ... */ }
// React only when Health changed since last run (change detection).
fn on_health_change(q: Query<&Health, Changed<Health>>) {
for health in &q { /* update the HUD, etc. */ }
}
fn add_points(mut score: ResMut<Score>) {
score.0 += 10; // ResMut = write access
}
fn show_score(score: Res<Score>) {
info!("score: {}", score.0); // Res = read access
}
fn main() {
App::new()
.add_plugins((DefaultPlugins, GameplayPlugin))
// .chain() forces order: damage resolves before death is checked.
.add_systems(Update, (apply_damage, check_deaths).chain())
// run_if gates a system on a condition each frame.
.add_systems(Update, spawn_wave.run_if(wave_timer_finished))
.run();
}
struct GameplayPlugin;
impl Plugin for GameplayPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(Score(0))
.add_systems(Startup, setup)
.add_systems(Update, (move_players, add_points));
}
}
delta_seconds() not found → it was renamed to time.delta_secs() (and
elapsed_secs()) in 0.16. Using the old name fails to compile.time.delta_secs(). Never assume a fixed frame time.Querys in one
system both write the same component, or one reads while another writes overlapping
entities. Make them disjoint with With/Without, or use ParamSet.Camera2dBundle/SpriteBundle not found → bundles were deprecated in 0.15 and
removed in 0.16.
Spawn the components directly (Camera2d, Sprite, Transform); required
components fill in the rest.Component is not implemented" → you forgot #[derive(Component)]
(or #[derive(Resource)] for a resource).Commands are
deferred and applied at the next sync point. Read the entity in a subsequent system,
not the one that spawned it.B must follow A, add (A, B).chain() or an explicit ordering constraint.Resource and Component in 0.19 → Resource now extends
Component; derive Resource alone to avoid conflicting implementations.SystemSet ordering, States/OnEnter/OnExit, change
detection, Commands lifecycle and sync points, ParamSet for conflicting
queries, and a version note on the events/observers API, read
references/queries-and-scheduling.md.game-ai — FSMs/behavior trees/steering as portable concepts to implement in ECS.procedural-gen — noise/RNG/generation algorithms to drive from systems.pygame-core / love2d-core — lighter-weight engines for smaller projects.