--- 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. ## Minimal starting point ```rust use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, gameplay) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera3d); } fn gameplay() {} ``` `DefaultPlugins` includes windowing, rendering, input, audio, and asset loading. For headless (tests, servers): `MinimalPlugins`. ## 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`. Schedules: `Startup` runs once at launch (initialization, spawning the world). `Update` runs every frame. `FixedUpdate` runs at a fixed timestep (framerate-independent simulation). Full schedule list: `references/plugins-and-app.md`. ### 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. ### Resources Global singletons accessed by type: ```rust #[derive(Resource)] struct Score(u32); // Insert in setup: app.insert_resource(Score(0)); app.init_resource::(); // requires Default // Access in systems: fn show_score(score: Res) { /* read */ } fn add_point(mut score: ResMut) { score.0 += 1; } ``` - `Option>` / `Option>` for resources that may not exist — mandatory for render resources in headless-capable systems. - `Local` is per-system private state (default-initialized, persists across runs) — not a resource. **0.19**: Resources live on dedicated entities internally. This means `Query` (and other broad queries) match resource entities. Filter with `Without`. A type cannot derive both `Component` and `Resource`. See below. Details: `references/ecs.md`. ### Resources-as-components (0.19) In 0.19, `Resource` is implemented via `Component` internally. Each resource lives on a hidden entity. This is mostly transparent, but creates real issues: 1. **Broad queries match resources.** `Query` or `Query<&Name>` will include resource entities. Always add `Without` to gameplay queries that don't filter on a specific game component. 2. **Cannot dual-derive.** `#[derive(Component, Resource)]` on one type is a compile error. Pick one role. If you need both a global config and per-entity overrides, use two types. 3. **Observers see resources.** Entity-lifecycle observers (`on_add`, `on_remove`) fire for resource insertion/removal. This can surprise broad observers like `On>`. 4. **`Res` / `ResMut` still work.** The system param API is unchanged — you do not need to query for resources manually. The entity backing is an implementation detail unless you write broad queries or observers. ## 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. ## Asset loading ```rust fn setup(mut commands: Commands, asset_server: Res) { let texture: Handle = asset_server.load("textures/player.png"); let scene: Handle = asset_server.load("models/enemy.glb#Scene0"); commands.spawn((Mesh3d(asset_server.load("mesh.glb#Mesh0/Primitive0")), MeshMaterial3d(asset_server.load("mesh.glb#Material0/std")))); } ``` - `asset_server.load(path)` returns a `Handle` immediately; the asset loads asynchronously in the background. - Assets live in `Assets` collections: `fn sys(images: Res>)` to inspect loaded data. - Gate on readiness with `AssetEvent::LoadedWithDependencies` or `asset_server.is_loaded_with_dependencies(handle)`. - GLTF scenes must use **scene labels** (`"model.glb#Scene0"`), not the raw file handle. - **0.19**: `Scene`/`SceneRoot` renamed to `WorldAsset`/`WorldAssetRoot`. `Assets::get_mut` returns `AssetMut` (mutation-tracked). Caching pitfalls and more: `references/camera-rendering.md`. ## Time and timers ```rust fn move_player(time: Res