Skip to content

Commit

Permalink
Improve sfx abstractions + preload sfx on startup
Browse files Browse the repository at this point in the history
  • Loading branch information
rukai committed Jan 1, 2021
1 parent b98da2d commit 94375b7
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 41 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions assets/audio/sfx/TorielOven/ovenTimer.ogg
Git LFS file not shown
32 changes: 11 additions & 21 deletions canon_collision/src/audio.rs → canon_collision/src/audio/mod.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,44 @@
use std::path::PathBuf;
use std::fs;
use std::path::PathBuf;

use audiotags::Tag;
use kira::instance::{InstanceId, InstanceSettings, StopInstanceSettings};
use kira::manager::{AudioManager, AudioManagerSettings};
use kira::playable::PlayableSettings;
use kira::Value;
use rand::seq::IteratorRandom;
use rand;
use treeflection::{Node, NodeRunner, NodeToken};

use canon_collision_lib::assets::Assets;
use canon_collision_lib::entity_def::EntityDef;

pub mod sfx;

use sfx::{SFX, SFXType};

pub struct Audio {
manager: AudioManager,
path: PathBuf,
bgm: Option<InstanceId>,
sfx: SFX,
}

impl Audio {
pub fn new(assets: Assets) -> Self {
let manager = AudioManager::new(AudioManagerSettings::default()).unwrap();
let mut manager = AudioManager::new(AudioManagerSettings::default()).unwrap();
let path = assets.path().join("audio");
let sfx = SFX::new(&mut manager, &path);

Audio {
manager,
path,
sfx,
bgm: None,
}
}

/// TODO: Load all sounds effects on startup
/// TODO: Random sfx selection from a pool?
/// TODO: How to handle rollback?
/// TODO: I could probably add a foo.txt for a foo.mp3 that contains a relative path to another mp3 file
pub fn play_sound_effect(&mut self, entity: &EntityDef, sfx_name: &str, volume: Value<f64>, pitch: Value<f64>) {
self.play_sound_effect_inner(entity, sfx_name, volume, pitch).unwrap();
}

pub fn play_sound_effect_inner(&mut self, entity: &EntityDef, sfx_name: &str, volume: Value<f64>, pitch: Value<f64>) -> Result<(), String> {
let folder = entity.name.replace(" ", "");
let path = self.path.join("sfx").join(&folder).join(sfx_name);

let playable_settings = PlayableSettings::default();
let new_sound = self.manager.load_sound(&path, playable_settings).map_err(|x| format!("Failed to load {:?}. {}", path, x))?;

let instance_settings = InstanceSettings::default().volume(volume).pitch(pitch);
self.manager.play(new_sound, instance_settings).map_err(|x| x.to_string())?;
Ok(())
pub fn play_sound_effect(&mut self, entity: &EntityDef, sfx: SFXType) {
self.sfx.play_sound_effect(&mut self.manager, entity, sfx);
}

/// Folders can contain music organized by stage/menu or fighter
Expand Down
114 changes: 114 additions & 0 deletions canon_collision/src/audio/sfx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};

use kira::Value;
use kira::instance::InstanceSettings;
use kira::manager::AudioManager;
use kira::playable::PlayableSettings;
use kira::sound::SoundId;

use canon_collision_lib::entity_def::EntityDef;

// TODO: move into hitbox canon_collision_lib hitbox definition
pub enum HitBoxSFX {
Sword,
Punch,
//Explode, etc...
}

pub enum SFXType {
Walk,
Run,
Dash,
Jump,
Land,
Die,
Hit (HitBoxSFX),
/// TODO: Dont know if the ergonomics and efficiency of this is a good idea.
/// Lets play with it a bit and throw it away if we dont like it.
Custom {
filename: String,
volume: Value<f64>,
pitch: Value<f64>
}
}

pub struct SFX {
sfx: HashMap<String, SoundId>,
}

impl SFX {
pub fn new(manager: &mut AudioManager, path: &Path) -> Self {
let mut sfx = HashMap::new();
let path = path.join("sfx");
SFX::populate_sfx(manager, &path, None, &mut sfx);
SFX { sfx }
}

fn populate_sfx(manager: &mut AudioManager, root_path: &Path, search_path: Option<&PathBuf>, sfx: &mut HashMap<String, SoundId>) {
let path = if let Some(search_path) = search_path {
root_path.join(search_path)
} else {
root_path.to_path_buf()
};

for file in fs::read_dir(path).unwrap() {
let file = file.unwrap();
let playable_settings = PlayableSettings::default();

let sub_search_path = if let Some(search_path) = search_path {
search_path.join(file.file_name())
} else {
PathBuf::from(file.file_name())
};

let file_type = file.file_type().unwrap();
if file_type.is_file() {
let id = manager.load_sound(file.path(), playable_settings).unwrap();

let key = sub_search_path.to_str().unwrap().replace(MAIN_SEPARATOR, "/");
if sfx.contains_key(&key) {
panic!("Duplicate sfx key");
}
sfx.insert(key, id);
}
else if file_type.is_dir() {
SFX::populate_sfx(manager, root_path, Some(&sub_search_path), sfx);
}
}
}

/// TODO: How to handle rollback?
pub fn play_sound_effect(&mut self, manager: &mut AudioManager, entity: &EntityDef, sfx: SFXType) {
let entity_name = entity.name.replace(" ", "");

let sfx_id = match (&entity_name, &sfx) {
//(_, SFXType::Walk) => ["Common/walk1.ogg", "Common/walk2.ogg"].choose(&mut rand::thread_rng()).unwrap(), // TODO: This is possible
(_, SFXType::Walk) => self.sfx["Common/walk.ogg"],
(_, SFXType::Run) => self.sfx["Common/walk.ogg"],
(_, SFXType::Dash) => self.sfx["Common/dash.ogg"],
(_, SFXType::Jump) => self.sfx["Common/jump.ogg"],
(_, SFXType::Land) => self.sfx["Common/land.ogg"],
(_, SFXType::Die) => self.sfx["Common/die.wav"],
(_, SFXType::Hit (HitBoxSFX::Sword)) => self.sfx["Common/swordHit.ogg"],
(_, SFXType::Hit (HitBoxSFX::Punch)) => self.sfx["Common/hit.wav"],
(folder, SFXType::Custom { filename, .. }) => self.sfx[&format!("{}/{}", folder, filename)],
}.clone();

let (volume, pitch) = match (&entity_name, sfx) {
(_, SFXType::Walk) => (Value::Random(0.01, 0.03), Value::Random(0.95, 1.05)),
(_, SFXType::Run) => (Value::Random(0.03, 0.1), Value::Random(0.95, 1.05)),
(_, SFXType::Dash) => (Value::Random(0.15, 0.2), Value::Random(0.95, 1.05)),
(_, SFXType::Jump) => (Value::Random(0.15, 0.2), Value::Random(0.90, 1.1)),
(_, SFXType::Land) => (Value::Random(0.05, 0.1), Value::Random(0.90, 1.1)),
(_, SFXType::Die) => (Value::Random(0.30, 0.4), Value::Random(0.90, 1.1)),
(_, SFXType::Hit (HitBoxSFX::Sword)) => (Value::Random(0.15, 0.2), Value::Random(0.95, 1.05)),
(_, SFXType::Hit (HitBoxSFX::Punch)) => (Value::Random(0.15, 0.2), Value::Random(0.90, 1.1)),
(_, SFXType::Custom { volume, pitch, .. }) => (volume, pitch),
};

let instance_settings = InstanceSettings::default().volume(volume).pitch(pitch);
manager.play(sfx_id, instance_settings).map_err(|x| x.to_string()).unwrap();
}
}
26 changes: 12 additions & 14 deletions canon_collision/src/entity/fighters/player.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::audio::sfx::SFXType;
use crate::collision::collision_box::CollisionResult;
use crate::entity::components::action_state::ActionState;
use crate::entity::components::body::{Body, Location, PhysicsResult};
use crate::entity::item::{MessageItem, Item};
use crate::entity::{Entity, EntityType, StepContext, DebugEntity, VectorArrow, Entities, EntityKey, Message, MessageContents, ActionResult};
use crate::graphics;
use crate::particle::{Particle, ParticleType};
use crate::results::{RawPlayerResult, DeathRecord};
use crate::rules::{Goal, Rules};
use crate::entity::item::{MessageItem, Item};
use crate::entity::{Entity, EntityType, StepContext, DebugEntity, VectorArrow, Entities, EntityKey, Message, MessageContents, ActionResult};
use crate::entity::components::body::{Body, Location, PhysicsResult};
use crate::entity::components::action_state::ActionState;

use canon_collision_lib::entity_def::{EntityDef, HitStun, Shield, HitBox, HurtBox, HitboxEffect};
use canon_collision_lib::entity_def::player::PlayerAction;
Expand All @@ -18,7 +19,6 @@ use canon_collision_lib::stage::{Stage, Surface};

use treeflection::KeyedContextVec;
use rand::Rng;
use kira::Value;

use std::f32;
use std::f32::consts::PI;
Expand Down Expand Up @@ -250,14 +250,12 @@ impl Player {
for col_result in col_results {
match col_result {
&CollisionResult::HitAtk { ref hitbox, ref point, .. } => {
context.audio.play_sound_effect(&context.entity_def, "hit.wav", Value::Random(0.15, 0.2), Value::Random(0.90, 1.1));
self.hit_particles(point.clone(), hitbox);
}
&CollisionResult::HitDef { ref hitbox, ref hurtbox, entity_atk_i } => {
set_action = self.launch(context, state, hitbox, hurtbox, entity_atk_i);
}
&CollisionResult::HitShieldAtk { ref hitbox, ref power_shield, entity_defend_i } => {
context.audio.play_sound_effect(&context.entity_def, "hit.wav", Value::Random(0.15, 0.2), Value::Random(0.90, 1.1));
let entity_def = &context.entities[entity_defend_i];
if let Some(player_def) = &entity_def.ty.get_player() {
if let &Some(ref power_shield) = power_shield {
Expand Down Expand Up @@ -669,7 +667,7 @@ impl Player {

fn jump_action(&mut self, context: &mut StepContext, state: &ActionState) -> Option<ActionResult> {
if state.frame == 0 {
context.audio.play_sound_effect(&context.entity_def, "jump.ogg", Value::Random(0.15, 0.2), Value::Random(0.90, 1.1));
context.audio.play_sound_effect(&context.entity_def, SFXType::Jump);
}
None
.or_else(|| self.check_attacks_aerial(context))
Expand Down Expand Up @@ -925,7 +923,7 @@ impl Player {

fn attack_land_action(&mut self, context: &mut StepContext, state: &ActionState) -> Option<ActionResult> {
if state.frame == 0 {
context.audio.play_sound_effect(&context.entity_def, "land.ogg", Value::Random(0.05, 0.1), Value::Random(0.90, 1.1));
context.audio.play_sound_effect(&context.entity_def, SFXType::Land);
}
let frame = state.frame + self.land_frame_skip as i64 + 1;

Expand All @@ -935,7 +933,7 @@ impl Player {

fn land_action(&mut self, context: &mut StepContext, state: &ActionState) -> Option<ActionResult> {
if state.frame == 0 {
context.audio.play_sound_effect(&context.entity_def, "land.ogg", Value::Random(0.05, 0.1), Value::Random(0.90, 1.1));
context.audio.play_sound_effect(&context.entity_def, SFXType::Land);
}
self.land_particles(context, state);

Expand Down Expand Up @@ -988,7 +986,7 @@ impl Player {

fn walk_action(&mut self, context: &mut StepContext, state: &ActionState) -> Option<ActionResult> {
if state.frame_no_restart % 20 == 0 {
context.audio.play_sound_effect(&context.entity_def, "walk.ogg", Value::Random(0.01, 0.03), Value::Random(0.95, 1.05));
context.audio.play_sound_effect(&context.entity_def, SFXType::Walk);
}

if context.input[0].stick_x == 0.0 {
Expand Down Expand Up @@ -1026,7 +1024,7 @@ impl Player {

fn dash_action(&mut self, context: &mut StepContext, state: &ActionState) -> Option<ActionResult> {
if state.frame == 0 {
context.audio.play_sound_effect(&context.entity_def, "dash.ogg", Value::Random(0.15, 0.2), Value::Random(0.95, 1.05));
context.audio.play_sound_effect(&context.entity_def, SFXType::Dash);
}
self.dash_particles(context, state);
if state.frame == 1 {
Expand Down Expand Up @@ -1084,7 +1082,7 @@ impl Player {

fn run_action(&mut self, context: &mut StepContext, state: &ActionState) -> Option<ActionResult> {
if state.frame_no_restart % 17 == 0 {
context.audio.play_sound_effect(&context.entity_def, "walk.ogg", Value::Random(0.03, 0.1), Value::Random(0.95, 1.05));
context.audio.play_sound_effect(&context.entity_def, SFXType::Run);
}
None
.or_else(|| self.check_jump(context))
Expand Down Expand Up @@ -2163,7 +2161,7 @@ impl Player {
}

fn die(&mut self, context: &mut StepContext, game_frame: usize, goal: Goal) -> Option<ActionResult> {
context.audio.play_sound_effect(&context.entity_def, "die.wav", Value::Random(0.30, 0.4), Value::Random(0.90, 1.1));
context.audio.play_sound_effect(&context.entity_def, SFXType::Die);
self.body = if context.stage.respawn_points.len() == 0 {
Body::new(
Location::Airbourne { x: 0.0, y: 0.0 },
Expand Down
9 changes: 6 additions & 3 deletions canon_collision/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ pub(crate) mod toriel_fireball;
pub(crate) mod toriel_oven;
pub(crate) mod fighters;

use std::collections::HashSet;
use std::f32::consts::PI;

use fighters::player::{Player, RenderPlayer, MessagePlayer};
use projectile::Projectile;
use toriel_fireball::TorielFireball;
Expand All @@ -19,6 +22,7 @@ use crate::collision::collision_box::CollisionResult;
use crate::graphics;
use crate::particle::Particle;
use crate::rules::Goal;
use crate::audio::sfx::{SFXType, HitBoxSFX};

use canon_collision_lib::geometry::Rect;
use canon_collision_lib::entity_def::{EntityDef, ActionFrame, CollisionBoxRole, ECB};
Expand All @@ -30,9 +34,6 @@ use rand_chacha::ChaChaRng;
use slotmap::{DenseSlotMap, SparseSecondaryMap, new_key_type};
use treeflection::KeyedContextVec;

use std::collections::HashSet;
use std::f32::consts::PI;

new_key_type! { pub struct EntityKey; }
pub type Entities = DenseSlotMap<EntityKey, Entity>;
pub type DebugEntities = SparseSecondaryMap<EntityKey, DebugEntity>;
Expand Down Expand Up @@ -155,10 +156,12 @@ impl Entity {
for col_result in col_results {
match col_result {
&CollisionResult::HitAtk { entity_defend_i, ref hitbox, .. } => {
context.audio.play_sound_effect(&context.entity_def, SFXType::Hit(HitBoxSFX::Punch));
self.state.hitlist.push(entity_defend_i);
self.state.hitlag = Hitlag::Attack { counter: (hitbox.damage / 3.0 + 3.0) as u64 };
}
&CollisionResult::HitShieldAtk { entity_defend_i, ref hitbox, .. } => {
context.audio.play_sound_effect(&context.entity_def, SFXType::Hit(HitBoxSFX::Sword));
self.state.hitlist.push(entity_defend_i);
self.state.hitlag = Hitlag::Attack { counter: (hitbox.damage / 3.0 + 3.0) as u64 };
}
Expand Down
18 changes: 15 additions & 3 deletions canon_collision/src/entity/toriel_oven.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::entity::{StepContext, ActionResult};
use crate::entity::components::body::Body;
use kira::Value;

use crate::audio::sfx::SFXType;
use crate::entity::components::action_state::ActionState;
use crate::entity::components::body::Body;
use crate::entity::{StepContext, ActionResult};

use canon_collision_lib::entity_def::toriel_oven::TorielOvenAction;

Expand Down Expand Up @@ -54,7 +57,16 @@ impl TorielOven {
}
}
Some(TorielOvenAction::AttackExtended) => None,
Some(TorielOvenAction::Attack) => None,
Some(TorielOvenAction::Attack) => {
if state.frame == 40 {
context.audio.play_sound_effect(&context.entity_def, SFXType::Custom {
filename: "ovenTimer.ogg".into(),
volume: Value::Fixed(0.3),
pitch: Value::Fixed(1.0),
});
}
None
}
None => None,
};

Expand Down

0 comments on commit 94375b7

Please sign in to comment.