Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ron format maps as assets #180

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions bevy_asset_loader/assets/custom.map.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
({
"materials": ({
"tree": StandardMaterial (
base_color: (0.1, 10., 0.3, 0.0),
base_color_texture: "images/tree.png"
),
"player": StandardMaterial (
base_color: (0.0, 1.0, 0.0, 0.0),
base_color_texture: "images/player.png"
),
}),
"images": ({
"combined_image": CombinedImage (
bottom_layer: "images/tree.png",
top_layer: "images/player.png",
),
}),
"meshes": ({
"cube": Cube (
size: 1.3,
),
}),
})
230 changes: 230 additions & 0 deletions bevy_asset_loader/examples/custom_dynamic_assets_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use bevy::core_pipeline::clear_color::ClearColorConfig;
use bevy::prelude::*;
use bevy::reflect::TypePath;
use bevy::utils::HashMap;
use bevy_asset_loader::prelude::*;
use bevy_common_assets::ron::RonAssetPlugin;

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
RonAssetPlugin::<MyAssetCollection>::new(&["map.ron"]),
))
// We need to make sure that our dynamic asset collections can be loaded from the asset file
.add_state::<MyStates>()
.add_loading_state(
LoadingState::new(MyStates::AssetLoading)
.continue_to_state(MyStates::Next)
.load_collection::<MyAssets>()
.register_dynamic_asset_collection::<MyAssetCollection>()
.with_dynamic_assets_file::<MyAssetCollection>("custom.map.ron"),
)
.add_systems(OnEnter(MyStates::Next), render_stuff)
.run();
}

fn render_stuff(mut commands: Commands, assets: Res<MyAssets>) {
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..Camera3dBundle::default()
});
commands.spawn(PbrBundle {
mesh: assets.cube.clone(),
material: assets.materials.get("tree").unwrap().clone(),
transform: Transform::from_xyz(-1., 0., 1.),
..default()
});
commands.spawn(PbrBundle {
mesh: assets.cube.clone(),
material: assets.materials.get("player").unwrap().clone(),
transform: Transform::from_xyz(1., 0., 1.),
..default()
});
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});

commands.spawn(Camera2dBundle {
camera: Camera {
order: 1,
..default()
},
camera_2d: Camera2d {
clear_color: ClearColorConfig::None,
},
..default()
});
// Combined image as sprite
commands.spawn(SpriteBundle {
texture: assets.combined_image.clone(),
transform: Transform::from_xyz(0.0, 200.0, 0.0),
..default()
});
}

#[derive(AssetCollection, Resource)]
struct MyAssets {
#[asset(key = "combined_image")]
combined_image: Handle<Image>,
#[asset(key = "materials", collection(mapped, typed))]
materials: HashMap<String, Handle<StandardMaterial>>,
#[asset(key = "cube")]
cube: Handle<Mesh>,
}

#[derive(serde::Deserialize, Asset, TypePath)]
pub struct MyAssetCollection(HashMap<String, MyAssetMap>);

impl DynamicAssetCollection for MyAssetCollection {
fn register(&self, dynamic_assets: &mut bevy_asset_loader::dynamic_asset::DynamicAssets) {
for (name, assets) in self.0.clone().into_iter() {
for (k, v) in assets.0.iter() {
dynamic_assets.register_asset(k, Box::new(v.clone()));
}
dynamic_assets.register_asset(name, Box::new(assets.clone()));
}
}
}

#[derive(Debug, serde::Deserialize, Clone)]
pub struct MyAssetMap(HashMap<String, CustomDynamicAsset>);

impl DynamicAsset for MyAssetMap {
fn load(&self, asset_server: &AssetServer) -> Vec<UntypedHandle> {
self.0
.iter()
.map(|(k, v)| v.load(asset_server))
.flatten()
.collect()
}
fn build(&self, world: &mut World) -> Result<DynamicAssetType, anyhow::Error> {
let hash: HashMap<String, DynamicAssetType> = self
.0
.iter()
.map(|(k, v)| (k.to_string(), v.build(world)))
.filter(|(k, v)| v.is_ok())
.map(|(k, v)| (k, v.unwrap()))
.collect();
Ok(DynamicAssetType::Map(hash))
}
}

#[derive(serde::Deserialize, Debug, Clone)]
enum CustomDynamicAsset {
CombinedImage {
bottom_layer: String,
top_layer: String,
},
StandardMaterial {
base_color: [f32; 4],
base_color_texture: String,
},
Cube {
size: f32,
},
}

