Path: blob/main/examples/asset/processing/asset_processing.rs
6595 views
//! This example illustrates how to define custom `AssetLoader`s, `AssetTransformer`s, and `AssetSaver`s, how to configure them, and how to register asset processors.12use bevy::{3asset::{4embedded_asset,5io::{Reader, Writer},6processor::LoadTransformAndSave,7saver::{AssetSaver, SavedAsset},8transformer::{AssetTransformer, TransformedAsset},9AssetLoader, AsyncWriteExt, LoadContext,10},11prelude::*,12reflect::TypePath,13};14use serde::{Deserialize, Serialize};15use std::convert::Infallible;16use thiserror::Error;1718fn main() {19App::new()20// Using the "processed" mode will configure the AssetPlugin to use asset processing.21// If you also enable the `asset_processor` cargo feature, this will run the AssetProcessor22// in the background, run them through configured asset processors, and write the results to23// the `imported_assets` folder. If you also enable the `file_watcher` cargo feature, changes to the24// source assets will be detected and they will be reprocessed.25//26// The AssetProcessor will create `.meta` files automatically for assets in the `assets` folder,27// which can then be used to configure how the asset will be processed.28.add_plugins((29DefaultPlugins.set(AssetPlugin {30mode: AssetMode::Processed,31// This is just overriding the default paths to scope this to the correct example folder32// You can generally skip this in your own projects33file_path: "examples/asset/processing/assets".to_string(),34processed_file_path: "examples/asset/processing/imported_assets/Default"35.to_string(),36..default()37}),38TextPlugin,39))40.add_systems(Startup, setup)41.add_systems(Update, print_text)42.run();43}4445/// This [`TextPlugin`] defines two assets types:46/// * [`CoolText`]: a custom RON text format that supports dependencies and embedded dependencies47/// * [`Text`]: a "normal" plain text file48///49/// It also defines an asset processor that will load [`CoolText`], resolve embedded dependencies, and write the resulting50/// output to a "normal" plain text file. When the processed asset is loaded, it is loaded as a Text (plaintext) asset.51/// This illustrates that when you process an asset, you can change its type! However you don't _need_ to change the type.52struct TextPlugin;5354impl Plugin for TextPlugin {55fn build(&self, app: &mut App) {56embedded_asset!(app, "examples/asset/processing/", "e.txt");57app.init_asset::<CoolText>()58.init_asset::<Text>()59.register_asset_loader(CoolTextLoader)60.register_asset_loader(TextLoader)61.register_asset_processor::<LoadTransformAndSave<CoolTextLoader, CoolTextTransformer, CoolTextSaver>>(62LoadTransformAndSave::new(CoolTextTransformer, CoolTextSaver),63)64.set_default_asset_processor::<LoadTransformAndSave<CoolTextLoader, CoolTextTransformer, CoolTextSaver>>("cool.ron");65}66}6768#[derive(Asset, TypePath, Debug)]69struct Text(String);7071#[derive(Default)]72struct TextLoader;7374#[derive(Clone, Default, Serialize, Deserialize)]75struct TextSettings {76text_override: Option<String>,77}7879impl AssetLoader for TextLoader {80type Asset = Text;81type Settings = TextSettings;82type Error = std::io::Error;83async fn load(84&self,85reader: &mut dyn Reader,86settings: &TextSettings,87_load_context: &mut LoadContext<'_>,88) -> Result<Text, Self::Error> {89let mut bytes = Vec::new();90reader.read_to_end(&mut bytes).await?;91let value = if let Some(ref text) = settings.text_override {92text.clone()93} else {94String::from_utf8(bytes).unwrap()95};96Ok(Text(value))97}9899fn extensions(&self) -> &[&str] {100&["txt"]101}102}103104#[derive(Serialize, Deserialize)]105struct CoolTextRon {106text: String,107dependencies: Vec<String>,108embedded_dependencies: Vec<String>,109dependencies_with_settings: Vec<(String, TextSettings)>,110}111112#[derive(Asset, TypePath, Debug)]113struct CoolText {114text: String,115#[expect(116dead_code,117reason = "Used to show that our assets can hold handles to other assets"118)]119dependencies: Vec<Handle<Text>>,120}121122#[derive(Default)]123struct CoolTextLoader;124125#[derive(Debug, Error)]126enum CoolTextLoaderError {127#[error(transparent)]128Io(#[from] std::io::Error),129#[error(transparent)]130RonSpannedError(#[from] ron::error::SpannedError),131#[error(transparent)]132LoadDirectError(#[from] bevy::asset::LoadDirectError),133}134135impl AssetLoader for CoolTextLoader {136type Asset = CoolText;137type Settings = ();138type Error = CoolTextLoaderError;139140async fn load(141&self,142reader: &mut dyn Reader,143_settings: &Self::Settings,144load_context: &mut LoadContext<'_>,145) -> Result<CoolText, Self::Error> {146let mut bytes = Vec::new();147reader.read_to_end(&mut bytes).await?;148let ron: CoolTextRon = ron::de::from_bytes(&bytes)?;149let mut base_text = ron.text;150for embedded in ron.embedded_dependencies {151let loaded = load_context152.loader()153.immediate()154.load::<Text>(&embedded)155.await?;156base_text.push_str(&loaded.get().0);157}158for (path, settings_override) in ron.dependencies_with_settings {159let loaded = load_context160.loader()161.with_settings(move |settings| {162*settings = settings_override.clone();163})164.immediate()165.load::<Text>(&path)166.await?;167base_text.push_str(&loaded.get().0);168}169Ok(CoolText {170text: base_text,171dependencies: ron172.dependencies173.iter()174.map(|p| load_context.load(p))175.collect(),176})177}178179fn extensions(&self) -> &[&str] {180&["cool.ron"]181}182}183184#[derive(Default)]185struct CoolTextTransformer;186187#[derive(Default, Serialize, Deserialize)]188struct CoolTextTransformerSettings {189appended: String,190}191192impl AssetTransformer for CoolTextTransformer {193type AssetInput = CoolText;194type AssetOutput = CoolText;195type Settings = CoolTextTransformerSettings;196type Error = Infallible;197198async fn transform<'a>(199&'a self,200mut asset: TransformedAsset<Self::AssetInput>,201settings: &'a Self::Settings,202) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {203asset.text = format!("{}{}", asset.text, settings.appended);204Ok(asset)205}206}207208struct CoolTextSaver;209210impl AssetSaver for CoolTextSaver {211type Asset = CoolText;212type Settings = ();213type OutputLoader = TextLoader;214type Error = std::io::Error;215216async fn save(217&self,218writer: &mut Writer,219asset: SavedAsset<'_, Self::Asset>,220_settings: &Self::Settings,221) -> Result<TextSettings, Self::Error> {222writer.write_all(asset.text.as_bytes()).await?;223Ok(TextSettings::default())224}225}226227#[derive(Resource)]228struct TextAssets {229a: Handle<Text>,230b: Handle<Text>,231c: Handle<Text>,232d: Handle<Text>,233e: Handle<Text>,234}235236fn setup(mut commands: Commands, assets: Res<AssetServer>) {237// This the final processed versions of `assets/a.cool.ron` and `assets/foo.c.cool.ron`238// Check out their counterparts in `imported_assets` to see what the outputs look like.239commands.insert_resource(TextAssets {240a: assets.load("a.cool.ron"),241b: assets.load("foo/b.cool.ron"),242c: assets.load("foo/c.cool.ron"),243d: assets.load("d.cool.ron"),244e: assets.load("embedded://asset_processing/e.txt"),245});246}247248fn print_text(249handles: Res<TextAssets>,250texts: Res<Assets<Text>>,251mut asset_events: EventReader<AssetEvent<Text>>,252) {253if !asset_events.is_empty() {254// This prints the current values of the assets255// Hot-reloading is supported, so try modifying the source assets (and their meta files)!256println!("Current Values:");257println!(" a: {:?}", texts.get(&handles.a));258println!(" b: {:?}", texts.get(&handles.b));259println!(" c: {:?}", texts.get(&handles.c));260println!(" d: {:?}", texts.get(&handles.d));261println!(" e: {:?}", texts.get(&handles.e));262println!("(You can modify source assets and their .meta files to hot-reload changes!)");263println!();264asset_events.clear();265}266}267268269