c661027d19
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
187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
---
|
||
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::<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
|
||
|
||
```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 log and the system retries next tick; `?` works with anything convertible to `BevyError`:
|
||
|
||
```rust
|
||
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; 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::<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.
|
||
|
||
```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::<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.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::<T>()`, 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<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 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<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 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, 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.
|