diff --git a/CHANGELOG.md b/CHANGELOG.md index e077e53..e10d3af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- bevy_common_assets allowing certain data to be loaded from asset files +- Allow window mode and master volume to be set via config file + ### Changed - Gravitable component added allowing dynamic_orbit system to be applied to any entity +- Load credits from asset file ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 82a1eff..5cab07f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,18 @@ dependencies = [ "rodio", ] +[[package]] +name = "bevy_common_assets" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e5659f20aeaa1703e76d87c62d66f92aaa56e431fbed71bb38345b576aa6f0" +dependencies = [ + "anyhow", + "bevy", + "ron", + "serde", +] + [[package]] name = "bevy_core" version = "0.11.3" @@ -3497,18 +3509,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -4012,11 +4024,13 @@ dependencies = [ "bevy", "bevy-inspector-egui", "bevy_asset_loader", + "bevy_common_assets", "bevy_rapier2d", "bevy_spatial", "bevy_tiling_background", "image", "leafwing-input-manager", + "serde", "winit", ] diff --git a/Cargo.toml b/Cargo.toml index d9bc2a0..1685c44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,13 @@ edition = "2021" bevy = "0.11.3" bevy-inspector-egui = "0.20.0" bevy_asset_loader = { version = "0.17.0", features = ["2d"] } +bevy_common_assets = { version = "0.7.0", features = ["ron"] } bevy_rapier2d = "0.22.0" bevy_spatial = "0.6.0" bevy_tiling_background = { git = "https://github.com/rparrett/bevy_tiling_background.git", branch = "tex-typo" } image = "0.24.7" leafwing-input-manager = "0.10.0" +serde = "1.0.190" winit = "0.28.7" # Enable a small amount of optimization in debug mode diff --git a/assets/VerseSquircle-256.png b/assets/images/VerseSquircle-256.png similarity index 100% rename from assets/VerseSquircle-256.png rename to assets/images/VerseSquircle-256.png diff --git a/assets/grey_arrowUpWhite.png b/assets/images/grey_arrowUpWhite.png similarity index 100% rename from assets/grey_arrowUpWhite.png rename to assets/images/grey_arrowUpWhite.png diff --git a/assets/verse.png b/assets/images/verse.png similarity index 100% rename from assets/verse.png rename to assets/images/verse.png diff --git a/assets/verse.config.ron b/assets/verse.config.ron new file mode 100644 index 0000000..3c3e782 --- /dev/null +++ b/assets/verse.config.ron @@ -0,0 +1,4 @@ +GameConfig( + window_mode: Windowed, // Windowed, Fullscreen, BorderlessFullscreen, SizedFullscreen + master_volume: 1.0, +) diff --git a/assets/verse.credits.ron b/assets/verse.credits.ron new file mode 100644 index 0000000..c15d670 --- /dev/null +++ b/assets/verse.credits.ron @@ -0,0 +1,67 @@ +([ + ( + section_title: "Developed by", + credits: [(credit_title: "Thom Bruce")] + ), + ( + section_title: "Title Font", + credits: [( + credit_title: "Edge of the Galaxy by Quinn Davis Type", + credit_meta: Some("FontSpace, Public Domain") + )] + ), + ( + section_title: "Art", + credits: [ + ( + credit_title: "Space Shooter Redux by Kenney", + credit_meta: Some("kenney.nl, CC0") + ), + ( + credit_title: "Ship Mixer by Kenney", + credit_meta: Some("kenney.nl") + ), + ( + credit_title: "Pixel Planets by Deep-Fold", + credit_meta: Some("itch.io, MIT") + ), + ( + credit_title: "Xolonium Typeface by Severin Meyer", + credit_meta: Some("Font Library, OFL (SIL Open Font License)") + ), + ] + ), + ( + section_title: "Music", + credits: [ + ( + credit_title: "Lightspeed by Beat Mekanik", + credit_meta: Some("Free Music Archive, CC BY") + ), + ( + credit_title: "Space Dust by Kirk Osamayo", + credit_meta: Some("Free Music Archive, CC BY") + ), + ] + ), + ( + section_title: "Audio", + credits: [ + ( + credit_title: "Impact Sounds by Kenney", + credit_meta: Some("kenney.nl, CC0") + ), + ] + ), + // ( + // section_title: "Become a Supporter", + // credits: [ + // ( + // credit_title: "ko-fi.com/thombruce" + // ), + // ( + // credit_title: "patreon.com/thombruce" + // ), + // ] + // ), +]) diff --git a/src/core/resources/assets.rs b/src/core/resources/assets.rs index 2162f1b..c87b8ba 100644 --- a/src/core/resources/assets.rs +++ b/src/core/resources/assets.rs @@ -1,6 +1,19 @@ use bevy::prelude::*; use bevy_asset_loader::prelude::*; +use crate::ui::menus::credits::Credits; + +use super::config::GameConfig; + +#[derive(AssetCollection, Resource)] +pub struct DataAssets { + #[asset(path = "verse.config.ron")] + pub config: Handle, + + #[asset(path = "verse.credits.ron")] + pub credits: Handle, +} + #[derive(AssetCollection, Resource)] pub struct SpriteAssets { #[asset(path = "space/ships/player.png")] @@ -72,6 +85,6 @@ pub struct UiAssets { #[asset(path = "fonts/Xolonium/Xolonium-Bold.ttf")] pub font: Handle, - #[asset(path = "verse.png")] + #[asset(path = "images/verse.png")] pub title: Handle, } diff --git a/src/core/resources/config.rs b/src/core/resources/config.rs new file mode 100644 index 0000000..2d17a09 --- /dev/null +++ b/src/core/resources/config.rs @@ -0,0 +1,38 @@ +use bevy::prelude::*; +use bevy::reflect::{TypePath, TypeUuid}; +use bevy::window::WindowMode; +use bevy_common_assets::ron::RonAssetPlugin; + +use super::{assets::DataAssets, state::GameState}; + +pub struct ConfigPlugin; +impl Plugin for ConfigPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(RonAssetPlugin::::new(&["config.ron"])); + + app.insert_resource(GameConfig { + window_mode: WindowMode::Fullscreen, + master_volume: 1.0, + }); + + app.add_systems(OnExit(GameState::Loading), load_config); + } +} + +#[derive(serde::Deserialize, TypeUuid, TypePath, Resource)] +#[uuid = "bdb624ed-62bc-447f-9f89-f361ed58748c"] +pub struct GameConfig { + pub(crate) window_mode: WindowMode, + pub(crate) master_volume: f32, +} + +fn load_config( + data: Res, + mut configs: ResMut>, + mut game_config: ResMut, +) { + if let Some(config) = configs.remove(data.config.id()) { + game_config.window_mode = config.window_mode; + game_config.master_volume = config.master_volume; + } +} diff --git a/src/core/resources/mod.rs b/src/core/resources/mod.rs index 87960ac..47a9ba1 100644 --- a/src/core/resources/mod.rs +++ b/src/core/resources/mod.rs @@ -1,4 +1,5 @@ pub mod assets; +pub mod config; pub mod despawn_timer; pub mod game_time; pub mod state; diff --git a/src/core/resources/state.rs b/src/core/resources/state.rs index 6460536..f364f4f 100644 --- a/src/core/resources/state.rs +++ b/src/core/resources/state.rs @@ -55,7 +55,7 @@ fn game_setup( source: audios.ambience.clone(), settings: PlaybackSettings { mode: PlaybackMode::Loop, - volume: Volume::new_absolute(0.5), + volume: Volume::new_relative(0.5), ..default() }, }, diff --git a/src/main.rs b/src/main.rs index 9cd72be..64a596f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #[allow(unused_imports)] use bevy::{ + audio::{AudioPlugin, VolumeLevel}, prelude::*, window::{Cursor, PrimaryWindow, WindowMode}, winit::WinitWindows, @@ -19,7 +20,8 @@ mod world; use crate::{ core::resources::{ - assets::{AudioAssets, SpriteAssets, UiAssets}, + assets::{AudioAssets, DataAssets, SpriteAssets, UiAssets}, + config::{ConfigPlugin, GameConfig}, state::GameState, }, core::CorePlugin, @@ -53,7 +55,7 @@ fn main() { .set(ImagePlugin::default_nearest()), ); - app.add_plugins((CorePlugin, ShipsPlugin, WorldPlugin, UiPlugin)); + app.add_plugins((ConfigPlugin, CorePlugin, ShipsPlugin, WorldPlugin, UiPlugin)); #[cfg(debug_assertions)] app.add_plugins(( @@ -66,7 +68,8 @@ fn main() { ) .add_collection_to_loading_state::<_, SpriteAssets>(GameState::Loading) .add_collection_to_loading_state::<_, AudioAssets>(GameState::Loading) - .add_collection_to_loading_state::<_, UiAssets>(GameState::Loading); + .add_collection_to_loading_state::<_, UiAssets>(GameState::Loading) + .add_collection_to_loading_state::<_, DataAssets>(GameState::Loading); app.insert_resource(ClearColor(Color::rgb(0., 0., 0.))); @@ -84,9 +87,13 @@ fn main() { } /// The setup function -fn setup() { - // Good place to put window setup configs, like whether or not - // the player has suggested the game be played fullscreen. +fn setup( + config: Res, + mut window: Query<&mut Window>, + mut volume: ResMut, +) { + window.single_mut().mode = config.window_mode; + volume.volume = VolumeLevel::new(config.master_volume); } // Documented: @@ -104,7 +111,7 @@ fn set_window_icon( // here we use the `image` crate to load our icon data from a png file // this is not a very bevy-native solution, but it will do let (icon_rgba, icon_width, icon_height) = { - let image = image::open("assets/VerseSquircle-256.png") + let image = image::open("assets/images/VerseSquircle-256.png") .expect("Failed to open icon path") .into_rgba8(); let (width, height) = image.dimensions(); diff --git a/src/ships/bullet.rs b/src/ships/bullet.rs index e0c2e65..79dc294 100644 --- a/src/ships/bullet.rs +++ b/src/ships/bullet.rs @@ -1,4 +1,7 @@ -use bevy::prelude::*; +use bevy::{ + audio::{PlaybackMode, Volume}, + prelude::*, +}; use bevy_rapier2d::prelude::*; use crate::core::resources::{ @@ -89,7 +92,15 @@ fn spawn_bullet( ActiveEvents::COLLISION_EVENTS, AudioBundle { source: audios.gun.clone(), - ..default() + settings: PlaybackSettings { + mode: PlaybackMode::Remove, + // TODO: This should be relative to an SFX Volume setting + // which should in turn be relative to the master volume. + // Right now, this is just being set relative to the + // GlobalVolume (which is configurable as master_volume). + volume: Volume::new_relative(1.0), + ..default() + }, }, )); } diff --git a/src/ui/hud/indicator.rs b/src/ui/hud/indicator.rs index 5e05294..e3a99d1 100644 --- a/src/ui/hud/indicator.rs +++ b/src/ui/hud/indicator.rs @@ -53,7 +53,7 @@ fn setup( for (entity, indicated) in entities_query.iter() { parent.spawn(( ImageBundle { - image: UiImage::new(asset_server.load("grey_arrowUpWhite.png")), + image: UiImage::new(asset_server.load("images/grey_arrowUpWhite.png")), style: Style { position_type: PositionType::Absolute, width: Val::Px(15.0), diff --git a/src/ui/menus/credits.rs b/src/ui/menus/credits.rs index 43b7ef7..e01489b 100644 --- a/src/ui/menus/credits.rs +++ b/src/ui/menus/credits.rs @@ -1,8 +1,12 @@ -use bevy::prelude::*; +use bevy::{ + prelude::*, + reflect::{TypePath, TypeUuid}, +}; +use bevy_common_assets::ron::RonAssetPlugin; use crate::{ core::resources::{ - assets::UiAssets, + assets::{DataAssets, UiAssets}, state::{ForState, GameState}, }, ui::resources::top::Top, @@ -11,15 +15,39 @@ use crate::{ pub struct CreditsPlugin; impl Plugin for CreditsPlugin { fn build(&self, app: &mut App) { + app.add_plugins(RonAssetPlugin::::new(&["credits.ron"])); app.add_systems(OnEnter(GameState::Credits), setup); app.add_systems(Update, credits_system.run_if(in_state(GameState::Credits))); } } +#[derive(serde::Deserialize, TypeUuid, TypePath)] +#[uuid = "6763db47-17ca-4530-b604-94492c3a4c58"] +pub struct Credits(Vec); + +#[derive(serde::Deserialize)] +pub struct CreditSection { + section_title: String, + credits: Vec, +} + +#[derive(serde::Deserialize)] +pub struct Credit { + credit_title: String, + credit_meta: Option, +} + #[derive(Component)] -pub struct Credits; +pub struct Scrolling; + +fn setup( + mut commands: Commands, + ui: Res, + data: Res, + credits: ResMut>, +) { + let credits = credits.get(&data.credits.clone()).unwrap(); -fn setup(mut commands: Commands, ui: Res) { commands .spawn(( NodeBundle { @@ -53,312 +81,66 @@ fn setup(mut commands: Commands, ui: Res) { }, ..default() }, - Credits, - // TODO: Initial value should be window Y + 25.0 + Scrolling, + // TODO: Initial value should be window Y Top(1000.0), )) .with_children(|parent| { - parent.spawn((TextBundle { - text: Text::from_section( - "Developed by".to_ascii_uppercase(), - TextStyle { - font: ui.font.clone(), - font_size: 24.0, - color: Color::rgb_u8(0x00, 0x88, 0x88), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Thom Bruce", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(50.)), - ..default() - }, - text: Text::from_section( - "Title Font".to_ascii_uppercase(), - TextStyle { - font: ui.font.clone(), - font_size: 24.0, - color: Color::rgb_u8(0x00, 0x88, 0x88), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Edge of the Galaxy by Quinn Davis Type", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "FontSpace, Public Domain", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(50.)), - ..default() - }, - text: Text::from_section( - "Art".to_ascii_uppercase(), - TextStyle { - font: ui.font.clone(), - font_size: 24.0, - color: Color::rgb_u8(0x00, 0x88, 0x88), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), + for credit_section in credits.0.iter() { + parent.spawn((TextBundle { + style: Style { + margin: UiRect::top(Val::Px(50.)), + ..default() + }, + text: Text::from_section( + &credit_section.section_title.to_ascii_uppercase(), + TextStyle { + font: ui.font.clone(), + font_size: 24.0, + color: Color::rgb_u8(0x00, 0x88, 0x88), + }, + ), ..default() - }, - text: Text::from_section( - "Space Shooter Redux by Kenney", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "kenney.nl, CC0", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Ship Mixer by Kenney", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "kenney.nl", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Pixel Planets by Deep-Fold", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "itch.io, MIT", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Xolonium Typeface by Severin Meyer", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "Font Library, OFL (SIL Open Font License)", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(50.)), - ..default() - }, - text: Text::from_section( - "Music".to_ascii_uppercase(), - TextStyle { - font: ui.font.clone(), - font_size: 24.0, - color: Color::rgb_u8(0x00, 0x88, 0x88), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Lightspeed by Beat Mekanik", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "Free Music Archive, CC BY", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Space Dust by Kirk Osamayo", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "Free Music Archive, CC BY", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(50.)), - ..default() - }, - text: Text::from_section( - "Audio".to_ascii_uppercase(), - TextStyle { - font: ui.font.clone(), - font_size: 24.0, - color: Color::rgb_u8(0x00, 0x88, 0x88), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - style: Style { - margin: UiRect::top(Val::Px(25.)), - ..default() - }, - text: Text::from_section( - "Impact Sounds by Kenney", - TextStyle { - font: ui.font.clone(), - font_size: 20.0, - color: Color::rgb_u8(0xCC, 0xCC, 0xCC), - }, - ), - ..default() - },)); - parent.spawn((TextBundle { - text: Text::from_section( - "kenney.nl, CC0", - TextStyle { - font: ui.font.clone(), - font_size: 14.0, - color: Color::rgb_u8(0xAA, 0xAA, 0xAA), - }, - ), - ..default() - },)); + },)); + + for credit in credit_section.credits.iter() { + parent.spawn((TextBundle { + style: Style { + margin: UiRect::top(Val::Px(25.)), + ..default() + }, + text: Text::from_section( + &credit.credit_title, + TextStyle { + font: ui.font.clone(), + font_size: 20.0, + color: Color::rgb_u8(0xCC, 0xCC, 0xCC), + }, + ), + ..default() + },)); + if let Some(credit_meta) = &credit.credit_meta { + parent.spawn((TextBundle { + text: Text::from_section( + credit_meta, + TextStyle { + font: ui.font.clone(), + font_size: 14.0, + color: Color::rgb_u8(0xAA, 0xAA, 0xAA), + }, + ), + ..default() + },)); + } + } + } }); }); } fn credits_system( time: Res