🤖 Add missing fundamentals to bevy-0.19 skill

- Minimal App::new() starting point with DefaultPlugins/MinimalPlugins
- Resources section (Res/ResMut, insert, init, Option for headless)
- Resources-as-components 0.19 expansion (broad query traps, dual-derive, observers)
- Startup/Update/FixedUpdate schedule overview
- Asset loading (handles, scene labels, readiness gates, 0.19 renames)
- Time and timers (delta_secs, Timer component pattern)
This commit is contained in:
2026-07-07 17:52:30 +02:00
parent 8988b16bee
commit 51e7a2579b
+103
View File
@@ -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<Damage>) ->
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>`.
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<T>`, tuples.
@@ -96,6 +120,39 @@ Useful params beyond the obvious: `Single<D, F>` (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::<Score>(); // requires Default
// Access in systems:
fn show_score(score: Res<Score>) { /* read */ }
fn add_point(mut score: ResMut<Score>) { score.0 += 1; }
```
- `Option<Res<T>>` / `Option<ResMut<T>>` for resources that may not exist — mandatory for render resources in headless-capable systems.
- `Local<T>` is per-system private state (default-initialized, persists across runs) — not a resource.
**0.19**: Resources live on dedicated entities internally. This means `Query<Entity>` (and other broad queries) match resource entities. Filter with `Without<IsResource>`. 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<Entity>` or `Query<&Name>` will include resource entities. Always add `Without<IsResource>` 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<Add<Name>>`.
4. **`Res<T>` / `ResMut<T>` 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<AssetServer>) {
let texture: Handle<Image> = asset_server.load("textures/player.png");
let scene: Handle<WorldAsset> = 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<T>` immediately; the asset loads asynchronously in the background.
- Assets live in `Assets<T>` collections: `fn sys(images: Res<Assets<Image>>)` 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<A>` (mutation-tracked).
Caching pitfalls and more: `references/camera-rendering.md`.
## Time and timers
```rust
fn move_player(time: Res<Time>, mut q: Query<&mut Transform, With<Player>>) -> Result {
let mut t = q.single_mut()?;
t.translation += Vec3::NEG_Z * 5.0 * time.delta_secs();
Ok(())
}
```
- `Res<Time>``time.delta_secs()` (f32) or `time.delta()` (Duration) for the frame delta.
- `time.elapsed_secs()` for total time since app start.
```rust
#[derive(Component)]
struct FireRate(Timer);
fn shoot(time: Res<Time>, mut q: Query<&mut FireRate>) {
for mut rate in &mut q {
rate.0.tick(time.delta());
if rate.0.just_finished() { /* fire */ }
}
}
```
`Timer::from_seconds(1.0, TimerMode::Repeating)` for recurring; `TimerMode::Once` for one-shot. Timers do not auto-tick — call `.tick(delta)` each frame.
## Silent-failure checklist
Bevy often succeeds silently when misconfigured. If something "doesn't show up" with no errors: