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 is a trait in Amethyst for implementing a format: Format<A: Asset::Data>
.
Format
provides a loading implementation that provides detection when an asset should be reloaded for hot reloading; you don't need to implement it since it has a default implementation.
A blanket implementation will implement Format::import
and we only need to implement
Format::import_simple
.
Format
takes a type parameter for the asset data type it supports. This guide covers a type
parameterized implementation of Format<D>
where D
is an arbitrary Asset::Data
, so we can
reuse it for any asset which can be loaded from deserializable asset data.
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
Format
trait.This is where the logic to deserialize the asset data type is provided. Fields of the format struct can be used to specify additional parameters for deserialization; use a unit struct 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, Format}, }; 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<D> Format<D> for MyLangFormat where D: for<'a> Deserialize<'a> + Send + Sync + 'static, { fn name(&self) -> &'static str { "MyLangFormat" } fn import_simple(&self, bytes: Vec<u8>) -> Result<D, Error> { let mut deserializer = Deserializer::from_bytes(&bytes)?; let val = D::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, Format, }, ecs::{VecStorage, World, WorldExt}, 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<D> Format<D> for MyLangFormat where D: for<'a> DeserializeTrait<'a> + Send + Sync + 'static, { fn name(&self) -> &'static str { "MyLangFormat" } fn import_simple(&self, bytes: Vec<u8>) -> Result<D, Error> { let mut deserializer = Deserializer::from_bytes(&bytes)?; let val = D::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(()) }