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::() .register_asset_loader(LevelLoader); } }
## Asking the reader for random access
```rust
// 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()returnsAssetPath, not&Path. To get the platform path, use.path()on it:ctx.path().path().LoadContext::asset_bytes()is gone. Uselet 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. Useload_context.load_builder(). Immediate loads becameload_value, dynamic loads becameload_erased, and unknown-type loads becameload_untypedbuilder operations.- Custom
Readerimplementations must defineseekable()in 0.19. ReturnOk(self)only when the type implementsAsyncSeek; otherwise returnErr(ReaderNotSeekableError). AssetLoaderisasyncbut you can'ttokio::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 withTypePath. 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.