impl DynamicAsset for CustomDynamicAsset {
// At this point, the content of your dynamic asset file is done loading.
// You should return untyped handles to any assets that need to finish loading for your
// dynamic asset to be ready.
fn load(&self, asset_server: &AssetServer) -> Vec<UntypedHandle> {
match self {
CustomDynamicAsset::CombinedImage {
top_layer,
bottom_layer,
} => vec![
asset_server.load_untyped(bottom_layer).untyped(),
asset_server.load_untyped(top_layer).untyped(),
],
CustomDynamicAsset::StandardMaterial {
base_color_texture, ..
} => vec![asset_server.load_untyped(base_color_texture).untyped()],
CustomDynamicAsset::Cube { .. } => vec![],
}
}

// This method is called when all asset handles returned from `load` are done loading.
// The handles that you return, should also be loaded.
fn build(&self, world: &mut World) -> Result<DynamicAssetType, anyhow::Error> {
let cell = world.cell();
let asset_server = cell
.get_resource::<AssetServer>()
.expect("Failed to get asset server");
match self {
CustomDynamicAsset::CombinedImage {
top_layer,
bottom_layer,
} => {
let mut images = cell
.get_resource_mut::<Assets<Image>>()
.expect("Failed to get image assets");
let first = images
.get(&asset_server.load(top_layer))
.expect("Failed to get first layer");
let second = images
.get(&asset_server.load(bottom_layer))
.expect("Failed to get second layer");
let combined = Image::new(
second.texture_descriptor.size,
second.texture_descriptor.dimension,
second
.data
.iter()
.enumerate()
.map(|(index, data)| {
data.saturating_add(
*first
.data
.get(index)
.expect("Images do not have the same size!"),
)
})
.collect(),
second.texture_descriptor.format,
);

Ok(DynamicAssetType::Single(images.add(combined).untyped()))
}
CustomDynamicAsset::StandardMaterial {
base_color_texture,
base_color,
} => {
let mut materials = cell
.get_resource_mut::<Assets<StandardMaterial>>()
.expect("Failed to get standard material assets");
let color = Color::rgba(base_color[0], base_color[1], base_color[2], base_color[3]);
let image = asset_server.load(base_color_texture);
let mut material = StandardMaterial::from(color);
material.base_color_texture = Some(image);
material.alpha_mode = AlphaMode::Opaque;

Ok(DynamicAssetType::Single(materials.add(material).untyped()))
}
CustomDynamicAsset::Cube { size } => {
let mut meshes = cell
.get_resource_mut::<Assets<Mesh>>()
.expect("Failed to get mesh assets");
let handle = meshes
.add(Mesh::from(shape::Cube { size: *size }))
.untyped();

Ok(DynamicAssetType::Single(handle))
}
}
}
}

#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum MyStates {
#[default]
AssetLoading,
Next,
}
2 changes: 2 additions & 0 deletions bevy_asset_loader/src/dynamic_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub enum DynamicAssetType {
Single(UntypedHandle),
/// Dynamic asset that is defined by multiple handles
Collection(Vec<UntypedHandle>),
/// Dynamic asset that is a map of multiple handles
Map(HashMap<String, DynamicAssetType>),
}

/// Any type implementing this trait can be assigned to asset keys as part of a dynamic
Expand Down
21 changes: 21 additions & 0 deletions bevy_asset_loader_derive/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,27 @@ impl AssetField {
}
folder_map
},
::bevy_asset_loader::prelude::DynamicAssetType::Map(data) => {
let asset_server = world.get_resource::<::bevy::asset::AssetServer>().expect("Cannot get AssetServer");
let mut asset_map = ::bevy::utils::HashMap::default();
for (key, handle) in data {
// match handle {
// ::bevy_asset_loader::prelude::DynamicAssetType::Single(handle) => {
// asset_map.insert(key, #handle);
// }
// ::bevy_asset_loader::prelude::DynamicAssetType::Collection(handles) => {
// asset_map.insert(key, #handles);
// }
// ::bevy_asset_loader::prelude::DynamicAssetType::Map(handle) => {
// asset_map.insret(key, #handle);
// }
// }
if let ::bevy_asset_loader::prelude::DynamicAssetType::Single(handle) = handle {
asset_map.insert(key, #handle);
}
}
asset_map
},
result => panic!("The dynamic asset '{}' cannot be created. The asset collection {} expected it to resolve to `Collection(handle)`, but {asset:?} resolves to {result:?}", #asset_key, #name),
)
}
Expand Down