🤖 Flesh out bevy-0.19 and bevy-upgrade skills from research
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
This commit is contained in:
@@ -1,38 +1,186 @@
|
||||
---
|
||||
name: bevy-0.19
|
||||
description: "TODO: Use the skill-creator skill to write this. See scaffolding goals below."
|
||||
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 — Implementation Helper
|
||||
# Bevy 0.19
|
||||
|
||||
> **This skill is a scaffold.** Use `/skill-creator` to create the full skill content from the research material.
|
||||
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.
|
||||
|
||||
## Scaffolding Goals
|
||||
## Step 0: Confirm the version
|
||||
|
||||
1. **Use the `skill-creator:skill-creator` skill** to create the full SKILL.md content
|
||||
2. Feed it the research reports from [`docs/workpad/research/bevy-skill-research/`](../../../../docs/workpad/research/bevy-skill-research/00-index.md)
|
||||
3. Follow **Approach A** from the [design sketch](../../../../docs/workpad/research/bevy-skill-research/08-landscape-and-design-sketch.md): compact always-loaded cheat sheet (~500 lines) plus `references/` folder for deep dives
|
||||
4. The skill should trigger on Bevy 0.19 Rust code (`bevy = "0.19"` in Cargo.toml, `use bevy::prelude::*`, `App::new()`, `#[derive(Component)]`, etc.)
|
||||
5. Core content priorities (ranked by LLM error frequency):
|
||||
- Event/Message rename and split (0.18)
|
||||
- ChildOf replacing Parent (0.16+)
|
||||
- Fallible systems with `-> Result` (0.18)
|
||||
- Coordinate system gotchas
|
||||
- Transform propagation timing
|
||||
- Resources-as-Components (0.19)
|
||||
6. Populate `references/` with per-topic deep-dive files derived from research reports 01-07
|
||||
7. The skill should know about `bevy-upgrade` and invoke it when upgrade/migration work is detected
|
||||
Check `Cargo.toml` for the `bevy` version before writing code.
|
||||
|
||||
## Reference Material
|
||||
- **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.
|
||||
|
||||
The research that feeds this skill lives in the workpad:
|
||||
## Critical traps
|
||||
|
||||
- [00-index.md](../../../../docs/workpad/research/bevy-skill-research/00-index.md) — Research overview and design rationale
|
||||
- [01-plugins.md](../../../../docs/workpad/research/bevy-skill-research/01-plugins.md) — Plugin lifecycle
|
||||
- [02-ecs-core.md](../../../../docs/workpad/research/bevy-skill-research/02-ecs-core.md) — ECS fundamentals
|
||||
- [03-hierarchy.md](../../../../docs/workpad/research/bevy-skill-research/03-hierarchy.md) — Hierarchy and ChildOf
|
||||
- [04-events-observers.md](../../../../docs/workpad/research/bevy-skill-research/04-events-observers.md) — Events, messages, observers
|
||||
- [05-input.md](../../../../docs/workpad/research/bevy-skill-research/05-input.md) — Input handling
|
||||
- [06-camera-gotchas.md](../../../../docs/workpad/research/bevy-skill-research/06-camera-gotchas.md) — Camera and rendering
|
||||
- [07-lessons-from-production.md](../../../../docs/workpad/research/bevy-skill-research/07-lessons-from-production.md) — Production gotchas
|
||||
- [08-landscape-and-design-sketch.md](../../../../docs/workpad/research/bevy-skill-research/08-landscape-and-design-sketch.md) — Architecture options
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user