custom_asset/
custom_asset.rs

1//! Implements loader for a custom asset type.
2
3use bevy::{
4    asset::{io::Reader, AssetLoader, LoadContext},
5    prelude::*,
6    reflect::TypePath,
7};
8use serde::Deserialize;
9use thiserror::Error;
10
11#[derive(Asset, TypePath, Debug, Deserialize)]
12struct CustomAsset {
13    #[expect(
14        dead_code,
15        reason = "Used to show how the data inside an asset file will be loaded into the struct"
16    )]
17    value: i32,
18}
19
20#[derive(Default)]
21struct CustomAssetLoader;
22
23/// Possible errors that can be produced by [`CustomAssetLoader`]
24#[non_exhaustive]
25#[derive(Debug, Error)]
26enum CustomAssetLoaderError {
27    /// An [IO](std::io) Error
28    #[error("Could not load asset: {0}")]
29    Io(#[from] std::io::Error),
30    /// A [RON](ron) Error
31    #[error("Could not parse RON: {0}")]
32    RonSpannedError(#[from] ron::error::SpannedError),
33}
34
35impl AssetLoader for CustomAssetLoader {
36    type Asset = CustomAsset;
37    type Settings = ();
38    type Error = CustomAssetLoaderError;
39    async fn load(
40        &self,
41        reader: &mut dyn Reader,
42        _settings: &(),
43        _load_context: &mut LoadContext<'_>,
44    ) -> Result<Self::Asset, Self::Error> {
45        let mut bytes = Vec::new();
46        reader.read_to_end(&mut bytes).await?;
47        let custom_asset = ron::de::from_bytes::<CustomAsset>(&bytes)?;
48        Ok(custom_asset)
49    }
50
51    fn extensions(&self) -> &[&str] {
52        &["custom"]
53    }
54}
55
56#[derive(Asset, TypePath, Debug)]
57struct Blob {
58    bytes: Vec<u8>,
59}
60
61#[derive(Default)]
62struct BlobAssetLoader;
63
64/// Possible errors that can be produced by [`BlobAssetLoader`]
65#[non_exhaustive]
66#[derive(Debug, Error)]
67enum BlobAssetLoaderError {
68    /// An [IO](std::io) Error
69    #[error("Could not load file: {0}")]
70    Io(#[from] std::io::Error),
71}
72
73impl AssetLoader for BlobAssetLoader {
74    type Asset = Blob;
75    type Settings = ();
76    type Error = BlobAssetLoaderError;
77
78    async fn load(
79        &self,
80        reader: &mut dyn Reader,
81        _settings: &(),
82        _load_context: &mut LoadContext<'_>,
83    ) -> Result<Self::Asset, Self::Error> {
84        info!("Loading Blob...");
85        let mut bytes = Vec::new();
86        reader.read_to_end(&mut bytes).await?;
87
88        Ok(Blob { bytes })
89    }
90}
91
92fn main() {
93    App::new()
94        .add_plugins(DefaultPlugins)
95        .init_resource::<State>()
96        .init_asset::<CustomAsset>()
97        .init_asset::<Blob>()
98        .init_asset_loader::<CustomAssetLoader>()
99        .init_asset_loader::<BlobAssetLoader>()
100        .add_systems(Startup, setup)
101        .add_systems(Update, print_on_load)
102        .run();
103}
104
105#[derive(Resource, Default)]
106struct State {
107    handle: Handle<CustomAsset>,
108    other_handle: Handle<CustomAsset>,
109    blob: Handle<Blob>,
110    printed: bool,
111}
112
113fn setup(mut state: ResMut<State>, asset_server: Res<AssetServer>) {
114    // Recommended way to load an asset
115    state.handle = asset_server.load("data/asset.custom");
116
117    // File extensions are optional, but are recommended for project management and last-resort inference
118    state.other_handle = asset_server.load("data/asset_no_extension");
119
120    // Will use BlobAssetLoader instead of CustomAssetLoader thanks to type inference
121    state.blob = asset_server.load("data/asset.custom");
122}
123
124fn print_on_load(
125    mut state: ResMut<State>,
126    custom_assets: Res<Assets<CustomAsset>>,
127    blob_assets: Res<Assets<Blob>>,
128) {
129    let custom_asset = custom_assets.get(&state.handle);
130    let other_custom_asset = custom_assets.get(&state.other_handle);
131    let blob = blob_assets.get(&state.blob);
132
133    // Can't print results if the assets aren't ready
134    if state.printed {
135        return;
136    }
137
138    if custom_asset.is_none() {
139        info!("Custom Asset Not Ready");
140        return;
141    }
142
143    if other_custom_asset.is_none() {
144        info!("Other Custom Asset Not Ready");
145        return;
146    }
147
148    if blob.is_none() {
149        info!("Blob Not Ready");
150        return;
151    }
152
153    info!("Custom asset loaded: {:?}", custom_asset.unwrap());
154    info!("Custom asset loaded: {:?}", other_custom_asset.unwrap());
155    info!("Blob Size: {} Bytes", blob.unwrap().bytes.len());
156
157    // Once printed, we won't print again
158    state.printed = true;
159}