Merge pull request #50 from Ewpratten/master

update to master
This commit is contained in:
Evan Pratten 2021-10-03 11:11:29 -07:00 committed by GitHub
commit c77ee2c9bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 228 additions and 44 deletions

View File

@ -28,11 +28,12 @@ This game is developed by a team of 6 students from *Sheridan College* and *Tren
- [**Luna Sicardi**](https://github.com/LuS404) - [**Luna Sicardi**](https://github.com/LuS404)
- Software developer - Software developer
- UI design - UI design
- **Emilia Frias** - [**Emilia Frias**](https://www.instagram.com/demilurii/)
- Character art - Character art
- Animations - Animations
- Map assets - Tilesets
- [**Kori**](https://www.instagram.com/korigama/) - [**Kori**](https://www.instagram.com/korigama/)
- Concept art - Concept art
- Tilesets
A special thanks goes out to: [James Nickoli](https://github.com/rsninja722/) for insight on 2D collision detection, as well as [Ray](https://github.com/raysan5) and the members of the [raylib community](https://discord.gg/raylib) on discord for their support with the past two game jam projects. A special thanks goes out to: [James Nickoli](https://github.com/rsninja722/) for insight on 2D collision detection, as well as [Ray](https://github.com/raysan5) and the members of the [raylib community](https://discord.gg/raylib) on discord for their support with the past two game jam projects.

Binary file not shown.

Binary file not shown.

View File

@ -1,9 +1,10 @@
use std::{cell::RefCell, sync::mpsc::Sender}; use std::{cell::RefCell, collections::HashMap, sync::mpsc::Sender};
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use discord_sdk::activity::ActivityBuilder; use discord_sdk::activity::ActivityBuilder;
use raylib::audio::Sound;
use crate::{progress::ProgressData, utilities::non_ref_raylib::HackedRaylibHandle, GameConfig}; use crate::{GameConfig, progress::ProgressData, utilities::{audio_player::AudioPlayer, non_ref_raylib::HackedRaylibHandle}};
#[derive(Debug)] #[derive(Debug)]
pub enum ControlFlag { pub enum ControlFlag {
@ -11,12 +12,15 @@ pub enum ControlFlag {
SwitchLevel(usize), SwitchLevel(usize),
UpdateLevelStart(DateTime<Utc>), UpdateLevelStart(DateTime<Utc>),
SaveProgress, SaveProgress,
MaybeUpdateHighScore(usize, Duration) MaybeUpdateHighScore(usize, Duration),
SoundTrigger(String)
} }
#[derive(Debug)] #[derive(Debug)]
pub struct GameContext { pub struct GameContext {
pub renderer: RefCell<HackedRaylibHandle>, pub renderer: RefCell<HackedRaylibHandle>,
pub audio: AudioPlayer,
pub sounds: HashMap<String, Sound>,
pub config: GameConfig, pub config: GameConfig,
pub player_progress: ProgressData, pub player_progress: ProgressData,
pub current_level: usize, pub current_level: usize,

View File

@ -70,7 +70,7 @@
)] )]
#![clippy::msrv = "1.57.0"] #![clippy::msrv = "1.57.0"]
use std::{borrow::BorrowMut, cell::RefCell, sync::mpsc::TryRecvError}; use std::{borrow::BorrowMut, cell::RefCell, collections::HashMap, sync::mpsc::TryRecvError};
use chrono::Utc; use chrono::Utc;
use discord_sdk::activity::ActivityBuilder; use discord_sdk::activity::ActivityBuilder;
@ -84,6 +84,8 @@ use crate::{
progress::ProgressData, progress::ProgressData,
scenes::{build_screen_state_machine, Scenes}, scenes::{build_screen_state_machine, Scenes},
utilities::{ utilities::{
audio_player::AudioPlayer,
datastore::{load_music_from_internal_data, load_sound_from_internal_data},
game_config::FinalShaderConfig, game_config::FinalShaderConfig,
shaders::{ shaders::{
shader::ShaderWrapper, shader::ShaderWrapper,
@ -172,10 +174,23 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
rl.set_target_fps(60); rl.set_target_fps(60);
raylib_thread = thread; raylib_thread = thread;
// Init the audio subsystem
let mut audio_system = AudioPlayer::new(RaylibAudio::init_audio_device());
audio_system.set_master_volume(0.4);
// Load any other sounds
let mut sounds = HashMap::new();
sounds.insert(
"button-press".to_string(),
load_sound_from_internal_data("audio/button-press.mp3").unwrap(),
);
// Build the game context // Build the game context
context = Box::new(GameContext { context = Box::new(GameContext {
renderer: RefCell::new(rl.into()), renderer: RefCell::new(rl.into()),
config: game_config.clone(), config: game_config.clone(),
audio: audio_system,
sounds,
current_level: 0, current_level: 0,
player_progress: save_file, player_progress: save_file,
level_start_time: Utc::now(), level_start_time: Utc::now(),
@ -184,6 +199,17 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
}); });
} }
// Load the game's main song
let mut main_song = load_music_from_internal_data(
&mut context.renderer.borrow_mut(),
&raylib_thread,
"audio/soundtrack.mp3",
)
.unwrap();
// Start the song
context.audio.play_music_stream(&mut main_song);
// Get the main state machine // Get the main state machine
info!("Setting up the scene management state machine"); info!("Setting up the scene management state machine");
let mut game_state_machine = let mut game_state_machine =
@ -223,6 +249,12 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
puffin::profile_scope!("main_loop"); puffin::profile_scope!("main_loop");
puffin::GlobalProfiler::lock().new_frame(); puffin::GlobalProfiler::lock().new_frame();
// Update the audio
context.audio.update_music_stream(&mut main_song);
if !context.audio.is_music_playing(&main_song) {
context.audio.play_music_stream(&mut main_song);
}
// Update the GPU texture that we draw to. This handles screen resizing and some other stuff // Update the GPU texture that we draw to. This handles screen resizing and some other stuff
dynamic_texture dynamic_texture
.update(&mut context.renderer.borrow_mut(), &raylib_thread) .update(&mut context.renderer.borrow_mut(), &raylib_thread)
@ -341,6 +373,11 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
.player_progress .player_progress
.maybe_write_new_time(level, &time); .maybe_write_new_time(level, &time);
} }
context::ControlFlag::SoundTrigger(name) => {
context.audio.play_sound(
context.sounds.get(&name).unwrap(),
);
}
} }
} }
} }

