🤖 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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user