diff --git a/plugins/bevy/skills/bevy-0.19/SKILL.md b/plugins/bevy/skills/bevy-0.19/SKILL.md index 8d88c67..42ed4ff 100644 --- a/plugins/bevy/skills/bevy-0.19/SKILL.md +++ b/plugins/bevy/skills/bevy-0.19/SKILL.md @@ -15,6 +15,28 @@ Check `Cargo.toml` for the `bevy` version before writing code. - **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: @@ -81,6 +103,8 @@ fn apply_damage(mut q: Query<&mut Health>, mut reader: MessageReader) -> 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. @@ -96,6 +120,39 @@ Useful params beyond the obvious: `Single` (exactly one match or panic), ` `(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. @@ -144,6 +201,52 @@ Plugin trait lifecycle, plugin groups, states, run conditions: `references/plugi - 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