Files
reliquary/plugins/bevy/skills/bevy-0.19/SKILL.md
T
g4borg 8988b16bee 🤖 Fix proofreading issues in bevy skills
- Fix `ref` keyword used as variable name in code examples
- Fix "retries next tick" → systems continue on normal schedule
- Fix `State::set()` → `NextState::set()` in upgrade skill
- Fix `SceneRoot` → `WorldAssetRoot` for 0.19 consistency
- Fix Replace lifecycle event description (overwritten, not removed)
- Fix grep patterns (quoting, trailing spaces)
- Remove third-party physics/networking content (not Bevy built-ins)
2026-07-07 17:52:20 +02:00

11 KiB
Raw Blame History

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.140.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/SceneRootWorldAsset/WorldAssetRoot
Ref<T>.clone() Returns Ref<T>, not the inner value — use my_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 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 SystemParams. 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:

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.

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 (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

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.