diff --git a/game/dist/assets/audio/gameSoundtrack.mp3 b/game/dist/assets/audio/gameSoundtrack.mp3 new file mode 100644 index 00000000..ed58fae0 Binary files /dev/null and b/game/dist/assets/audio/gameSoundtrack.mp3 differ diff --git a/game/game_logic/src/asset_manager/mod.rs b/game/game_logic/src/asset_manager/mod.rs index 2b5a023f..d9755643 100644 --- a/game/game_logic/src/asset_manager/mod.rs +++ b/game/game_logic/src/asset_manager/mod.rs @@ -24,4 +24,4 @@ pub use json::{InternalJsonLoadError, load_json_structure}; mod sprite_types; pub use sprite_types::{KnownSpriteType, load_known_sprite_types}; mod texture; -pub use texture::{load_texture_from_internal_data, ResourceLoadError}; \ No newline at end of file +pub use texture::{load_texture_from_internal_data, ResourceLoadError, load_music_from_internal_data, load_sound_from_internal_data}; \ No newline at end of file diff --git a/game/game_logic/src/asset_manager/texture.rs b/game/game_logic/src/asset_manager/texture.rs index 36a819d8..975f2787 100644 --- a/game/game_logic/src/asset_manager/texture.rs +++ b/game/game_logic/src/asset_manager/texture.rs @@ -4,7 +4,11 @@ use std::path::Path; -use raylib::{texture::Texture2D, RaylibHandle, RaylibThread}; +use raylib::{ + audio::{Music, RaylibAudio, Sound}, + texture::Texture2D, + RaylibHandle, RaylibThread, +}; use tempfile::tempdir; use crate::asset_manager::InternalData; @@ -62,3 +66,70 @@ pub fn load_texture_from_internal_data( Ok(texture) } + +pub fn load_music_from_internal_data( + thread: &RaylibThread, + path: &str, +) -> Result { + // Create a temp file path to work with + let temp_dir = tempdir()?; + debug!( + "Created temporary directory for passing embedded data to Raylib: {}", + temp_dir.path().display() + ); + let tmp_path = temp_dir.path().join(Path::new(path).file_name().unwrap()); + + // Unpack the raw sound data to a real file on the local filesystem so raylib will read it correctly + std::fs::write( + &tmp_path, + &InternalData::get(path) + .ok_or(ResourceLoadError::AssetNotFound(path.to_string()))? + .data, + )?; + + // Call through via FFI to re-load the file + let texture = Music::load_music_stream(thread, tmp_path.to_str().unwrap()) + .map_err(ResourceLoadError::Generic)?; + + // Close the file + debug!( + "Dropping temporary directory: {}", + temp_dir.path().display() + ); + // temp_dir.close()?; + + Ok(texture) +} + +pub fn load_sound_from_internal_data( + path: &str, +) -> Result { + // Create a temp file path to work with + let temp_dir = tempdir()?; + debug!( + "Created temporary directory for passing embedded data to Raylib: {}", + temp_dir.path().display() + ); + let tmp_path = temp_dir.path().join(Path::new(path).file_name().unwrap()); + + // Unpack the raw sound data to a real file on the local filesystem so raylib will read it correctly + std::fs::write( + &tmp_path, + &InternalData::get(path) + .ok_or(ResourceLoadError::AssetNotFound(path.to_string()))? + .data, + )?; + + // Call through via FFI to re-load the file + let texture = + Sound::load_sound(tmp_path.to_str().unwrap()).map_err(ResourceLoadError::Generic)?; + + // Close the file + debug!( + "Dropping temporary directory: {}", + temp_dir.path().display() + ); + temp_dir.close()?; + + Ok(texture) +} \ No newline at end of file diff --git a/game/game_logic/src/rendering/event_loop.rs b/game/game_logic/src/rendering/event_loop.rs index 3b9dc90a..cf9860ba 100644 --- a/game/game_logic/src/rendering/event_loop.rs +++ b/game/game_logic/src/rendering/event_loop.rs @@ -111,6 +111,11 @@ pub async fn handle_graphics_blocking( constants, ) .await; + + // Handle exiting the game + if render_delegate.needs_exit { + break; + } } _ => backend_sm = RenderBackendStates::sm_failed(), }; diff --git a/game/game_logic/src/scenes/main_menu.rs b/game/game_logic/src/scenes/main_menu.rs index caf82caa..55169340 100644 --- a/game/game_logic/src/scenes/main_menu.rs +++ b/game/game_logic/src/scenes/main_menu.rs @@ -24,7 +24,7 @@ pub enum MenuStateSignal { #[derive(Debug)] pub struct MainMenu { - has_updated_discord_rpc: bool, + pub has_updated_discord_rpc: bool, } impl MainMenu { diff --git a/game/game_logic/src/scenes/mod.rs b/game/game_logic/src/scenes/mod.rs index 9c02ccbb..c71de971 100644 --- a/game/game_logic/src/scenes/mod.rs +++ b/game/game_logic/src/scenes/mod.rs @@ -23,6 +23,8 @@ mod test_fox; /// This is a struct to allow for stateful data (like sub-screens) to be set up pub struct SceneRenderDelegate { menu_control_signal: MenuStateSignal, + pub needs_exit: bool, + audio_subsystem: RaylibAudio, /* Scenes */ scene_test_fox: TestFoxScene, scene_playable: PlayableScene, @@ -36,7 +38,9 @@ impl SceneRenderDelegate { rl_thread: &RaylibThread, constants: &ProjectConstants, ) -> Self { - // TODO: Stick any init code you want here. + // Set up audio + let audio_subsystem = RaylibAudio::init_audio_device(); + audio_subsystem.set_master_volume(0.4); // Init some scenes let scene_test_fox = TestFoxScene::new(raylib, rl_thread); @@ -45,6 +49,8 @@ impl SceneRenderDelegate { Self { menu_control_signal: MenuStateSignal::DoMainMenu, + needs_exit: false, + audio_subsystem, scene_test_fox, scene_playable, scene_main_menu, @@ -62,22 +68,29 @@ impl SceneRenderDelegate { global_resources: &GlobalResources, constants: &ProjectConstants, ) { + // Render the main menu if in it, otherwise, render the game match self.menu_control_signal { MenuStateSignal::StartGame => { self.scene_playable - .render_frame(raylib, rl_thread, &discord, global_resources, constants) - .await; - self.scene_playable - .update_physics(raylib, constants) + .render_frame(raylib, rl_thread, &discord, global_resources, constants, &mut self.audio_subsystem) .await; + self.scene_playable.update_physics(raylib, constants).await; + + // Clear the menu system discord status + self.scene_main_menu.has_updated_discord_rpc = false; + } + MenuStateSignal::QuitGame => { + self.needs_exit = true; } - MenuStateSignal::QuitGame => unimplemented!(), MenuStateSignal::DoMainMenu => { self.menu_control_signal = self .scene_main_menu .render_main_menu_frame(raylib, rl_thread, discord, global_resources, constants) - .await + .await; + + // Clear the ingame discord status + self.scene_playable.has_updated_discord_rpc = false; } MenuStateSignal::DoOptions => { self.menu_control_signal = self diff --git a/game/game_logic/src/scenes/player_interaction.rs b/game/game_logic/src/scenes/player_interaction.rs index 638b08c3..13749738 100644 --- a/game/game_logic/src/scenes/player_interaction.rs +++ b/game/game_logic/src/scenes/player_interaction.rs @@ -5,6 +5,7 @@ use raylib::prelude::*; use std::time::SystemTime; use crate::{ + asset_manager::{load_music_from_internal_data, load_sound_from_internal_data}, discord::{DiscordChannel, DiscordRpcSignal}, global_resource_package::GlobalResources, model::player::Player, @@ -14,40 +15,43 @@ use crate::{ #[derive(Debug)] pub struct PlayableScene { - has_updated_discord_rpc: bool, + pub has_updated_discord_rpc: bool, player: Player, world_map: MapRenderer, camera: raylib::camera::Camera2D, last_update: SystemTime, + game_soundtrack: Music, } impl PlayableScene { /// Construct a new `PlayableScene` pub fn new( raylib_handle: &mut raylib::RaylibHandle, - thread: & raylib::RaylibThread, + thread: &raylib::RaylibThread, constants: &ProjectConstants, ) -> Self { let map_renderer = MapRenderer::new("map_gameMap.tmx", "map_gameMap.objects.json", raylib_handle, thread).unwrap(); + // Load the game music + let game_soundtrack = + load_music_from_internal_data(thread, "assets/audio/gameSoundtrack.mp3").unwrap(); + Self { has_updated_discord_rpc: false, player: Player::new(na::Vector2::new(10.0, 10.0)), world_map: map_renderer, camera: raylib::camera::Camera2D { - target: raylib::math::Vector2 { - x: 0.0, - y: 0.0, - }, - offset: raylib::math::Vector2 { + target: raylib::math::Vector2 { x: 0.0, y: 0.0 }, + offset: raylib::math::Vector2 { x: (constants.base_window_size.0 as f32 / 2.0), - y: (constants.base_window_size.1 as f32 / 2.0) + y: (constants.base_window_size.1 as f32 / 2.0), }, rotation: 0.0, - zoom: 1.0 + zoom: 1.0, }, - last_update: SystemTime::UNIX_EPOCH + last_update: SystemTime::UNIX_EPOCH, + game_soundtrack, } } @@ -59,6 +63,7 @@ impl PlayableScene { discord: &DiscordChannel, global_resources: &GlobalResources, constants: &ProjectConstants, + audio_subsystem: &mut RaylibAudio, ) { // Handle updating discord RPC if !self.has_updated_discord_rpc { @@ -76,96 +81,101 @@ impl PlayableScene { self.has_updated_discord_rpc = true; } + // Ensure the game soundtrack is playing + if !audio_subsystem.is_music_playing(&self.game_soundtrack) { + debug!("Playing game soundtrack"); + audio_subsystem.play_music_stream(&mut self.game_soundtrack); + } else { + audio_subsystem.update_music_stream(&mut self.game_soundtrack); + } + // Get a drawing handle let mut draw = raylib.begin_drawing(rl_thread); // Clear the screen draw.clear_background(Color::WHITE); - + self.draw_world(&mut draw, constants); self.draw_ui(&mut draw, constants); } - pub fn draw_world( - &mut self, - draw: &mut RaylibDrawHandle, - constants: &ProjectConstants, - ) { + pub fn draw_world(&mut self, draw: &mut RaylibDrawHandle, constants: &ProjectConstants) { // Begin camera mode let mut ctx2d = draw.begin_mode2D(self.camera); // Render the map + self.world_map.render_map(&mut ctx2d, &self.camera, true, self.player.position); let player_size = (constants.tile_size as f32 * constants.player.start_size * self.player.size) as i32; ctx2d.draw_rectangle( - self.player.position[0] as i32 - player_size / 2, + self.player.position[0] as i32 - player_size / 2, self.player.position[1] as i32 * -1 - player_size / 2, player_size, player_size, - Color::LIGHTBLUE + Color::LIGHTBLUE, ); } - pub fn draw_ui( - &mut self, - draw: &mut RaylibDrawHandle, - constants: &ProjectConstants, - ) { - draw.draw_rectangle( - draw.get_screen_width() / 2 - 225, 0, - 450, 40, - Color::WHITE - ); + pub fn draw_ui(&mut self, draw: &mut RaylibDrawHandle, constants: &ProjectConstants) { + draw.draw_rectangle(draw.get_screen_width() / 2 - 225, 0, 450, 40, Color::WHITE); draw.draw_text( - "Unregistered HyperCam 2", - draw.get_screen_width() / 2 - 215, 0, - 32, Color::BLACK + "Unregistered HyperCam 2", + draw.get_screen_width() / 2 - 215, + 0, + 32, + Color::BLACK, ); } - + // Physics pub async fn update_physics( &mut self, - raylib: & raylib::RaylibHandle, + raylib: &raylib::RaylibHandle, constants: &ProjectConstants, ) { - // Get time since last physics update let time = SystemTime::now(); - let elapsed = time.duration_since(self.last_update).expect("Time Appears to Have Moved Backwards!"); + let elapsed = time + .duration_since(self.last_update) + .expect("Time Appears to Have Moved Backwards!"); self.last_update = time; let delta_time = elapsed.as_millis() as f32 / 1000.0; // Physics will be scaled by this value let player = &mut self.player; // Get input direction components - let h_axis = raylib.is_key_down(KeyboardKey::KEY_D) as i8 - raylib.is_key_down(KeyboardKey::KEY_A) as i8; - let v_axis = raylib.is_key_down(KeyboardKey::KEY_W) as i8 - raylib.is_key_down(KeyboardKey::KEY_S) as i8; + let h_axis = raylib.is_key_down(KeyboardKey::KEY_D) as i8 + - raylib.is_key_down(KeyboardKey::KEY_A) as i8; + let v_axis = raylib.is_key_down(KeyboardKey::KEY_W) as i8 + - raylib.is_key_down(KeyboardKey::KEY_S) as i8; if h_axis != 0 || v_axis != 0 { // Normalize input and accelerate in desired direction let direction = na::Vector2::new(h_axis as f32, v_axis as f32).normalize(); - player.velocity += &direction.xy() + player.velocity += &direction.xy() * constants.player.acceleration as f32 - * constants.tile_size as f32 + * constants.tile_size as f32 * delta_time; } if player.velocity.magnitude() != 0.0 { - player.velocity -= player.velocity.normalize() - * constants.player.deceleration as f32 - * constants.tile_size as f32 + player.velocity -= player.velocity.normalize() + * constants.player.deceleration as f32 + * constants.tile_size as f32 * delta_time; if player.velocity.magnitude() < 1.0 { player.velocity.set_magnitude(0.0); } } - if ((constants.player.max_velocity * constants.tile_size) as f32) - < player.velocity.magnitude() { - player.velocity.set_magnitude((constants.player.max_velocity * constants.tile_size) as f32); + if ((constants.player.max_velocity * constants.tile_size) as f32) + < player.velocity.magnitude() + { + player + .velocity + .set_magnitude((constants.player.max_velocity * constants.tile_size) as f32); } player.position += &player.velocity * delta_time; @@ -173,15 +183,10 @@ impl PlayableScene { self.update_camera(raylib); } - pub fn update_camera( - &mut self, - raylib: & raylib::RaylibHandle, - ) { + pub fn update_camera(&mut self, raylib: &raylib::RaylibHandle) { self.camera.target = self.player.position.into(); self.camera.target.y *= -1.0; self.camera.offset.x = raylib.get_screen_width() as f32 / 2.0; self.camera.offset.y = raylib.get_screen_height() as f32 / 2.0; } } - -