Skip to content
Back to skills

Manage Bevy Assets

ASecurity

Use when a Bevy game loads sprites, audio, levels or custom data — a loading state with handles, one asset-table seam, custom loaders, hot reload in dev, optional processing, and embedded assets so web and single-file builds work.

  • 5 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 10, 2026
developmentrustgobashreactapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 10, 2026

npx -y skills add VelimirMueller/lab-claude-skills --skill manage-bevy-assets --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Manage Bevy Assets?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Manage Bevy Assets
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/velimirmueller-manage-bevy-assets/badge)](https://www.skillsdirectory.com/skills/velimirmueller-manage-bevy-assets)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: manage-bevy-assets
description: Use when a Bevy game loads sprites, audio, levels or custom data — a loading state with handles, one asset-table seam, custom loaders, hot reload in dev, optional processing, and embedded assets so web and single-file builds work.
---

# Manage Bevy Assets

Bevy 0.20 native asset API. Rationale: [asset-patterns.md](asset-patterns.md). Versions and the `bevy_asset_loader` status: [stack-versions](../_shared/stack-versions.md).

## 1. Audit current state (change nothing)

```bash
cat .claude/stack-profile.md 2>/dev/null                  # does the target include web?
ls assets assets/* 2>/dev/null | head -30; ls src/assets.rs 2>/dev/null
grep -rnE 'asset_server\.load|AssetServer' src/ | grep -v "src/assets.rs"       # loads outside the seam
grep -nE 'file_watcher|embedded_watcher|asset_processor|AssetMetaCheck|AssetMode' Cargo.toml src/ -r
grep -rnE 'Screen::Loading|LoadState|is_loaded_with_dependencies|recursive_dependency_load_state' src/
grep -n "bevy_asset_loader\|bevy_common_assets" Cargo.toml
```

## 2. Decide what to do
- No loading state and loads scattered across plugins → build the seam and the loading state (steps 5–6).
- Seam exists → add only what is missing (custom loader, hot reload, web meta check).
- Seam, loading gate, `dev` hot reload all present → "already in place", stop.
- `bevy_asset_loader` already in `Cargo.toml` → check its `bevy` requirement against the project's Bevy line; keep it only if it matches.

## 3. Detect track
- **Desktop** → files on disk in `assets/`; hot reload via the `dev` feature.
- **Web** → no filesystem: set `AssetMetaCheck::Never`, never use `AssetMode::Processed`, embed what the first frame needs.
- **Single-file release** → `embedded_asset!` for the few assets that must ship inside the binary; everything else stays in `assets/`.
- **Custom data (levels, config, tables)** → a custom `AssetLoader` (step 5). Use `bevy_common_assets` only if its stable release targets your Bevy line.

## 4. Install only what's missing
Nothing by default. Native loading covers handles, folders, custom loaders and hot reload.
`bevy_asset_loader` and `bevy_common_assets`: on 2026-10-09 only release candidates targeted Bevy 0.20 ([stack-versions](../_shared/stack-versions.md)). Re-check crates.io; do not add a release candidate to a production project.

## 5. Generate the seams

**Asset table (`src/assets.rs`)** — the only module that calls `asset_server.load`. Strong handles keep assets alive.

```rust
#[derive(Resource)]
pub struct GameAssets {
    pub player: Handle<Image>,
    pub jump: Handle<AudioSource>,
}

pub struct AssetsPlugin;

impl Plugin for AssetsPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(OnEnter(Screen::Loading), start_loading).add_systems(
            Update,
            finish_loading
                .run_if(in_state(Screen::Loading))
                .run_if(resource_exists::<GameAssets>),
        );
    }
}

fn start_loading(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.insert_resource(GameAssets {
        player: asset_server.load("sprites/player.png"),
        jump: asset_server.load("audio/jump.ogg"),
    });
}

fn finish_loading(
    assets: Res<GameAssets>,
    asset_server: Res<AssetServer>,
    mut next: ResMut<NextState<Screen>>,
) {
    let ids = [assets.player.id().untyped(), assets.jump.id().untyped()];
    let mut all_loaded = true;
    for id in ids {
        match asset_server.recursive_dependency_load_state(id) {
            RecursiveDependencyLoadState::Loaded => {}
            RecursiveDependencyLoadState::Failed(error) => panic!("asset failed to load: {error}"),
            _ => all_loaded = false,
        }
    }
    if all_loaded {
        next.set(Screen::Menu);
    }
}
```
(`use bevy::asset::RecursiveDependencyLoadState;`.) A failed load stops the game with the file in the message: a broken path never becomes a silent invisible sprite.

**Custom asset and loader** — template in [asset-patterns.md](asset-patterns.md#rule-custom-data-is-an-asset-with-a-loader): `#[derive(Asset, TypePath)]`, `impl AssetLoader`, `init_asset::<T>().init_asset_loader::<L>()`.

**Load settings** (pixel art, per asset): `asset_server.load_builder().with_settings(|s: &mut ImageLoaderSettings| { s.sampler = ImageSampler::nearest(); }).load("sprites/player.png")`. Global default: `DefaultPlugins.set(ImagePlugin::default_nearest())`.

## 6. Wire it
- `AssetsPlugin` into `GamePlugin`; `Screen::Loading` is the default state ([structure-bevy-app](../structure-bevy-app/SKILL.md)).
- Other plugins take `Res<GameAssets>`; none calls `asset_server.load` directly.
- Hot reload: `dev = ["bevy/dev", ...]` already enables `file_watcher`. Add `bevy/embedded_watcher` for embedded assets. Run `cargo run --features dev`; edit a file in `assets/`.
- Web: `DefaultPlugins.set(AssetPlugin { meta_check: AssetMetaCheck::Never, ..default() })` (`use bevy::asset::AssetMetaCheck;`).
- Embedded: in the plugin `embedded_asset!(app, "src/", "splash.png");` then load `"embedded://<crate_name>/splash.png"`.
- Reacting to reloads: read `MessageReader<AssetEvent<T>>`; `Modified` fires only on real mutation since 0.19.

## 7. Verify

```bash
cargo clippy --all-targets -- -D warnings && cargo nextest run
cargo run --features dev    # starts in Loading, reaches Menu; touching assets/<file> logs/shows a reload
grep -rn "asset_server.load" src/ | grep -v "src/assets.rs"; echo "exit $?"
```

Expected: tests pass; the game leaves `Loading`; the grep prints nothing and `exit 1`. A headless loader test ([test-bevy-systems](../test-bevy-systems/SKILL.md)) loads a fixture file and asserts its content. Rename an asset file: the game must stop with `asset failed to load`.

Contracts: [engineering principles](../../core/_shared/engineering-principles.md) (fail fast, one seam), [security baseline](../../core/_shared/security-baseline.md) (no secrets in `assets/`; it ships to players).

Files in this skill

  • SKILL.md5.9 KB
  • asset-patterns.md8.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.