View File

@ -6,17 +6,13 @@ use discord_sdk::activity::{ActivityBuilder, Assets};
use pkg_version::pkg_version_major; use pkg_version::pkg_version_major;
use raylib::prelude::*; use raylib::prelude::*;
use crate::{ use crate::{GameConfig, context::{ControlFlag, GameContext}, utilities::{
context::GameContext,
utilities::{
datastore::{load_texture_from_internal_data, ResourceLoadError}, datastore::{load_texture_from_internal_data, ResourceLoadError},
game_version::get_version_string, game_version::get_version_string,
math::interpolate_exp, math::interpolate_exp,
non_ref_raylib::HackedRaylibHandle, non_ref_raylib::HackedRaylibHandle,
render_layer::ScreenSpaceRender, render_layer::ScreenSpaceRender,
}, }};
GameConfig,
};
use super::{Scenes, ScreenError}; use super::{Scenes, ScreenError};
use tracing::{debug, error, info, trace}; use tracing::{debug, error, info, trace};
@ -69,6 +65,10 @@ impl Action<Scenes, ScreenError, GameContext> for DeathScreen {
self.timer_value = format!("{:02}:{:02}", elapsed.num_minutes(), elapsed.num_seconds() % 60); self.timer_value = format!("{:02}:{:02}", elapsed.num_minutes(), elapsed.num_seconds() % 60);
if self.is_retry_pressed { if self.is_retry_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
Ok(ActionFlag::SwitchState(Scenes::InGameScene)) Ok(ActionFlag::SwitchState(Scenes::InGameScene))
} else { } else {
Ok(ActionFlag::Continue) Ok(ActionFlag::Continue)

View File

@ -6,17 +6,13 @@ use discord_sdk::activity::{ActivityBuilder, Assets};
use pkg_version::pkg_version_major; use pkg_version::pkg_version_major;
use raylib::prelude::*; use raylib::prelude::*;
use crate::{ use crate::{GameConfig, context::{ControlFlag, GameContext}, utilities::{
context::GameContext,
utilities::{
datastore::{load_texture_from_internal_data, ResourceLoadError}, datastore::{load_texture_from_internal_data, ResourceLoadError},
game_version::get_version_string, game_version::get_version_string,
math::interpolate_exp, math::interpolate_exp,
non_ref_raylib::HackedRaylibHandle, non_ref_raylib::HackedRaylibHandle,
render_layer::ScreenSpaceRender, render_layer::ScreenSpaceRender,
}, }};
GameConfig,
};
use super::{Scenes, ScreenError}; use super::{Scenes, ScreenError};
use tracing::{debug, error, info, trace}; use tracing::{debug, error, info, trace};
@ -66,6 +62,10 @@ impl Action<Scenes, ScreenError, GameContext> for HowToPlayScreen {
self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config); self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config);
if self.is_btm_pressed { if self.is_btm_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen)) Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen))
} else { } else {
Ok(ActionFlag::Continue) Ok(ActionFlag::Continue)

View File

@ -76,12 +76,28 @@ impl Action<Scenes, ScreenError, GameContext> for MainMenuScreen {
self.level_times = Some(context.player_progress.level_best_times.iter().map(|x| (*x.0, *x.1)).collect::<Vec<(_,_)>>().iter().map(|x| *x).enumerate().collect()); self.level_times = Some(context.player_progress.level_best_times.iter().map(|x| (*x.0, *x.1)).collect::<Vec<(_,_)>>().iter().map(|x| *x).enumerate().collect());
if self.is_start_pressed { if self.is_start_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
Ok(ActionFlag::SwitchState(Scenes::InGameScene)) Ok(ActionFlag::SwitchState(Scenes::InGameScene))
} else if self.is_htp_pressed { } else if self.is_htp_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
Ok(ActionFlag::SwitchState(Scenes::HowToPlayScreen)) Ok(ActionFlag::SwitchState(Scenes::HowToPlayScreen))
} else if self.is_options_pressed { } else if self.is_options_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
Ok(ActionFlag::SwitchState(Scenes::OptionsScreen)) Ok(ActionFlag::SwitchState(Scenes::OptionsScreen))
} else if self.is_quit_pressed { } else if self.is_quit_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
context.flag_send.send(Some(ControlFlag::Quit)).unwrap(); context.flag_send.send(Some(ControlFlag::Quit)).unwrap();
Ok(ActionFlag::Continue) Ok(ActionFlag::Continue)
} else { } else {
@ -294,5 +310,7 @@ impl ScreenSpaceRender for MainMenuScreen {
} }
} }
self.is_quit_pressed = mouse_pressed && hovering_quit; self.is_quit_pressed = mouse_pressed && hovering_quit;
// for
} }
} }

