About bevy-assets
bevy-assets is a Bevy 0.19 skill for loading and managing assets in applications. It guides developers through readiness checks, glTF scene instances, asset events, hot reload, and version-specific API changes.
Key features
- 01Loading assets asynchronously
- 02Checking asset readiness
- 03Handling asset events
- 04Spawning and managing glTF scenes
- 05Enabling development hot reload
- 06Navigating Bevy 0.19 API changes
Use cases
- 01Loading models, textures, or audio
- 02Waiting for an asset before reading it
- 03Responding to asset modifications
- 04Managing glTF scene instances
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ chrisgliddon/ bevy-skills --skill bevy-assetsAfter installing, type/bevy-assetsin Claude Code to confirm it loaded.
Bundled filesreferences/gltf-scenes.md
Details
From the project READMEBevy 0.19 — Assets
When to use this skill
- Loading a model, texture, audio file, or scene.
- Reading the loaded data back from
Assets<T>once it's ready. - Reacting to load progress (
AssetEvent::Added/Modified). - Spawning glTF scenes, waiting for
WorldInstanceReady, reading names/extras, or owning/despawning an instance cleanly. - Enabling hot-reload during development.
- Writing your own loader → see
bevy-custom-assets.
Canonical pattern
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(AssetPlugin {
// Hot-reload on file change — dev-only.
watch_for_changes_override: Some(true),
..default()
}))
.init_resource::<MyHandles>()
.add_systems(Startup, load_handles)
.add_systems(Update, react_to_loads)
.run();
}
#[derive(Resource, Default)]
struct MyHandles {
hero: Handle<WorldAsset>,
bricks: Handle<Image>,
}
fn load_handles(asset_server: Res<AssetServer>, mut handles: ResMut<MyHandles>) {
// GLTF scenes are addressed by sub-asset label.
handles.hero = asset_server.load("models/hero.glb#Scene0");
handles.bricks = asset_server.load("textures/bricks.png");
}
fn react_to_loads(
mut ev: MessageReader<AssetEvent<Image>>,
images: Res<Assets<Image>>,
) {
for event in ev.read() {
if let AssetEvent::LoadedWithDependencies { id } = event {
if let Some(img) = images.get(*id) {
info!("image loaded: {}x{}", img.width(), img.height());
}
}
}
}
Asset paths
use bevy::asset::AssetPath;
// `LoadContext::path()` returns `AssetPath`, not `&Path`.
// Build paths explicitly when generating handles inside a custom loader:
let path = AssetPath::from("textures/bricks.png");
let path_with_label = AssetPath::from("models/hero.glb").with_label("Scene0");
let _ = path;
let _ = path_with_label;
Asset readiness check
use bevy::prelude::*;
# fn _check(
asset_server: Res<AssetServer>,
handles: Res<MyHandles>,
# ) {
use bevy::asset::LoadState;
if asset_server.load_state(&handles.hero) == LoadState::Loaded {
// Safe to spawn WorldAssetRoot(handles.hero.clone()).
}
# }
# #[derive(Resource)] struct MyHandles { hero: Handle<WorldAsset> }
Bevy 0.19 gotchas
LoadContext::path()returnsAssetPath, not&Path. Callers that didctx.path().to_string_lossy()need toctx.path().path().to_string_lossy()or use theAssetPathAPI directly.SeekableReaderis new in 0.18. Loaders that need random access into the underlying file can ask:if let Ok(s) = reader.seekable() { /* s: &mut dyn SeekableReader */ }.- Every custom
Readerimplementsseekable()in 0.19. ReturnOk(self)when it also implementsAsyncSeek; otherwise returnErr(ReaderNotSeekableError).AsyncSeekForwardwas removed. - Advanced loads use builders in 0.19. Prefer
AssetServer::load_builder()for settings, guards, untyped loads, or approval overrides; the many specializedload_*variants are deprecated. AssetPath::resolvenow takes&AssetPath. Useresolve_str/resolve_embed_strwhen the child path starts as text.AssetSourceBuilder::new(...)replacesAssetSource::build().with_reader(...). Existing custom asset sources need to be re-shaped.AssetSourcechannel isasync_channel::Senderin 0.18 (wascrossbeam_channel). Usesend_blocking(...).Image::reinterpret_size(size)returnsResultin 0.18.- Sub-asset labels.
path.glb#Scene0,path.glb#Mesh0/Primitive0— distinct handles, can be loaded independently. Forgetting the label gives you the root asset, not the named one. - Hot reload is dev-only. Don't ship
watch_for_changes_override: Some(true)in a release build — it polls the filesystem. asset_server.load(...)is non-blocking. QueryingAssets<T>::getimmediately after returnsNone. Wait forAssetEvent::LoadedWithDependenciesor pollload_state(...).AssetEvent<T>is aMessage, so iterate withMessageReader<AssetEvent<T>>, not the oldEventReader.
See also
- glTF scenes —
GltfAssetLabel,WorldAssetRoot, instance readiness/ownership, names/extras, metadata proxies, and despawning. bevy-custom-assets— writing anAssetLoaderand nested load builder.bevy-save-load— durable stable IDs and save schema; glTF names/entity IDs are not persistence contracts.bevy-migration-0-17-to-0-18—LoadContext::pathand channel-type renames.bevy-migration-0-18-to-0-19— load builders and requiredReader::seekable.
Comments
Markdown supported