bevy-ecs
gamedev-skills/awesome-gamedev-agent-skills
Structure Bevy games in Rust using Entity Component System: App, components, resources, systems, and scheduling.
What is bevy-ecs?
Bevy ECS is the core architecture pattern for building games in Bevy (Rust). Use it when wiring a Bevy App, defining components and resources, writing systems with queries, ordering systems, or fixing frame-rate-dependent motion. Essential for any Bevy project.
- Build and configure a Bevy App with plugins and system schedules (Startup and Update)
- Define Component and Resource types to model per-entity and global data
- Write systems as plain functions with Query, Res, ResMut, and Commands parameters
- Filter and order systems using With/Without, Changed, .chain(), and run_if conditions
- Implement frame-rate-independent motion using time.delta_secs()
- Diagnose and resolve conflicting query accesses and borrow-conflict panics
How to install bevy-ecs
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill bevy-ecs- Rust toolchain and cargo installed
- Bevy dependency in Cargo.toml (pinned to a specific minor version like 0.19)
- Basic understanding of Rust traits and derive macros
How to use bevy-ecs
- 1.Read Cargo.toml and Cargo.lock to detect the pinned Bevy version; do not silently migrate across minor releases
- 2.Create or update main.rs with App::new().add_plugins(DefaultPlugins) and register systems into Startup and Update schedules
- 3.Define Component types with #[derive(Component)] for per-entity data and Resource types with #[derive(Resource)] for global data
- 4.Write systems as plain functions with parameters (Query, Res, ResMut, Commands) that declare data access
- 5.Multiply per-frame motion by time.delta_secs() to ensure frame-rate independence
- 6.Use With/Without filters and .chain() to order systems only where necessary; read compiler panics to diagnose conflicting accesses
Use cases
- Setting up a new Bevy 0.19 project with App::new() and DefaultPlugins
- Adding player movement and velocity using Query and Time resource
- Implementing game state (score, settings) as Resources and updating them in systems
- Ordering damage resolution before death checks using .chain()
- Migrating existing Bevy code across minor versions while respecting Cargo.lock
- Rust game developers building 2D or 3D games with Bevy
- Game programmers learning or debugging ECS architecture
- Teams maintaining Bevy projects across version upgrades
bevy-ecs FAQ
Pin the version in Cargo.toml (e.g., bevy = "0.19"). Never silently migrate an existing project across a minor release; consult the matching migration guide and docs for your version.
You must multiply per-frame changes by time.delta_secs(). Never assume a fixed frame time; this ensures motion is frame-rate independent.
Two queries in one system are both writing the same component or one reads while another writes overlapping entities. Make them disjoint using With/Without filters, or use ParamSet to serialize access.
Commands are deferred and applied at the next sync point. Read the entity in a subsequent system, not the one that spawned it.
No. Resource now extends Component in 0.19; derive Resource alone to avoid conflicting implementations.
Full instructions (SKILL.md)
Source of truth, from gamedev-skills/awesome-gamedev-agent-skills.
name: bevy-ecs description: > Structure a Bevy app around its Entity Component System: build the App with plugins, define Component/Resource types, write systems with Query/Res/Commands, filter and order systems, and use the Time resource for frame-rate-independent motion. Use when building or debugging a Bevy game in Rust — when the user mentions Bevy, ECS, App::new, add_systems, Query, Commands, components/systems, or a Cargo.toml depending on bevy.
Bevy ECS
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.
When to use
- Use when wiring a Bevy
App, definingComponent/Resourcetypes, writing systems that query entities, ordering/filtering systems, or fixing borrow-conflict panics and frame-dependent movement. - Use when
Cargo.tomldepends onbevyand code callsApp::new(),add_systems,Query, orCommands.
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.
Core workflow
- Detect and pin the version. Read
Cargo.tomlandCargo.lockfirst. For a new project usebevy = "0.19"; never silently migrate an existing project across a Bevy minor release. Treat the matching docs and migration guides as truth. - Build the
App.App::new().add_plugins(DefaultPlugins)gives windowing, input, rendering, time, etc. Register systems into schedules:Startup(once) andUpdate(every frame). - Model data as components, globals as resources.
#[derive(Component)]for per-entity data;#[derive(Resource)]for one-of-a-kind data (score, settings, theTimeclock). In 0.19ResourceextendsComponent, so do not derive both. - Write systems as plain functions. Parameters declare data access:
Query<...>for entities,Res<T>/ResMut<T>for resources,Commandsfor deferred spawn/despawn. Systems run in parallel when their accesses don't conflict. - Drive motion by
time.delta_secs()so speed is frame-rate independent. - Order only what must be ordered with
.chain()or explicit constraints; gate systems withrun_if. Group related setup intoPlugins. Build withcargo runand read the panics — Bevy reports conflicting queries at startup.
Patterns
1. Cargo.toml + minimal App
# 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();
}
2. Components, resources, and spawning
#[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),
));
}
3. A system with a query + the Time resource
// 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();
}
}
4. Query filters (With / Without / Changed)
// 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. */ }
}
5. Resources: read and write
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
}
6. Ordering, run conditions, and plugins
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));
}
}
Pitfalls
delta_seconds()not found → it was renamed totime.delta_secs()(andelapsed_secs()) in 0.16. Using the old name fails to compile.- Movement speed scales with frame rate → multiply per-frame changes by
time.delta_secs(). Never assume a fixed frame time. - Panic: "conflicting accesses" / "&mut T and &mut T" → two
Querys in one system both write the same component, or one reads while another writes overlapping entities. Make them disjoint withWith/Without, or useParamSet. Camera2dBundle/SpriteBundlenot 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.- "trait
Componentis not implemented" → you forgot#[derive(Component)](or#[derive(Resource)]for a resource). - Spawned entity not visible to a later query in the same frame →
Commandsare deferred and applied at the next sync point. Read the entity in a subsequent system, not the one that spawned it. - System order assumed but not enforced → systems run in parallel by default.
If
Bmust followA, add(A, B).chain()or an explicit ordering constraint. - Deriving both
ResourceandComponentin 0.19 →Resourcenow extendsComponent; deriveResourcealone to avoid conflicting implementations. - Copy-pasting older Bevy snippets → APIs shift between minor versions. The buffered event system became the message system in recent releases. Verify against the docs and migration guide for your pinned version; don't mix versions.
References
- For schedules and
SystemSetordering,States/OnEnter/OnExit, change detection,Commandslifecycle and sync points,ParamSetfor conflicting queries, and a version note on the events/observers API, readreferences/queries-and-scheduling.md.
Related skills
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.
Related skills
More from gamedev-skills/awesome-gamedev-agent-skills and the wider catalog.

camera-systems
Build smooth, bounded game cameras with deadzone, look-ahead, orbit, and collision—engine-neutral techniques for 2D and 3D.

card-game
Build card games with zones, draw mechanics, turn structure, and effect resolution.

create-game-assets
Plan, generate, source, normalize, and validate cohesive visual game assets for any engine or art style.

dialogue-systems
Build branching dialogue trees with Ink, Yarn Spinner, or a custom JSON runner.

fps-shooter
Build first-person shooter mechanics: controller, hitscan/projectile combat, weapons, health, and enemy AI.

game-ai
Design NPC and enemy decision-making with FSMs, behavior trees, steering, and A* pathfinding.