View File

@ -6,13 +6,7 @@ use self::{
death_screen::DeathScreen, win_screen::WinScreen, death_screen::DeathScreen, win_screen::WinScreen,
next_level_screen::NextLevelScreen next_level_screen::NextLevelScreen
}; };
use crate::{ use crate::{context::GameContext, utilities::{datastore::{ResourceLoadError, load_music_from_internal_data, load_sound_from_internal_data, load_texture_from_internal_data}, non_ref_raylib::HackedRaylibHandle}};
context::GameContext,
utilities::{
datastore::{load_texture_from_internal_data, ResourceLoadError},
non_ref_raylib::HackedRaylibHandle,
},
};
use dirty_fsm::StateMachine; use dirty_fsm::StateMachine;
use raylib::{texture::Texture2D, RaylibThread}; use raylib::{texture::Texture2D, RaylibThread};

View File

@ -6,17 +6,13 @@ use discord_sdk::activity::{ActivityBuilder, Assets};
use pkg_version::pkg_version_major; use pkg_version::pkg_version_major;
use raylib::prelude::*; use raylib::prelude::*;
use crate::{ use crate::{GameConfig, context::{ControlFlag, GameContext}, utilities::{
context::GameContext,
utilities::{
datastore::{load_texture_from_internal_data, ResourceLoadError}, datastore::{load_texture_from_internal_data, ResourceLoadError},
game_version::get_version_string, game_version::get_version_string,
math::interpolate_exp, math::interpolate_exp,
non_ref_raylib::HackedRaylibHandle, non_ref_raylib::HackedRaylibHandle,
render_layer::ScreenSpaceRender, render_layer::ScreenSpaceRender,
}, }};
GameConfig,
};
use super::{Scenes, ScreenError}; use super::{Scenes, ScreenError};
use tracing::{debug, error, info, trace}; use tracing::{debug, error, info, trace};
@ -87,6 +83,10 @@ impl Action<Scenes, ScreenError, GameContext> for NextLevelScreen {
); );
if self.is_next_pressed { if self.is_next_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
Ok(ActionFlag::SwitchState(Scenes::InGameScene)) Ok(ActionFlag::SwitchState(Scenes::InGameScene))
} else { } else {
Ok(ActionFlag::Continue) Ok(ActionFlag::Continue)

View File

@ -6,17 +6,13 @@ use discord_sdk::activity::{ActivityBuilder, Assets};
use pkg_version::pkg_version_major; use pkg_version::pkg_version_major;
use raylib::prelude::*; use raylib::prelude::*;
use crate::{ use crate::{GameConfig, context::{ControlFlag, GameContext}, utilities::{
context::GameContext,
utilities::{
datastore::{load_texture_from_internal_data, ResourceLoadError}, datastore::{load_texture_from_internal_data, ResourceLoadError},
game_version::get_version_string, game_version::get_version_string,
math::interpolate_exp, math::interpolate_exp,
non_ref_raylib::HackedRaylibHandle, non_ref_raylib::HackedRaylibHandle,
render_layer::ScreenSpaceRender, render_layer::ScreenSpaceRender,
}, }};
GameConfig,
};
use super::{Scenes, ScreenError}; use super::{Scenes, ScreenError};
use tracing::{debug, error, info, trace}; use tracing::{debug, error, info, trace};
@ -69,6 +65,10 @@ impl Action<Scenes, ScreenError, GameContext> for OptionsScreen {
self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config); self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config);
if self.is_btm_pressed { if self.is_btm_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen)) Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen))
} else { } else {
Ok(ActionFlag::Continue) Ok(ActionFlag::Continue)

View File

@ -6,17 +6,13 @@ use discord_sdk::activity::{ActivityBuilder, Assets};
use pkg_version::pkg_version_major; use pkg_version::pkg_version_major;
use raylib::prelude::*; use raylib::prelude::*;
use crate::{ use crate::{GameConfig, context::{ControlFlag, GameContext}, utilities::{
context::GameContext,
utilities::{
datastore::{load_texture_from_internal_data, ResourceLoadError}, datastore::{load_texture_from_internal_data, ResourceLoadError},
game_version::get_version_string, game_version::get_version_string,
math::interpolate_exp, math::interpolate_exp,
non_ref_raylib::HackedRaylibHandle, non_ref_raylib::HackedRaylibHandle,
render_layer::ScreenSpaceRender, render_layer::ScreenSpaceRender,
}, }};
GameConfig,
};
use super::{Scenes, ScreenError}; use super::{Scenes, ScreenError};
use tracing::{debug, error, info, trace}; use tracing::{debug, error, info, trace};
@ -82,6 +78,10 @@ impl Action<Scenes, ScreenError, GameContext> for PauseScreen {
&& Rectangle::new(centered_x_paused, centered_y_paused, 435.0, 80.0) && Rectangle::new(centered_x_paused, centered_y_paused, 435.0, 80.0)
.check_collision_point_rec(mouse_position) .check_collision_point_rec(mouse_position)
{ {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
return Ok(ActionFlag::SwitchState(Scenes::InGameScene)); return Ok(ActionFlag::SwitchState(Scenes::InGameScene));
} }
//For Menu //For Menu
@ -89,6 +89,10 @@ impl Action<Scenes, ScreenError, GameContext> for PauseScreen {
&& Rectangle::new(centered_x_menu, centered_y_menu, 200.0, 50.0) && Rectangle::new(centered_x_menu, centered_y_menu, 200.0, 50.0)
.check_collision_point_rec(mouse_position) .check_collision_point_rec(mouse_position)
{ {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
return Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen)); return Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen));
} }

