How to Define Custom Formats
This guide explains how to define a new asset format. This will allow Amethyst to load assets stored in a particular encoding.
There are two formatting traits in Amethyst – Format<A: Asset>
and SimpleFormat<A: Asset>
. Format
requests a loading implementation that provides detection when an asset should be reloaded for hot reloading; SimpleFormat
requires a loading implementation. A blanket implementation will implement Format
for all SimpleFormat
implementations, and provides hot reloading detection based on file modification time – this is sufficient for most applications that require hot reloading. This guide covers implementation of the SimpleFormat
trait.
SimpleFormat
takes a type parameter for the asset type it supports. This guide covers a type parameterized implementation of SimpleFormat<A: Asset>
for all A
, as it is easier to deduce the specific implementation from a parameterized implementation than the other way around.
If you are defining a new format that may be useful to others, please send us a PR!
-
Define a struct that represents the format.
In most cases a unit struct is sufficient. When possible, this should implement
Clone
andCopy
for ergonomic usage./// Format for loading from `.mylang` files. #[derive(Clone, Copy, Debug, Default)] pub struct MyLangFormat;
-
Implement the
SimpleFormat
trait.This is where the logic to deserialize the asset data type is provided. The
Options
associated type can be used to specify additional parameters for deserialization; use the empty tuple()
if this is not needed.In this example the RON deserializer is used, though it is already a supported format.
# extern crate amethyst; # extern crate ron; # extern crate serde; # use amethyst::{ error::Error, assets::{Asset, SimpleFormat}, }; use serde::Deserialize; use ron::de::Deserializer; // Replace this in your implementation. /// Format for loading from `.mylang` files. #[derive(Clone, Copy, Debug, Default)] pub struct MyLangFormat; impl<A> SimpleFormat<A> for MyLangFormat where A: Asset, A::Data: for<'a> Deserialize<'a> + Send + Sync + 'static, { const NAME: &'static str = "MyLang"; // If the deserializer implementation takes parameters, // the parameter type may be specified here. type Options = (); fn import(&self, bytes: Vec<u8>, _: ()) -> Result<A::Data, Error> { let mut deserializer = Deserializer::from_bytes(&bytes)?; let val = A::Data::deserialize(&mut deserializer)?; deserializer.end()?; Ok(val) } }
The custom format can now be used:
# extern crate amethyst; # extern crate ron; # extern crate serde; # extern crate serde_derive; # # use amethyst::{ # error::Error, # assets::{ # Asset, AssetStorage, Handle, Loader, Processor, ProgressCounter, # ProcessingState, SimpleFormat, # }, # ecs::VecStorage, # prelude::*, # utils::application_root_dir, # }; # use ron::de::Deserializer; # use serde::Deserialize as DeserializeTrait; # use serde_derive::{Deserialize, Serialize}; # # /// Custom asset representing an energy blast. # #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] # pub struct EnergyBlast { # /// How much HP to subtract. # pub hp_damage: u32, # /// How much MP to subtract. # pub mp_damage: u32, # } # # /// A handle to a `EnergyBlast` asset. # pub type EnergyBlastHandle = Handle<EnergyBlast>; # # impl Asset for EnergyBlast { # const NAME: &'static str = "my_crate::EnergyBlast"; # type Data = Self; # type HandleStorage = VecStorage<EnergyBlastHandle>; # } # # impl From<EnergyBlast> for Result<ProcessingState<EnergyBlast>, Error> { # fn from(energy_blast: EnergyBlast) -> Result<ProcessingState<EnergyBlast>, Error> { # Ok(ProcessingState::Loaded(energy_blast)) # } # } # # pub struct LoadingState { # /// Tracks loaded assets. # progress_counter: ProgressCounter, # /// Handle to the energy blast. # energy_blast_handle: Option<EnergyBlastHandle>, # } # # /// Format for loading from `.mylang` files. # #[derive(Clone, Copy, Debug, Default)] # pub struct MyLangFormat; # # impl<A> SimpleFormat<A> for MyLangFormat # where # A: Asset, # A::Data: for<'a> DeserializeTrait<'a> + Send + Sync + 'static, # { # const NAME: &'static str = "MyLang"; # # // If the deserializer implementation takes parameters, # // the parameter type may be specified here. # type Options = (); # # fn import(&self, bytes: Vec<u8>, _: ()) -> Result<A::Data, Error> { # let mut deserializer = Deserializer::from_bytes(&bytes)?; # let val = A::Data::deserialize(&mut deserializer)?; # deserializer.end()?; # # Ok(val) # } # } # impl SimpleState for LoadingState { fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) { let loader = &data.world.read_resource::<Loader>(); let energy_blast_handle = loader.load( "energy_blast.mylang", MyLangFormat, (), &mut self.progress_counter, &data.world.read_resource::<AssetStorage<EnergyBlast>>(), ); self.energy_blast_handle = Some(energy_blast_handle); } # # fn update( # &mut self, # _data: &mut StateData<'_, GameData<'_, '_>>, # ) -> SimpleTrans { # Trans::Quit # } } # # fn main() -> amethyst::Result<()> { # amethyst::start_logger(Default::default()); # let app_root = application_root_dir()?; # let assets_dir = app_root.join("assets"); # # let game_data = GameDataBuilder::default() # .with(Processor::<EnergyBlast>::new(), "", &[]); # let mut game = Application::new( # assets_dir, # LoadingState { # progress_counter: ProgressCounter::new(), # energy_blast_handle: None, # }, # game_data, # )?; # # game.run(); # Ok(()) # }