From 21e2225e1235ba5de5860444c8d3a3f505f86a6d Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Tue, 6 Dec 2022 01:18:30 +0000 Subject: [PATCH] Add music and UI sounds --- ATTRIBUTIONS | 5 + CREDITS.toml | 4 +- Cargo.lock | 16 +- Cargo.toml | 5 +- assets/resources.apack | 4 +- game_core/Cargo.toml | 2 + game_core/src/assets/apack_handler.rs | 29 ++- .../src/assets/asset_types/ldtk_project.rs | 39 +++- game_core/src/assets/mod.rs | 3 +- game_core/src/main.rs | 1 + game_core/src/states/menu_state.rs | 12 +- game_core/src/system/utilities.rs | 15 ++ game_core/src/ui/components/button.rs | 100 +++++++--- game_core/src/ui/utilities.rs | 9 +- game_core/src/ui/widgets/mod.rs | 7 +- game_core/src/ui/widgets/shop_panel.rs | 0 game_core/src/ui/widgets/town_menu.rs | 174 ++++++++++-------- game_core/src/ui/widgets/transit_panel.rs | 113 ++++++++++++ game_core/src/world/debug.rs | 2 +- game_core/src/world/spawning.rs | 62 +++++-- game_core/src/world/utils.rs | 2 +- raw_assets/.gitignore | 1 + raw_assets/manifest.toml | 22 ++- 23 files changed, 487 insertions(+), 140 deletions(-) create mode 100644 ATTRIBUTIONS create mode 100644 game_core/src/ui/widgets/shop_panel.rs create mode 100644 game_core/src/ui/widgets/transit_panel.rs diff --git a/ATTRIBUTIONS b/ATTRIBUTIONS new file mode 100644 index 0000000..5f75402 --- /dev/null +++ b/ATTRIBUTIONS @@ -0,0 +1,5 @@ +The following music was used for this media project: +Music: Fantasy Chamber Adventure by Rafael Krux +Free download: https://filmmusic.io/song/5424-fantasy-chamber-adventure +License (CC BY 4.0): https://filmmusic.io/standard-license +Artist website: https://www.orchestralis.net/ diff --git a/CREDITS.toml b/CREDITS.toml index d34acc3..1234027 100644 --- a/CREDITS.toml +++ b/CREDITS.toml @@ -8,4 +8,6 @@ usage = "Header and title font" name = "EquipmentPro.ttf" author = "Eeve Somepx" website = "https://somepx.itch.io/humble-fonts-free" -usage = "Interface body font" \ No newline at end of file +usage = "Interface body font" + +[[music]] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2d2ed42..a200f81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,17 +367,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bevy_ecs_tilemap" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce7f9fa49f364602ac0901d5f181445529b23fb0623e39e791548f8a6e8c2b5" -dependencies = [ - "bevy", - "log", - "regex", -] - [[package]] name = "bevy_ecs_tilemap" version = "0.9.0" @@ -1601,12 +1590,13 @@ version = "0.1.0" dependencies = [ "anyhow", "bevy", - "bevy_ecs_tilemap 0.9.0 (git+https://github.com/StarArawn/bevy_ecs_tilemap?rev=eb20fcaccdd253ea5bf280cac7ffc5a69b674df2)", + "bevy_ecs_tilemap", "bevy_tweening", "fastrand", "iyes_loopless", "kayak_font", "kayak_ui", + "kira", "ldtk_rust", "log", "micro_asset_io", @@ -2320,7 +2310,7 @@ checksum = "db0f957d47035e81cda7636e2c9e17aa8fb8816a29bd1739935893b605ad4a09" dependencies = [ "anyhow", "bevy", - "bevy_ecs_tilemap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bevy_ecs_tilemap", "serde", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index 1ae6fca..2828020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1.0.87" iyes_loopless = "0.9.1" micro_musicbox = { version = "0.5.0", features = ["mp3"] } -micro_banimate = "0.2.1" +micro_banimate = { version = "0.2.1", features = ["ecs_tilemap"] } kayak_ui = { rev = "220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7", git = "https://github.com/StarArawn/kayak_ui" } kayak_font = { rev = "220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7", git = "https://github.com/StarArawn/kayak_ui.git" } @@ -37,6 +37,9 @@ features = [ "filesystem_watcher" ] +[patch.crates-io] +bevy_ecs_tilemap = { version = "0.9", git = "https://github.com/StarArawn/bevy_ecs_tilemap", rev = "eb20fcaccdd253ea5bf280cac7ffc5a69b674df2" } + [profile.release] debug = 0 opt-level = 3 diff --git a/assets/resources.apack b/assets/resources.apack index 7db455a..d2135af 100644 --- a/assets/resources.apack +++ b/assets/resources.apack @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63fc5d48bdcc9ef1268da7877f399916b45d0463cb3f7a116594220ad5fdfa00 -size 110420 +oid sha256:8d6537a1b832665cd6f32c15a1988fed80b55999aa07cf4b73e8db2ecb02674e +size 1577712 diff --git a/game_core/Cargo.toml b/game_core/Cargo.toml index 8fc6726..a3c6e55 100644 --- a/game_core/Cargo.toml +++ b/game_core/Cargo.toml @@ -21,6 +21,7 @@ micro_musicbox.workspace = true micro_asset_io = { path = "../micro_asset_io" } num-traits = "0.2.15" +kira = { version = "0.7", default-features = false, features = ["cpal", "mp3"] } bevy_ecs_tilemap = { git = "https://github.com/StarArawn/bevy_ecs_tilemap", rev = "eb20fcaccdd253ea5bf280cac7ffc5a69b674df2" } bevy_tweening = "0.6.0" @@ -33,5 +34,6 @@ kayak_font.workspace = true #remote_events = { git = "https://lab.lcr.gr/microhacks/micro-bevy-remote-events.git", rev = "be0c6b43a73e4c5e7ece20797e3d6f59340147b4"} + [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3.58", features = ["Window"] } diff --git a/game_core/src/assets/apack_handler.rs b/game_core/src/assets/apack_handler.rs index 422ad53..d9b7604 100644 --- a/game_core/src/assets/apack_handler.rs +++ b/game_core/src/assets/apack_handler.rs @@ -5,6 +5,7 @@ /// files. Have a peek for curiosity, but try not to learn from this particular file /// use std::ffi::OsStr; +use std::io::Cursor; use std::path::PathBuf; use bevy::asset::Asset; @@ -13,8 +14,10 @@ use bevy::math::vec2; use bevy::prelude::*; use bevy::render::texture::{CompressedImageFormats, ImageType}; use kayak_font::{KayakFont, Sdf}; +use kira::sound::static_sound::{StaticSoundData, StaticSoundSettings}; use ldtk_rust::Project; use micro_asset_io::{APack, APackProcessingComplete}; +use micro_musicbox::prelude::AudioSource; use serde::{Deserialize, Serialize}; use crate::assets::asset_types::ldtk_project::LdtkProject; @@ -96,6 +99,8 @@ pub struct APackManifest { pub fonts: Vec<ManifestFontAsset>, #[serde(default = "Vec::new")] pub ldtk: Vec<ManifestGenericAsset>, + #[serde(default = "Vec::new")] + pub sounds: Vec<ManifestGenericAsset>, } fn in_world<Param: SystemParam + 'static>( @@ -132,7 +137,7 @@ pub fn handle_apack_process_events(world: &mut World) { }; for event in events { - let pack = { + let mut pack = { let mut state: SystemState<ResMut<Assets<APack>>> = SystemState::new(world); let mut packs = state.get_mut(world); if let Some(pack) = packs.get_mut(&event.0) { @@ -311,6 +316,28 @@ pub fn handle_apack_process_events(world: &mut World) { }, ); } + + for sound_entry in &manifest.sounds { + let mut asset = pack + .get_mut(&format!("./{}", &sound_entry.path)) + .expect("Missing asset"); + + let owned = std::mem::take(asset); + let sound = StaticSoundData::from_cursor( + Cursor::new(owned), + StaticSoundSettings::default(), + ) + .unwrap(); + let asset = AudioSource { sound }; + + in_world::<ParamSet<(ResMut<Assets<AudioSource>>, ResMut<AssetHandles>)>>( + world, + move |params| { + let handle = params.p0().add(asset); + params.p1().sounds.insert(sound_entry.name.clone(), handle); + }, + ); + } } log::info!("LOADED AN APACK"); diff --git a/game_core/src/assets/asset_types/ldtk_project.rs b/game_core/src/assets/asset_types/ldtk_project.rs index 7135d2e..f3f7aaf 100644 --- a/game_core/src/assets/asset_types/ldtk_project.rs +++ b/game_core/src/assets/asset_types/ldtk_project.rs @@ -5,7 +5,10 @@ use anyhow::Error; use bevy::asset::{AssetEvent, AssetLoader, Assets, BoxedFuture, LoadContext, LoadedAsset}; use bevy::prelude::{EventReader, Res, ResMut, Resource}; use bevy::reflect::TypeUuid; -use ldtk_rust::{Level, Project}; +use ldtk_rust::{Level, Project, TilesetDefinition}; +use serde_json::Value; + +use crate::system::utilities::SerdeClone; #[derive(TypeUuid)] #[uuid = "eb97c508-61ea-11ed-abd3-db91dd0a6e8b"] @@ -61,20 +64,46 @@ impl DerefMut for LevelIndex { } } +#[derive(Default)] +pub struct TileMetadata(pub HashMap<i64, Value>); + +#[derive(Resource, Default)] +pub struct TilesetIndex(pub HashMap<String, TileMetadata>); +impl Deref for TilesetIndex { + type Target = HashMap<String, TileMetadata>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for TilesetIndex { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + pub fn handle_ldtk_project_events( mut events: EventReader<AssetEvent<LdtkProject>>, assets: Res<Assets<LdtkProject>>, mut level_index: ResMut<LevelIndex>, + mut tilset_index: ResMut<TilesetIndex>, ) { for event in events.iter() { match event { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { if let Some(LdtkProject(project)) = assets.get(handle) { for level in &project.levels { - level_index.insert( - level.identifier.clone(), - serde_json::from_value(serde_json::to_value(level).unwrap()).unwrap(), - ); + level_index.insert(level.identifier.clone(), level.serde_clone()); + } + + for tileset in &project.defs.tilesets { + let mut tile_meta = HashMap::new(); + for custom in &tileset.custom_data { + tile_meta.insert( + custom.tile_id, + serde_json::from_str(&*custom.data).unwrap(), + ); + } + tilset_index.insert(tileset.identifier.clone(), TileMetadata(tile_meta)); } } } diff --git a/game_core/src/assets/mod.rs b/game_core/src/assets/mod.rs index e544371..33fdbf7 100644 --- a/game_core/src/assets/mod.rs +++ b/game_core/src/assets/mod.rs @@ -4,7 +4,7 @@ mod loader; mod resources; mod startup; -pub use asset_types::ldtk_project::{LdtkLoader, LdtkProject, LevelIndex}; +pub use asset_types::ldtk_project::{LdtkLoader, LdtkProject, LevelIndex, TilesetIndex}; use bevy::app::{App, Plugin}; use bevy::prelude::AddAsset; use iyes_loopless::condition::ConditionSet; @@ -21,6 +21,7 @@ impl Plugin for AssetsPlugin { fn build(&self, app: &mut App) { app.init_resource::<AssetHandles>() .init_resource::<LevelIndex>() + .init_resource::<TilesetIndex>() .add_asset::<LdtkProject>() .add_asset_loader(LdtkLoader) .add_enter_system(AppState::Preload, startup::start_preload_resources) diff --git a/game_core/src/main.rs b/game_core/src/main.rs index 92cafa4..a8a988f 100644 --- a/game_core/src/main.rs +++ b/game_core/src/main.rs @@ -21,6 +21,7 @@ fn main() { .add_plugin(game_core::world::WorldPlugin) .add_plugin(TilemapPlugin) .add_plugin(game_core::graphics::GraphicsPlugin) + .add_plugins(micro_banimate::BanimatePluginGroup) .add_plugins(AdventUIPlugins) .run(); } diff --git a/game_core/src/states/menu_state.rs b/game_core/src/states/menu_state.rs index 40ea550..f6a1012 100644 --- a/game_core/src/states/menu_state.rs +++ b/game_core/src/states/menu_state.rs @@ -4,6 +4,7 @@ use bevy::prelude::*; use bevy_tweening::lens::TextColorLens; use bevy_tweening::{Animator, EaseFunction, RepeatCount, RepeatStrategy, Tween}; use iyes_loopless::state::NextState; +use micro_musicbox::prelude::{AudioEasing, AudioTween, MusicBox}; use crate::assets::AssetHandles; use crate::system::flow::AppState; @@ -11,7 +12,16 @@ use crate::system::flow::AppState; #[derive(Component)] pub struct MenuStateEntity; -pub fn spawn_menu_entities(mut commands: Commands, assets: Res<AssetHandles>) { +pub fn spawn_menu_entities( + mut commands: Commands, + assets: Res<AssetHandles>, + mut musicbox: MusicBox<AssetHandles>, +) { + musicbox.fade_in_music( + "bgm", + AudioTween::new(Duration::from_secs(2), AudioEasing::Linear), + ); + commands.spawn(( SpriteBundle { texture: assets.image("menu_background"), diff --git a/game_core/src/system/utilities.rs b/game_core/src/system/utilities.rs index 1f4517c..c26cbbf 100644 --- a/game_core/src/system/utilities.rs +++ b/game_core/src/system/utilities.rs @@ -2,6 +2,8 @@ use std::ops::{Deref, DerefMut}; use bevy::prelude::UVec2; use num_traits::AsPrimitive; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; #[inline] pub fn f32_max(a: f32, b: f32) -> f32 { @@ -128,3 +130,16 @@ impl Indexer { self.height } } + +pub trait SerdeClone { + fn serde_clone(&self) -> Self; +} + +impl<T> SerdeClone for T +where + T: Serialize + DeserializeOwned, +{ + fn serde_clone(&self) -> Self { + serde_json::from_value(serde_json::to_value(&self).unwrap()).unwrap() + } +} diff --git a/game_core/src/ui/components/button.rs b/game_core/src/ui/components/button.rs index 58bc3a6..ba2adbb 100644 --- a/game_core/src/ui/components/button.rs +++ b/game_core/src/ui/components/button.rs @@ -1,6 +1,7 @@ use bevy::prelude::*; use kayak_ui::prelude::*; -use kayak_ui::widgets::{NinePatch, NinePatchBundle, TextProps, TextWidgetBundle}; +use kayak_ui::widgets::{ElementBundle, NinePatch, NinePatchBundle, TextProps, TextWidgetBundle}; +use micro_musicbox::prelude::MusicBox; use num_traits::AsPrimitive; use crate::assets::AssetHandles; @@ -73,17 +74,27 @@ pub fn render_button_widget( mut params: ParamSet<( Query<&ButtonWidgetProps>, Query<&mut ButtonWidgetState>, + MusicBox<AssetHandles>, )>| { let widget_props = match params.p0().get(entity) { Ok(p) => p.clone(), Err(..) => return (event_dispatcher_context, event), }; + let mut should_click = false; + let mut should_proing = false; + if let Ok(mut state) = params.p1().get_mut(state_entity) { match &event.event_type { - EventType::Hover(..) | EventType::MouseIn(..) => { + EventType::Hover(..) => { + if !widget_props.is_disabled { + state.is_hovered = true; + } + } + EventType::MouseIn(..) => { if !widget_props.is_disabled { state.is_hovered = true; + should_click = true; } } EventType::MouseOut(..) => { @@ -102,12 +113,21 @@ pub fn render_button_widget( if widget_props.is_disabled { event.prevent_default(); event.stop_propagation(); + } else { + should_proing = true; } } _ => {} } } + if should_click { + params.p2().play_sfx("ui_ping"); + } + if should_proing { + params.p2().play_sfx("ui_confirm"); + } + (event_dispatcher_context, event) }, ); @@ -139,17 +159,17 @@ pub fn render_button_widget( let sizing = if state.is_pressed { KStyle { top: px(11.0), - right: stretch(1.0), bottom: px(11.0), - left: stretch(1.0), + right: px(4.0), + left: px(4.0), ..Default::default() } } else { KStyle { top: px(8.0), - right: stretch(1.0), bottom: px(14.0), - left: stretch(1.0), + right: px(4.0), + left: px(4.0), ..Default::default() } }; @@ -159,15 +179,23 @@ pub fn render_button_widget( min_height: px(32.0), min_width: px(32.0), height: px(button_height), - padding: value(Edge::all(Units::Stretch(0.0))), ..Default::default() } - .with_style(style) + .with_style(style.clone()) + .with_style(KStyle { + padding_bottom: stretch(0.0), + padding_top: stretch(0.0), + padding_left: stretch(0.0), + padding_right: stretch(0.0), + ..Default::default() + }) .into(); let ninepatch_styles = KStyle { layout_type: value(LayoutType::Row), col_between: px(15.0), + padding_left: stretch(1.0), + padding_right: stretch(1.0), ..Default::default() }; @@ -178,17 +206,38 @@ pub fn render_button_widget( .with_style(if state.is_pressed { KStyle { top: px(6.0), - right: stretch(1.0), bottom: px(16.0), - left: stretch(1.0), + // right: px(4.0), + // left: px(4.0), ..Default::default() } } else { KStyle { top: px(3.0), - right: stretch(1.0), bottom: px(19.0), - left: stretch(1.0), + // right: px(4.0), + // left: px(4.0), + ..Default::default() + } + }); + + let icon_wrapper_style = KStyle { + ..Default::default() + } + .with_style(if state.is_pressed { + KStyle { + padding_top: px(11.0), + padding_bottom: px(11.0), + // padding_right: px(4.0), + // padding_left: px(4.0), + ..Default::default() + } + } else { + KStyle { + padding_top: px(8.0), + padding_bottom: px(14.0), + // padding_right: px(4.0), + // padding_left: px(4.0), ..Default::default() } }); @@ -201,11 +250,15 @@ pub fn render_button_widget( > { if !props.left_icon.is_none() { constructor!( - <InsetIconWidget - styles={sizing.clone()} - - props={InsetIconProps { image: props.left_icon.clone(), size: props.font_size }} - /> + <ElementBundle styles={icon_wrapper_style.clone()}> + <InsetIconWidget + styles={sizing.clone()} + props={InsetIconProps { + image: props.left_icon.clone(), + size: props.font_size + }} + /> + </ElementBundle> ); }} <TextWidgetBundle @@ -222,10 +275,15 @@ pub fn render_button_widget( { if !props.right_icon.is_none() { constructor!( - <InsetIconWidget - styles={sizing.clone()} - props={InsetIconProps { image: props.right_icon.clone(), size: props.font_size }} - /> + <ElementBundle styles={icon_wrapper_style.clone()}> + <InsetIconWidget + styles={sizing.clone()} + props={InsetIconProps { + image: props.right_icon.clone(), + size: props.font_size + }} + /> + </ElementBundle> ); }} </NinePatchBundle> diff --git a/game_core/src/ui/utilities.rs b/game_core/src/ui/utilities.rs index 5dfb327..cc2ad01 100644 --- a/game_core/src/ui/utilities.rs +++ b/game_core/src/ui/utilities.rs @@ -191,10 +191,17 @@ pub mod context { register_widget_with_resource!( widget_context, TownMenuPanelProps, - EmptyState, + TownMenuPanelState, UITravelInfo, render_town_menu_panel ); + register_widget_with_resource!( + widget_context, + TransitPanelProps, + EmptyState, + UITravelInfo, + render_transit_panel + ); } } } diff --git a/game_core/src/ui/widgets/mod.rs b/game_core/src/ui/widgets/mod.rs index 7acc2b1..7f5cc2e 100644 --- a/game_core/src/ui/widgets/mod.rs +++ b/game_core/src/ui/widgets/mod.rs @@ -1,3 +1,8 @@ +mod shop_panel; mod town_menu; +mod transit_panel; -pub use town_menu::{render_town_menu_panel, TownMenuPanel, TownMenuPanelProps}; +pub use town_menu::{ + render_town_menu_panel, TownMenuPanel, TownMenuPanelProps, TownMenuPanelState, +}; +pub use transit_panel::{render_transit_panel, TransitPanel, TransitPanelProps}; diff --git a/game_core/src/ui/widgets/shop_panel.rs b/game_core/src/ui/widgets/shop_panel.rs new file mode 100644 index 0000000..e69de29 diff --git a/game_core/src/ui/widgets/town_menu.rs b/game_core/src/ui/widgets/town_menu.rs index b7e75c1..f015185 100644 --- a/game_core/src/ui/widgets/town_menu.rs +++ b/game_core/src/ui/widgets/town_menu.rs @@ -14,54 +14,32 @@ use crate::ui::widgets::*; use crate::world::{CurrentResidence, MapQuery, TownPaths}; use crate::{basic_widget, empty_props, on_button_click}; -pub fn transit_button_factory(target: String) -> OnEvent { - let target = target.clone(); - on_button_click!( - ParamSet<( - Commands, - Res<TownPaths>, - Query<(Entity, &CurrentResidence), With<Player>>, - MapQuery, - )>, - |mut params: ParamSet<( - Commands, - Res<TownPaths>, - Query<(Entity, &CurrentResidence), With<Player>>, - MapQuery, - )>| { - let target = target.clone(); - let (entity, current) = { - match params.p2().get_single() { - Ok((entity, current)) => (entity.clone(), (current.get_location()).clone()), - _ => return, - } - }; - - let places = match params.p1().routes.get(¤t) { - Some(places) => places.clone(), - None => return, - }; - - let bundle = match params.p3().get_active_level() { - Some(level) => places.create_route_bundle_for(target, level).unwrap(), - None => return, - }; +empty_props!(TownMenuPanelProps); +basic_widget!(TownMenuPanelProps => TownMenuPanel); - params.p0().entity(entity).insert(bundle); - } - ) +#[derive(Debug, Ord, PartialOrd, PartialEq, Eq, Copy, Clone, Default)] +pub enum TownMenuTab { + #[default] + Travel, + Merchant, + Tavern, } -empty_props!(TownMenuPanelProps); -basic_widget!(TownMenuPanelProps => TownMenuPanel); +#[derive(Component, Clone, PartialEq, Default)] +pub struct TownMenuPanelState { + pub tab: TownMenuTab, +} pub fn render_town_menu_panel( In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, + state_query: Query<&TownMenuPanelState>, ui_data: Res<UITravelInfo>, places: Res<TownPaths>, ) -> bool { let parent_id = Some(entity); + let state_entity = + widget_context.use_state(&mut commands, entity, TownMenuPanelState::default()); let distance_style = KStyle { position_type: value(KPositionType::SelfDirected), @@ -89,7 +67,31 @@ pub fn render_town_menu_panel( ..Default::default() }; - if let Some(ref place) = ui_data.current_town { + let click_tab_travel = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query< + &mut TownMenuPanelState, + >| { + if let Ok(mut state) = q.get_mut(state_entity) { + state.tab = TownMenuTab::Travel; + } + }); + let click_tab_merchant = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query< + &mut TownMenuPanelState, + >| { + if let Ok(mut state) = q.get_mut(state_entity) { + state.tab = TownMenuTab::Merchant; + } + }); + let click_tab_tavern = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query< + &mut TownMenuPanelState, + >| { + if let Ok(mut state) = q.get_mut(state_entity) { + state.tab = TownMenuTab::Tavern; + } + }); + + if let (Some(ref place), Some(state)) = + (&ui_data.current_town, state_query.get(state_entity).ok()) + { rsx! { <PanelWidget styles={panel_style} @@ -112,43 +114,69 @@ pub fn render_town_menu_panel( <VDividerWidget props={VDividerWidgetProps { height: 4.0, padding: 5.0, color: Color::rgb(0.52, 0.369, 0.18)}} /> - <TextWidgetBundle - text={TextProps { - content: format!("Set off for:"), - size: 32.0, - ..Default::default() - }} - styles={KStyle { - color: value(Color::BLACK), - padding: edge_px(20.0), - left: stretch(1.0), - right: stretch(1.0), - ..Default::default() - }} - /> + <ElementBundle styles={KStyle { + layout_type: value(LayoutType::Row), + padding_top: stretch(1.0), + padding_bottom: stretch(1.0), + padding_left: stretch(1.0), + padding_right: stretch(1.0), + height: px(60.0), + col_between: px(15.0), + ..Default::default() + }}> + <ButtonWidget + styles={KStyle { + width: px(225.0), + padding_left: px(30.0), + padding_right: px(20.0), + ..Default::default() + }} + props={ButtonWidgetProps { + left_icon: IconContent::Atlas(String::from("icons"), 4), + ..ButtonWidgetProps::text("Travel", 28.0) + }} + on_event={click_tab_travel} + /> + <ButtonWidget + styles={KStyle { + width: px(260.0), + padding_left: px(30.0), + padding_right: px(20.0), + ..Default::default() + }} + props={ButtonWidgetProps { + left_icon: IconContent::Atlas(String::from("icons"), 9), + ..ButtonWidgetProps::text("Merchant", 28.0) + }} + on_event={click_tab_merchant} + /> + <ButtonWidget + styles={KStyle { + width: px(225.0), + padding_left: px(30.0), + padding_right: px(20.0), + ..Default::default() + }} + props={ButtonWidgetProps { + left_icon: IconContent::Atlas(String::from("icons"), 11), + ..ButtonWidgetProps::text("Tavern", 28.0) + }} + on_event={click_tab_tavern} + /> + </ElementBundle> + + <VDividerWidget props={VDividerWidgetProps { height: 4.0, padding: 5.0, color: Color::rgb(0.52, 0.369, 0.18)}} /> + { - for (place, distance) in ui_data.travel_options.iter() { - constructor! { - <ButtonWidget - styles={ - KStyle { - left: stretch(1.0), - right: stretch(1.0), - width: pct(70.0), - min_width: px(300.0), - max_width: px(600.0), - bottom: px(10.0), - ..Default::default() - } - } - props={ButtonWidgetProps { - left_icon: IconContent::Atlas(String::from("characters"), 0), - ..ButtonWidgetProps::text(format!("{}: {:.2}KM", &place, distance), 28.0) - }} - on_event={transit_button_factory(place.clone())} - /> - } + match state.tab { + TownMenuTab::Travel => { + constructor! { + <TransitPanel /> + } + }, + TownMenuTab::Merchant => {} + TownMenuTab::Tavern => {} } } </PanelWidget> diff --git a/game_core/src/ui/widgets/transit_panel.rs b/game_core/src/ui/widgets/transit_panel.rs new file mode 100644 index 0000000..6e3571e --- /dev/null +++ b/game_core/src/ui/widgets/transit_panel.rs @@ -0,0 +1,113 @@ +use bevy::prelude::*; +use kayak_ui::prelude::*; +use kayak_ui::widgets::{ + ElementBundle, ScrollBoxBundle, ScrollBoxProps, ScrollContextProviderBundle, TextProps, + TextWidgetBundle, +}; + +use crate::assets::AssetHandles; +use crate::states::Player; +use crate::ui::components::*; +use crate::ui::prelude::*; +use crate::ui::sync::UITravelInfo; +use crate::ui::widgets::*; +use crate::world::{CurrentResidence, MapQuery, TownPaths}; +use crate::{basic_widget, empty_props, on_button_click}; + +empty_props!(TransitPanelProps); +basic_widget!(TransitPanelProps => TransitPanel); + +pub fn transit_button_factory(target: String) -> OnEvent { + let target = target.clone(); + on_button_click!( + ParamSet<( + Commands, + Res<TownPaths>, + Query<(Entity, &CurrentResidence), With<Player>>, + MapQuery, + )>, + |mut params: ParamSet<( + Commands, + Res<TownPaths>, + Query<(Entity, &CurrentResidence), With<Player>>, + MapQuery, + )>| { + let target = target.clone(); + let (entity, current) = { + match params.p2().get_single() { + Ok((entity, current)) => (entity.clone(), (current.get_location()).clone()), + _ => return, + } + }; + + let places = match params.p1().routes.get(¤t) { + Some(places) => places.clone(), + None => return, + }; + + let bundle = match params.p3().get_active_level() { + Some(level) => places.create_route_bundle_for(target, level).unwrap(), + None => return, + }; + + params.p0().entity(entity).insert(bundle); + } + ) +} + +pub fn render_transit_panel( + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, + mut commands: Commands, + ui_data: Res<UITravelInfo>, +) -> bool { + let parent_id = Some(entity); + + rsx! { + <ElementBundle> + <TextWidgetBundle + text={TextProps { + content: format!("Set off for:"), + size: 32.0, + ..Default::default() + }} + styles={KStyle { + color: value(Color::BLACK), + padding: edge_px(20.0), + bottom: px(15.0), + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }} + /> + <ScrollContextProviderBundle> + <ScrollBoxBundle> + { + for (place, distance) in ui_data.travel_options.iter() { + constructor! { + <ButtonWidget + styles={ + KStyle { + left: stretch(1.0), + right: stretch(1.0), + width: pct(70.0), + min_width: px(300.0), + max_width: px(600.0), + bottom: px(10.0), + padding_left: px(20.0), + padding_right: px(20.0), + ..Default::default() + } + } + props={ButtonWidgetProps::text(format!("{}: {:.2}KM", &place, distance), 28.0)} + on_event={transit_button_factory(place.clone())} + /> + } + } + } + </ScrollBoxBundle> + </ScrollContextProviderBundle> + </ElementBundle> + } + + true +} diff --git a/game_core/src/world/debug.rs b/game_core/src/world/debug.rs index 97c9eae..b3d9637 100644 --- a/game_core/src/world/debug.rs +++ b/game_core/src/world/debug.rs @@ -21,7 +21,7 @@ pub fn create_tombstones( commands.spawn(( SpriteSheetBundle { transform: Transform::from_translation(point.extend(900.0)), - texture_atlas: assets.atlas("characters"), + texture_atlas: assets.atlas("icons"), sprite: TextureAtlasSprite::new(1), ..Default::default() }, diff --git a/game_core/src/world/spawning.rs b/game_core/src/world/spawning.rs index 1c94ea2..20c7adf 100644 --- a/game_core/src/world/spawning.rs +++ b/game_core/src/world/spawning.rs @@ -1,8 +1,10 @@ use bevy::prelude::*; use bevy_ecs_tilemap::prelude::*; +use micro_banimate::definitions::SimpleAnimationBundle; use num_traits::AsPrimitive; +use serde_json::Value; -use crate::assets::{AssetHandles, LdtkProject, LevelIndex}; +use crate::assets::{AssetHandles, LdtkProject, LevelIndex, TilesetIndex}; use crate::states::Player; use crate::system::camera::ChaseCam; use crate::world::towns::{CurrentResidence, TownPaths}; @@ -15,12 +17,17 @@ pub fn spawn_world_data( assets: Res<AssetHandles>, projects: Res<Assets<LdtkProject>>, level_index: Res<LevelIndex>, + tileset_index: Res<TilesetIndex>, mut last_spawned_level: Local<String>, ) { let mut active_level = match active_level { Some(l) => l, None => return, }; + let mut tileset = match tileset_index.get(&String::from("Overworld")) { + Some(l) => l, + None => return, + }; if *last_spawned_level == active_level.map && !active_level.dirty { return; @@ -62,14 +69,44 @@ pub fn spawn_world_data( tile_pos.y = map_tile_height as u32 - tile_pos.y - 1; - let tile_entity = commands - .spawn(TileBundle { - position: tile_pos, - tilemap_id: TilemapId(map_entity), - texture_index: TileTextureIndex(tile.tile.t as u32), - ..Default::default() - }) - .id(); + let tile_entity = if let Some(tile_entity) = tileset.0.get(&tile.tile.t) { + match tile_entity.get("animations") { + Some(Value::Array(frames)) => commands + .spawn(( + TileBundle { + position: tile_pos, + tilemap_id: TilemapId(map_entity), + texture_index: TileTextureIndex(tile.tile.t as u32), + ..Default::default() + }, + SimpleAnimationBundle::new( + frames + .iter() + .map(|val| val.as_u64().unwrap() as usize) + .collect(), + 0.5, + ), + )) + .id(), + _ => commands + .spawn(TileBundle { + position: tile_pos, + tilemap_id: TilemapId(map_entity), + texture_index: TileTextureIndex(tile.tile.t as u32), + ..Default::default() + }) + .id(), + } + } else { + commands + .spawn(TileBundle { + position: tile_pos, + tilemap_id: TilemapId(map_entity), + texture_index: TileTextureIndex(tile.tile.t as u32), + ..Default::default() + }) + .id() + }; storage.set(&tile_pos, tile_entity); }); @@ -112,10 +149,7 @@ pub fn spawn_world_data( }); let trade_routes = TownPaths::from(MapQuery::get_entities_of(level)); - let random_start = trade_routes - .routes - .values() - .nth(fastrand::usize(0..trade_routes.routes.len())); + let random_start = trade_routes.routes.get(&String::from("The Royal Lampoon")); if let Some(start) = random_start { if let Some(route) = start.routes.values().next() { @@ -128,7 +162,7 @@ pub fn spawn_world_data( level.px_hei as f32 - grid_to_px(point.tile_y), 400.0, ), - texture_atlas: assets.atlas("characters"), + texture_atlas: assets.atlas("icons"), sprite: TextureAtlasSprite { index: 0, ..Default::default() diff --git a/game_core/src/world/utils.rs b/game_core/src/world/utils.rs index 63abfb2..ccf8f7f 100644 --- a/game_core/src/world/utils.rs +++ b/game_core/src/world/utils.rs @@ -10,7 +10,7 @@ pub fn px_to_grid<T: AsPrimitive<i64>>(t: T) -> i64 { } pub fn grid_to_px<T: AsPrimitive<f32>>(t: T) -> f32 { - t.as_() * TILE_SCALE_F32 + (TILE_SCALE_F32 / 2.0) + t.as_() * TILE_SCALE_F32 // + (TILE_SCALE_F32 / 2.0) } pub fn entity_to_worldspace(level_height: i64, entity: &EntityInstance) -> (f32, f32) { diff --git a/raw_assets/.gitignore b/raw_assets/.gitignore index 6d9884b..8443973 100644 --- a/raw_assets/.gitignore +++ b/raw_assets/.gitignore @@ -3,6 +3,7 @@ fonts/ sprites/ ldtk/ ui/ +sound/ !.gitignore !manifest.toml \ No newline at end of file diff --git a/raw_assets/manifest.toml b/raw_assets/manifest.toml index c2b8e6e..41bb262 100644 --- a/raw_assets/manifest.toml +++ b/raw_assets/manifest.toml @@ -4,8 +4,8 @@ name = "overworld" tiles = { size = 4, columns = 32, rows = 64 } [[spritesheets]] -path = "sprites/characters.png" -name = "characters" +path = "sprites/icons.png" +name = "icons" tiles = { size = 8, columns = 16, rows = 16 } [[images]] @@ -57,4 +57,20 @@ msdf = "fonts/EquipmentPro.kayak_font" [[ldtk]] path = "ldtk/overworld_maps.ldtk" -name = "overworld" \ No newline at end of file +name = "overworld" + +[[sounds]] +path = "sound/ui_click.mp3" +name = "ui_click" + +[[sounds]] +path = "sound/ui_ping.mp3" +name = "ui_ping" + +[[sounds]] +path = "sound/ui_confirm.mp3" +name = "ui_confirm" + +[[sounds]] +path = "sound/fantasy-chamber-adventure-loop.mp3" +name = "bgm" \ No newline at end of file -- GitLab