--- name: bevy-0.19 description: 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::()` | `on_message::()` | | 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::` | `WinitPlugin` (not generic) | **0.19-specific:** | Trap | Detail | |------|--------| | Resources are components | Resources live on entities; `Query` matches them — filter broad queries with `Without`. Cannot derive both `Component` and `Resource` on one type. | | Scene renamed | `Scene`/`SceneRoot` → `WorldAsset`/`WorldAssetRoot` | | `Ref.clone()` | Returns `Ref`, not the inner value — use `my_ref.deref().clone()` | | `Assets::get_mut` | Returns `AssetMut` (mutation-tracked), not `&mut A` | | GLTF materials | Return `GltfMaterial`; use `#Material0/std` label for `StandardMaterial` | ## ECS core ### Components ```rust #[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 T` access. - `#[component(storage = "SparseSet")]` for frequently added/removed components. - Lifecycle hooks: `#[component(on_add = f)]`, also `on_insert`, `on_replace`, `on_remove`, `on_despawn` — synchronous, inline with the ECS operation. ### Systems Plain functions of `SystemParam`s. Make fallible systems return `Result` (0.18+) — errors are logged via `BevyError` and the system continues running on its normal schedule; `?` works with anything convertible to `BevyError`: ```rust fn apply_damage(mut q: Query<&mut Health>, mut reader: MessageReader) -> Result { for msg in reader.read() { q.get_mut(msg.target)?.0 -= msg.amount; } Ok(()) } ``` Useful params beyond the obvious: `Single` (exactly one match or panic), `Populated` (skip system if empty), `Option>` (resource may not exist — required for render resources in headless contexts), `Local` (per-system persistent state), `RemovedComponents`. ### Queries - Data: `&T`, `&mut T`, `Entity`, `Has`, tuples. - Filters: `With`, `Without`, `Added`, `Changed`, `Spawned`, `Or<(A, B)>`. - `.single()` panics unless exactly one match; prefer `Single` param or handle the `Result`. - Derive `QueryData`/`QueryFilter` for reusable complex queries. ### Commands `commands.spawn((CompA, CompB))` with tuples. `commands.entity(e).insert(..)` / `.insert_if_new(..)` / `.remove::()` / `.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. ```rust 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::()`. 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`) | 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`/`Added` 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.rs` should be little more than `App::new().add_plugins(...)`. - **Navigate by the plugin tree**: start at `App::new()`, follow `add_plugins` recursively; grep `add_systems` and `#[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_state` run conditions) for game flow; `DespawnOnExit(state)` for scoped entities. Remember same-state `set()` re-fires transitions — one-time setup in `OnEnter` needs `set_if_neq` discipline from callers. - **No formal plugin dependencies exist.** Guard with `is_plugin_added::()`, or do cross-plugin setup in `Plugin::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>`. - **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 treat `transform.back()` as the model's facing. - Transform propagation runs in `PostUpdate`: reading `GlobalTransform` in `Update` gives last frame's value. Move the reader to `PostUpdate` after `TransformSystem::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: 1. **Mesh has no normals** → invisible. Use `.with_computed_flat_normals()` on programmatic meshes. 2. **GLTF spawned by raw handle** → nothing. Spawn scene labels: `"model.glb#Scene0"`. 3. **Parent lacks `Visibility`/`InheritedVisibility`** → children invisible. 4. **Object beyond default far plane (1000 units)** → culled. 5. **Asset cache poisoning**: `load_with_settings` with stripped meshes caches per *path*; a later plain `load()` of the same path returns the stripped version. Never mix stripped and rendered loads of one asset in one process. 6. **Assets aren't ready after `load()`** — gate on `AssetEvent::LoadedWithDependencies` or `is_loaded_with_dependencies()`. 7. **Raw `ButtonInput` fires through UI** — world click handlers need UI-consumption checks. More production gotchas (custom relationships, debugging heuristics): `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 nested `if let` pyramids. - Prefer fallible systems with `?` over `unwrap()` 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, headless contexts, debugging heuristics | ## 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.