View File

@ -65,6 +65,10 @@ impl Action<Scenes, ScreenError, GameContext> for WinScreen {
self.counter += 1; self.counter += 1;
if self.is_menu_pressed { if self.is_menu_pressed {
context
.flag_send
.send(Some(ControlFlag::SoundTrigger("button-press".to_string())))
.unwrap();
context.flag_send.send(Some(ControlFlag::SwitchLevel(0))).unwrap(); context.flag_send.send(Some(ControlFlag::SwitchLevel(0))).unwrap();
Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen)) Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen))
} else { } else {

View File

@ -0,0 +1,49 @@
use raylib::audio::RaylibAudio;
/// A thin wrapper around `raylib::core::audio::RaylibAudio` that keeps track of the volume of its audio channels.
#[derive(Debug)]
pub struct AudioPlayer {
backend: RaylibAudio,
// Volume
pub master_volume: f32,
}
impl AudioPlayer {
/// Construct an AudioPlayer around a RaylibAudio
pub fn new(backend: RaylibAudio) -> Self {
Self {
backend,
master_volume: 1.0,
}
}
/// Set the master volume for all tracks. `0.0` to `1.0`
pub fn set_master_volume(&mut self, volume: f32) {
// The volume must be 0-1
let volume = volume.clamp(0.0, 1.0);
// Set the volume
self.master_volume = volume;
self.backend.set_master_volume(volume);
}
/// Get the master volume
pub fn get_master_volume(&self) -> f32 {
self.master_volume
}
}
impl std::ops::Deref for AudioPlayer {
type Target = RaylibAudio;
fn deref(&self) -> &Self::Target {
&self.backend
}
}
impl std::ops::DerefMut for AudioPlayer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.backend
}
}

View File

@ -1,6 +1,10 @@
use std::{io::Write, path::Path}; use std::{io::Write, path::Path};
use raylib::{texture::Texture2D, RaylibHandle, RaylibThread}; use raylib::{
audio::{Music, Sound},
texture::Texture2D,
RaylibHandle, RaylibThread,
};
use tempfile::{tempdir, NamedTempFile}; use tempfile::{tempdir, NamedTempFile};
use tracing::debug; use tracing::debug;
@ -67,3 +71,71 @@ pub fn load_texture_from_internal_data(
Ok(texture) Ok(texture)
} }
pub fn load_music_from_internal_data(
raylib_handle: &mut RaylibHandle,
thread: &RaylibThread,
path: &str,
) -> Result<Music, ResourceLoadError> {
// 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,
&StaticGameData::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<Sound, ResourceLoadError> {
// 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,
&StaticGameData::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)
}

View File

@ -8,3 +8,4 @@ pub mod non_ref_raylib;
pub mod render_layer; pub mod render_layer;
pub mod shaders; pub mod shaders;
pub mod world_paint_texture; pub mod world_paint_texture;
pub mod audio_player;