Replace scaffold placeholders with full skill content: - bevy-0.19: ECS cheat sheet, critical traps table, architecture guide, and 7 reference files - bevy-upgrade: version migration workflow, transition references for 0.15→0.19, and 3 per-bump reference files
11 KiB
name, description
| name | description |
|---|---|
| bevy-0.19 | Write and navigate Bevy 0.19 (Rust game engine) code — ECS, plugins, systems, queries, events/messages, hierarchy, input, camera, rendering, assets. Use whenever editing Rust code that imports bevy, or the task involves Bevy components, systems, entity spawning, or game architecture. Pre-training knowledge of Bevy APIs is stale; consult this before writing Bevy code. For version migrations, defer to the bevy-upgrade skill. |
Bevy 0.19
Bevy's API changes fundamentally between minor versions. Code that looks correct from memory frequently targets 0.14–0.17 APIs and either fails to compile or — worse — compiles and silently misbehaves. This skill is ground truth for 0.19.
Step 0: Confirm the version
Check Cargo.toml for the bevy version before writing code.
- 0.19 — this skill applies directly.
- 0.18 — most of this skill applies; 0.19-only notes are marked. Do not use 0.19-only APIs.
- ≤0.17, or the task is a version bump/migration — use the bevy-upgrade skill. It owns rename tables and semantic shifts per version transition. Come back here for writing new idiomatic code once the target version is settled.
Critical traps
The highest-frequency mistakes. When in doubt, trust this table over memory:
| Trap | Wrong (stale) | Correct (0.18/0.19) |
|---|---|---|
| Parent component | Parent |
ChildOf(entity) |
| Get parent | *child_of (deref) |
child_of.parent() |
| Child iteration | children.iter().copied() |
for child in &children |
| Recursive despawn | despawn_recursive() |
despawn() (recursive by default) |
| Buffered events | EventWriter/EventReader |
MessageWriter/MessageReader |
| Run condition | on_event::<T>() |
on_message::<T>() |
| Bundles | #[derive(Bundle)] |
spawn tuples + #[require(...)] |
| System errors | infallible fn(...) everywhere |
fn(...) -> Result where fallible |
| Forward direction | +Z | -Z (Transform::forward()) |
| GLTF model forward | -Z like Bevy | +Z — negate or use transform.back() |
GlobalTransform in Update |
assumed current | stale — propagation runs in PostUpdate |
| Material shader bind group | @group(2) |
@group(3) (group 2 is mesh bindings) |
Same-state set() |
no-op | fires OnExit+OnEnter; use set_if_neq() |
AmbientLight |
one global type | GlobalAmbientLight resource vs AmbientLight camera component |
ShaderRef import |
bevy::render::render_resource |
bevy::shader::ShaderRef |
WinitPlugin |
WinitPlugin::<WakeUp> |
WinitPlugin (not generic) |
0.19-specific:
| Trap | Detail |
|---|---|
| Resources are components | Resources live on entities; Query<Entity> matches them — filter broad queries with Without<IsResource>. Cannot derive both Component and Resource on one type. |
| Scene renamed | Scene/SceneRoot → WorldAsset/WorldAssetRoot |
Ref<T>.clone() |
Returns Ref<T>, not the inner value — use ref.deref().clone() |
Assets::get_mut |
Returns AssetMut<A> (mutation-tracked), not &mut A |
| GLTF materials | Return GltfMaterial; use #Material0/std label for StandardMaterial |
ECS core
Components
#[derive(Component)]
struct Health(f32);
#[derive(Component)]
#[require(Transform, Visibility)] // auto-inserted when Player is added
struct Player;
#[require(B, C)]replaces bundles: spawning the marker pulls in its dependencies. Constructors work:#[require(Speed(5.0))],#[require(Timer = default_timer())].#[component(immutable)](0.18+) forbids&mut Taccess.#[component(storage = "SparseSet")]for frequently added/removed components.- Lifecycle hooks:
#[component(on_add = f)], alsoon_insert,on_replace,on_remove,on_despawn— synchronous, inline with the ECS operation.
Systems
Plain functions of SystemParams. Make fallible systems return Result (0.18+) — errors log and the system retries next tick; ? works with anything convertible to BevyError:
fn apply_damage(mut q: Query<&mut Health>, mut reader: MessageReader<Damage>) -> Result {
for msg in reader.read() {
q.get_mut(msg.target)?.0 -= msg.amount;
}
Ok(())
}
Useful params beyond the obvious: Single<D, F> (exactly one match or panic), Populated<D, F> (skip system if empty), Option<Res<T>> (resource may not exist — required for render resources in headless contexts), Local<T> (per-system persistent state), RemovedComponents<T>.
Queries
- Data:
&T,&mut T,Entity,Has<T>, tuples. - Filters:
With<T>,Without<T>,Added<T>,Changed<T>,Spawned,Or<(A, B)>. .single()panics unless exactly one match; preferSingleparam or handle theResult.- Derive
QueryData/QueryFilterfor reusable complex queries.
Commands
commands.spawn((CompA, CompB)) with tuples. commands.entity(e).insert(..) / .insert_if_new(..) / .remove::<T>() / .despawn(). Commands are deferred — they apply at the next sync point, not immediately.
Ordering
(a, b).chain(), .before(x) / .after(x), #[derive(SystemSet)] + configure_sets. Unordered systems with overlapping mutable access run in nondeterministic order — order anything where it matters.
Hierarchy
ChildOf(parent) on the child is the source of truth; Children on the parent is auto-synced. Despawning a parent despawns descendants.
commands.spawn((Enemy, ChildOf(parent)));
commands.entity(parent).with_children(|s| { s.spawn(Turret); });
commands.entity(parent).add_child(existing);
Detaching without despawning: detach_child / detach_children / detach_all_children (0.18 names). Despawn only the children: despawn_related::<Children>().
Details, custom relationships, transform propagation: references/hierarchy.md.
Events vs Messages — pick correctly
0.18 split the old event system in two. Choosing wrong is the most common architectural error:
| Mechanism | Model | Use for |
|---|---|---|
#[derive(Message)] + MessageWriter/MessageReader |
Pull, double-buffered, batch | Decoupled many-to-many data flow; multiple systems consuming the same stream |
#[derive(Event)] + commands.trigger() + observers (On<E>) |
Push, immediate | React now: gameplay reactions, structural changes |
#[derive(EntityEvent)] + #[event_target] |
Push, per-entity, can propagate up ChildOf |
Entity-targeted reactions (hit, clicked) |
| Component hooks | Synchronous, inline | Response must be atomic with the ECS operation |
Changed<T>/Added<T> filters |
Per-tick polling | Cheap frame-by-frame reaction in a normal system |
Full API, lifecycle events (Add/Insert/Replace/Remove/Despawn), observer registration: references/events-observers.md.
Architecture
How to structure a Bevy project — and how to navigate an unfamiliar one:
- One plugin per feature/domain (
CombatPlugin,InventoryPlugin,UiPlugin), each owning its components, systems, messages, and state.main.rsshould be little more thanApp::new().add_plugins(...). - Navigate by the plugin tree: start at
App::new(), followadd_pluginsrecursively; grepadd_systemsand#[derive(SystemSet)]to map execution order; grep#[derive(Component)]/#[derive(Resource)]per module to map data ownership. - Cross-plugin communication goes through Messages or shared resources — not direct system coupling. A plugin's components are its internals; its messages are its API.
- States (
init_state,OnEnter/OnExit,in_staterun conditions) for game flow;DespawnOnExit(state)for scoped entities. Remember same-stateset()re-fires transitions — one-time setup inOnEnterneedsset_if_neqdiscipline from callers. - No formal plugin dependencies exist. Guard with
is_plugin_added::<T>(), or do cross-plugin setup inPlugin::finish()(runs after all plugins are ready). - Headless-safe systems: anything that may run in tests or dedicated servers must take render resources as
Option<Res<...>>. - Spawn from standard schedules. Entities spawned from exclusive/non-standard schedules (UI passes, custom schedules) can be invisible to plugins that hook the normal cycle — set a flag there, spawn in
Update.
Plugin trait lifecycle, plugin groups, states, run conditions: references/plugins-and-app.md.
Coordinates and transforms
- Right-handed. +Y up, -Z forward, +X right.
Transform::forward()is -Z. - GLTF models face +Z visually — opposite Bevy. Orient with
look_to(-dir, Vec3::Y)or treattransform.back()as the model's facing. - Transform propagation runs in
PostUpdate: readingGlobalTransforminUpdategives last frame's value. Move the reader toPostUpdateafterTransformSystem::TransformPropagate, or accept the one-frame lag deliberately.
Silent-failure checklist
Bevy often succeeds silently when misconfigured. If something "doesn't show up" with no errors:
- Mesh has no normals → invisible. Use
.with_computed_flat_normals()on programmatic meshes. - GLTF spawned by raw handle → nothing. Spawn scene labels:
"model.glb#Scene0". - Parent lacks
Visibility/InheritedVisibility→ children invisible. - Object beyond default far plane (1000 units) → culled.
- Asset cache poisoning:
load_with_settingswith stripped meshes caches per path; a later plainload()of the same path returns the stripped version. Never mix stripped and rendered loads of one asset in one process. - Assets aren't ready after
load()— gate onAssetEvent::LoadedWithDependenciesoris_loaded_with_dependencies(). - Raw
ButtonInput<MouseButton>fires through UI — world click handlers need UI-consumption checks.
More production gotchas (physics, multiplayer/shared-world, custom relationships): references/production-gotchas.md.
Code style
Write pragmatic Rust, not defensive Rust:
- Prefer
let Ok(x) = q.single() else { return; }/let Some(x) = ... else { return; }over nestedif letpyramids. - Prefer fallible systems with
?overunwrap()chains — a logged error beats a panic in a frame loop. - Small, single-purpose components; marker components (
struct Player;) are idiomatic and free. - Don't hand-roll what the ECS gives you: relationship sync, recursive despawn, required components, run conditions.
References
Load on demand — each is self-contained:
| File | Read when working on |
|---|---|
references/plugins-and-app.md |
Plugin trait/lifecycle, plugin groups, App builder, states, schedules, run conditions |
references/ecs.md |
Component/resource/system/query/command details beyond the summary above |
references/hierarchy.md |
ChildOf, Children, spawning/detaching, relationships, transform propagation |
references/events-observers.md |
Message vs Event APIs, observers, lifecycle events, hooks |
references/input.md |
Keyboard, mouse, gamepad, touch |
references/camera-rendering.md |
Cameras, coordinates, meshes, materials, shaders, assets, lighting |
references/production-gotchas.md |
Silent failures, physics-plugin traps, headless/server architecture |
Related skill
bevy-upgrade — version migration (0.15→…→0.19): rename tables, semantic shifts, step-by-step upgrade workflow. Use it whenever the task is moving code between versions rather than writing code for one. It may in turn send you back here for post-migration idiomatic cleanup.