diff --git a/assets/beach/beach.level.yaml b/assets/beach/beach.level.yaml index ad75b2d2..ef4b3ea0 100644 --- a/assets/beach/beach.level.yaml +++ b/assets/beach/beach.level.yaml @@ -58,3 +58,7 @@ enemies: location: [200, -10, 0] - fighter: *bandit location: [250, -50, 0] + +items: + - item: &bottle /items/bottle/bottle.item.yaml + location: [275, 0, 0] diff --git a/assets/items/bottle/bottle.item.yaml b/assets/items/bottle/bottle.item.yaml new file mode 100644 index 00000000..9f947c8d --- /dev/null +++ b/assets/items/bottle/bottle.item.yaml @@ -0,0 +1,5 @@ +name: Bottle + +image: + image: bottle.png + image_size: [11, 31] diff --git a/assets/items/bottle/bottle.png b/assets/items/bottle/bottle.png new file mode 100644 index 00000000..b7a4ef17 Binary files /dev/null and b/assets/items/bottle/bottle.png differ diff --git a/src/assets.rs b/src/assets.rs index c79a81cc..1318f473 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -20,6 +20,8 @@ pub fn register(app: &mut bevy::prelude::App) { .add_asset_loader(LevelMetaLoader) .add_asset::() .add_asset_loader(FighterLoader) + .add_asset::() + .add_asset_loader(ItemLoader) .add_asset::() .add_asset_loader(EguiFontLoader); } @@ -256,6 +258,38 @@ impl AssetLoader for FighterLoader { } } +pub struct ItemLoader; + +impl AssetLoader for ItemLoader { + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut bevy::asset::LoadContext, + ) -> bevy::utils::BoxedFuture<'a, Result<(), anyhow::Error>> { + Box::pin(async move { + let mut meta: ItemMeta = serde_yaml::from_slice(bytes)?; + trace!(?meta, "Loaded item asset"); + + let self_path = load_context.path(); + let mut dependencies = Vec::new(); + + let image_path = relative_asset_path(self_path, &meta.image.image); + let image_path = AssetPath::new(image_path, None); + let image_handle = load_context.get_handle(image_path.clone()); + dependencies.push(image_path); + meta.image.image_handle = image_handle; + + load_context.set_default_asset(LoadedAsset::new(meta).with_dependencies(dependencies)); + + Ok(()) + }) + } + + fn extensions(&self) -> &[&str] { + &["item.yml", "item.yaml"] + } +} + #[derive(Debug, Clone, TypeUuid)] #[uuid = "da277340-574f-4069-907c-7571b8756200"] pub struct EguiFont(pub egui::FontData); diff --git a/src/item.rs b/src/item.rs index 489b4ffe..6c03e2af 100644 --- a/src/item.rs +++ b/src/item.rs @@ -1,6 +1,8 @@ use bevy::{ math::Vec2, - prelude::{default, AssetServer, Commands, Component, EventReader, Res, Transform}, + prelude::{ + default, AssetServer, Bundle, Commands, Component, EventReader, Handle, Res, Transform, + }, sprite::SpriteBundle, }; use bevy_rapier2d::prelude::*; @@ -10,12 +12,31 @@ use crate::{ attack::Attack, collisions::BodyLayers, consts::{self, ITEM_HEIGHT, ITEM_LAYER, ITEM_WIDTH}, + metadata::{ItemMeta, ItemSpawnMeta}, movement::{MoveInArc, Rotate}, }; #[derive(Component)] pub struct Item; +#[derive(Bundle)] +pub struct ItemSpawnBundle { + item_meta_handle: Handle, + location: Transform, +} + +impl ItemSpawnBundle { + pub fn new(item_spawn_meta: &ItemSpawnMeta) -> Self { + let item_meta_handle = item_spawn_meta.item_handle.clone(); + let location = Transform::from_translation(item_spawn_meta.location); + + Self { + item_meta_handle, + location, + } + } +} + pub struct ThrowItemEvent { pub position: Vec2, pub facing: Facing, diff --git a/src/main.rs b/src/main.rs index 9a81a72a..1b2cbed8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ use audio::*; use camera::*; use collisions::*; use item::{spawn_throwable_items, ThrowItemEvent}; -use metadata::{FighterMeta, GameMeta, LevelMeta}; +use metadata::{FighterMeta, GameMeta, ItemMeta, LevelMeta}; use movement::*; use serde::Deserialize; use state::{State, StatePlugin}; @@ -65,6 +65,7 @@ use crate::{ attack::{attack_cleanup, attack_tick}, config::EngineConfig, input::PlayerAction, + item::ItemSpawnBundle, metadata::Settings, }; @@ -225,6 +226,7 @@ fn main() { ConditionSet::new() .run_in_state(GameState::InGame) .with_system(load_fighters) + .with_system(load_items) .with_system(spawn_throwable_items) .with_system(player_controller) .with_system(y_sort) @@ -362,6 +364,11 @@ fn load_level( commands.spawn_bundle(EnemyBundle::new(enemy)); } + // Spawn the items + for item_spawn_meta in &level.items { + commands.spawn_bundle(ItemSpawnBundle::new(item_spawn_meta)); + } + commands.insert_resource(level.clone()); commands.insert_resource(NextState(GameState::InGame)); } else { @@ -397,6 +404,23 @@ fn hot_reload_level( } } +fn load_items( + mut commands: Commands, + item_spawns: Query<(Entity, &Transform, &Handle), Without>, + item_assets: Res>, + asset_server: Res, +) { + for (entity, location, item_handle) in item_spawns.iter() { + if let Some(item_meta) = item_assets.get(item_handle) { + commands.entity(entity).insert_bundle(SpriteBundle { + texture: asset_server.load(&item_meta.image.image), + transform: *location, + ..default() + }); + } + } +} + /// Load all fighters that have their handles spawned. /// /// Fighters are spawned as "stubs" that only contain a transform, a marker component, and a diff --git a/src/metadata.rs b/src/metadata.rs index 5131e005..d7cec692 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -65,6 +65,8 @@ pub struct LevelMeta { pub players: Vec, #[serde(default)] pub enemies: Vec, + #[serde(default)] + pub items: Vec, pub music: String, #[serde(skip)] pub music_handle: Handle, @@ -88,6 +90,14 @@ pub struct FighterMeta { pub audio: AudioMeta, } +#[derive(TypeUuid, Deserialize, Clone, Debug, Component)] +#[serde(deny_unknown_fields)] +#[uuid = "5e2db270-ec2e-013a-92a8-2cf05d71216b"] +pub struct ItemMeta { + pub name: String, + pub image: ImageMeta, +} + #[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct FighterHudMeta { @@ -124,6 +134,16 @@ pub struct FighterSpawnMeta { pub location: Vec3, } +#[derive(TypeUuid, Deserialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +#[uuid = "f5092550-ec30-013a-92a9-2cf05d71216b"] +pub struct ItemSpawnMeta { + pub item: String, + #[serde(skip)] + pub item_handle: Handle, + pub location: Vec3, +} + #[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct ParallaxMeta {