How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/chrisgliddon/bevy-skills --skill bevy-custom-assetsBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Bevy 0.19 — Custom asset loaders
When to use this skill
- Loading a custom binary or text format (proprietary game data, sidecar configs, etc.).
- Producing an asset that depends on other assets (e.g. a level loader that pulls referenced textures).
- Migrating
LoadContext::loader()or an oldNestedLoadertoload_builder()/NestedLoadBuilder. - Needing async access to the underlying file (
reader.read_to_end,reader.seekable()). - Compiler error: "
MyLoader: TypePathis not implemented" — 0.18 madeTypePathmandatory on loaders.
Canonical pattern
use bevy::asset::io::Reader;
use bevy::asset::{Asset, AssetApp, AssetLoader, LoadContext};
use bevy::prelude::*;
use bevy::reflect::TypePath;
use serde::Deserialize;
use thiserror::Error;
#[derive(Asset, TypePath, Debug, Deserialize)]
pub struct LevelDef {
pub name: String,
pub gravity: f32,
pub thumbnail: String, // path to a referenced texture asset
}
#[derive(TypePath)] // 0.18: required on the loader itself.
pub struct LevelLoader;
#[derive(Debug, Error)]
pub enum LevelLoaderError {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("parse: {0}")]
Parse(#[from] ron::error::SpannedError),
}
impl AssetLoader for LevelLoader {
type Asset = LevelDef;
type Settings = ();
type Error = LevelLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
// Pull the whole file. For very large files prefer `seekable()`.
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let level: LevelDef = ron::de::from_bytes(&bytes)?;
// Pull in a referenced asset so it loads alongside this one.
// The resulting handle ends up tracked as a dependency.
let _: Handle<Image> = load_context.load_builder().load(level.thumbnail.clone());
Ok(level)
}
fn extensions(&self) -> &[&str] {
&["level.ron"]
}
}
pub struct LevelLoaderPlugin;
impl Plugin for LevelLoaderPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<LevelDef>()
.register_asset_loader(LevelLoader);
}
}Asking the reader for random access
// Bevy 0.19 requires Reader implementations to expose `seekable()`.
# use bevy::asset::io::Reader;
# async fn _example(reader: &mut dyn Reader) -> std::io::Result<()> {
match reader.seekable() {
Ok(seekable) => {
// SeekableReader supports `seek(SeekFrom::Start(n))`, `seek(SeekFrom::End(-n))`, etc.
let _ = seekable;
}
Err(_) => {
// Source isn't seekable (e.g. HTTP backend). Fall back to streaming.
}
}
# Ok(())
# }Bevy 0.19 gotchas
- `#[derive(TypePath)]` is required on the loader struct (not just the asset). 0.18 enforces this so loaders can be reflected. Without it: "
MyLoader: TypePathis not implemented". - `LoadContext::path()` returns `AssetPath`, not
&Path. To get the platform path, use.path()on it:ctx.path().path(). - `LoadContext::asset_bytes()` is gone. Use
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;inside theloadasync fn, orreader.seekable()for random access. - Dependencies must be loaded through `LoadContext`.
asset_server.load(...)from inside a loader does not register the result as a dependency of the asset being built — useload_context.load_builder().load(...)(or theload(...)convenience method) soAssetEvent::LoadedWithDependenciesfires correctly. - `LoadContext::loader()` was replaced in 0.19. Use
load_context.load_builder(). Immediate loads becameload_value, dynamic loads becameload_erased, and unknown-type loads becameload_untypedbuilder operations. - Custom `Reader` implementations must define `seekable()` in 0.19. Return
Ok(self)only when the type implementsAsyncSeek; otherwise returnErr(ReaderNotSeekableError). - `AssetLoader` is `async` but you can't
tokio::spawninside it — the executor is Bevy's, not Tokio's. Usebevy::tasks::futures_liteorbevy::tasks::AsyncComputeTaskPoolfor compute-heavy work. - `#[derive(Asset)]` is required on the asset type and combined with
TypePath. The asset'sSettingstype must beDefault + Serialize + DeserializeOwned + Send + Sync + 'staticto participate in.metafiles. - Extensions are matched on the full suffix.
"level.ron"matchesfoo.level.ronbut notfoo.ron— useful for disambiguating from generic RON.
See also
bevy-assets— using aHandle<MyAsset>once it's loaded.bevy-voxel-data— RON-driven asset patterns.bevy-migration-0-17-to-0-18—LoadContext::asset_bytesremoval.bevy-migration-0-18-to-0-19— nested load builders and reader changes.




