More footstep sfx based on distance

This commit is contained in:
jiminycrick 2021-01-19 14:50:18 -08:00
parent 03a1452d16
commit 7dda25a66d
14 changed files with 140 additions and 37 deletions

View File

@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Protection rating was moved to the top left of the loadout view - Protection rating was moved to the top left of the loadout view
- Changed camera smoothing to be off by default. - Changed camera smoothing to be off by default.
- Fixed AI behavior so only humanoids will attempt to roll - Fixed AI behavior so only humanoids will attempt to roll
- Footstep SFX is now dependant on distance moved, not time since last play
### Removed ### Removed

View File

@ -64,7 +64,27 @@
], ],
threshold: 0.25, threshold: 0.25,
), ),
Run: ( Run(Earth): (
files: [
"voxygen.audio.sfx.footsteps.stepdirt_1",
"voxygen.audio.sfx.footsteps.stepdirt_2",
"voxygen.audio.sfx.footsteps.stepdirt_3",
"voxygen.audio.sfx.footsteps.stepdirt_4",
"voxygen.audio.sfx.footsteps.stepdirt_5",
],
threshold: 1.6,
),
QuadRun(Earth): (
files: [
"voxygen.audio.sfx.footsteps.stepdirt_1",
"voxygen.audio.sfx.footsteps.stepdirt_2",
"voxygen.audio.sfx.footsteps.stepdirt_3",
"voxygen.audio.sfx.footsteps.stepdirt_4",
"voxygen.audio.sfx.footsteps.stepdirt_5",
],
threshold: 0.8,
),
Run(Grass): (
files: [ files: [
"voxygen.audio.sfx.footsteps.stepgrass_1", "voxygen.audio.sfx.footsteps.stepgrass_1",
"voxygen.audio.sfx.footsteps.stepgrass_2", "voxygen.audio.sfx.footsteps.stepgrass_2",
@ -73,9 +93,9 @@
"voxygen.audio.sfx.footsteps.stepgrass_5", "voxygen.audio.sfx.footsteps.stepgrass_5",
"voxygen.audio.sfx.footsteps.stepgrass_6", "voxygen.audio.sfx.footsteps.stepgrass_6",
], ],
threshold: 0.25, threshold: 1.6,
), ),
QuadRun: ( QuadRun(Grass): (
files: [ files: [
"voxygen.audio.sfx.footsteps.stepgrass_1", "voxygen.audio.sfx.footsteps.stepgrass_1",
"voxygen.audio.sfx.footsteps.stepgrass_2", "voxygen.audio.sfx.footsteps.stepgrass_2",
@ -84,23 +104,41 @@
"voxygen.audio.sfx.footsteps.stepgrass_5", "voxygen.audio.sfx.footsteps.stepgrass_5",
"voxygen.audio.sfx.footsteps.stepgrass_6", "voxygen.audio.sfx.footsteps.stepgrass_6",
], ],
threshold: 0.12, threshold: 0.8,
), ),
SnowRun: ( Run(Snow): (
files: [ files: [
"voxygen.audio.sfx.footsteps.snow_step_1", "voxygen.audio.sfx.footsteps.snow_step_1",
"voxygen.audio.sfx.footsteps.snow_step_2", "voxygen.audio.sfx.footsteps.snow_step_2",
"voxygen.audio.sfx.footsteps.snow_step_3", "voxygen.audio.sfx.footsteps.snow_step_3",
], ],
threshold: 0.25, threshold: 1.6,
), ),
QuadSnowRun: ( QuadRun(Snow): (
files: [ files: [
"voxygen.audio.sfx.footsteps.snow_step_1", "voxygen.audio.sfx.footsteps.snow_step_1",
"voxygen.audio.sfx.footsteps.snow_step_2", "voxygen.audio.sfx.footsteps.snow_step_2",
"voxygen.audio.sfx.footsteps.snow_step_3", "voxygen.audio.sfx.footsteps.snow_step_3",
], ],
threshold: 0.12, threshold: 0.8,
),
Run(Rock): (
files: [
"voxygen.audio.sfx.footsteps.stone_step_1",
"voxygen.audio.sfx.footsteps.stone_step_2",
"voxygen.audio.sfx.footsteps.stone_step_3",
"voxygen.audio.sfx.footsteps.stone_step_4",
],
threshold: 1.6,
),
QuadRun(Rock): (
files: [
"voxygen.audio.sfx.footsteps.stone_step_1",
"voxygen.audio.sfx.footsteps.stone_step_2",
"voxygen.audio.sfx.footsteps.stone_step_3",
"voxygen.audio.sfx.footsteps.stone_step_4",
],
threshold: 0.8,
), ),
//ExperienceGained: ( //ExperienceGained: (
// files: [ // files: [

BIN
assets/voxygen/audio/sfx/footsteps/stepdirt_1.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stepdirt_2.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stepdirt_3.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stepdirt_4.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stepdirt_5.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stone_step_1.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stone_step_2.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stone_step_3.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/stone_step_4.wav (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -10,6 +10,7 @@ use crate::{
use client::Client; use client::Client;
use common::{ use common::{
comp::{Body, CharacterState, PhysicsState, Pos, Vel}, comp::{Body, CharacterState, PhysicsState, Pos, Vel},
resources::DeltaTime,
terrain::{BlockKind, TerrainChunk}, terrain::{BlockKind, TerrainChunk},
vol::ReadVol, vol::ReadVol,
}; };
@ -25,6 +26,7 @@ struct PreviousEntityState {
time: Instant, time: Instant,
on_ground: bool, on_ground: bool,
in_water: bool, in_water: bool,
distance_travelled: f32,
} }
impl Default for PreviousEntityState { impl Default for PreviousEntityState {
@ -34,6 +36,7 @@ impl Default for PreviousEntityState {
time: Instant::now(), time: Instant::now(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 0.0,
} }
} }
} }
@ -112,6 +115,7 @@ impl EventMapper for MovementEventMapper {
underwater, underwater,
); );
internal_state.time = Instant::now(); internal_state.time = Instant::now();
internal_state.distance_travelled = 0.0;
} }
// update state to determine the next event. We only record the time (above) if // update state to determine the next event. We only record the time (above) if
@ -123,6 +127,8 @@ impl EventMapper for MovementEventMapper {
} else { } else {
internal_state.in_water = false; internal_state.in_water = false;
} }
let dt = ecs.fetch::<DeltaTime>().0;
internal_state.distance_travelled += vel.0.magnitude() * dt;
} }
} }
@ -156,14 +162,19 @@ impl MovementEventMapper {
/// any) needs to satisfy two conditions to be allowed to play: /// any) needs to satisfy two conditions to be allowed to play:
/// 1. An sfx.ron entry exists for the movement (we need to know which sound /// 1. An sfx.ron entry exists for the movement (we need to know which sound
/// file(s) to play) 2. The sfx has not been played since it's timeout /// file(s) to play) 2. The sfx has not been played since it's timeout
/// threshold has elapsed, which prevents firing every tick /// threshold has elapsed, which prevents firing every tick. For movement,
/// threshold is not a time, but a distance.
fn should_emit( fn should_emit(
previous_state: &PreviousEntityState, previous_state: &PreviousEntityState,
sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
) -> bool { ) -> bool {
if let Some((event, item)) = sfx_trigger_item { if let Some((event, item)) = sfx_trigger_item {
if &previous_state.event == event { if &previous_state.event == event {
previous_state.time.elapsed().as_secs_f32() >= item.threshold match event {
SfxEvent::Run(_) => previous_state.distance_travelled >= item.threshold,
SfxEvent::QuadRun(_) => previous_state.distance_travelled >= item.threshold,
_ => previous_state.time.elapsed().as_secs_f32() >= item.threshold,
}
} else { } else {
true true
} }
@ -199,8 +210,11 @@ impl MovementEventMapper {
SfxEvent::Sneak SfxEvent::Sneak
} else { } else {
match underfoot_block_kind { match underfoot_block_kind {
BlockKind::Snow => SfxEvent::SnowRun, BlockKind::Snow => SfxEvent::Run(BlockKind::Snow),
_ => SfxEvent::Run, BlockKind::Rock | BlockKind::WeakRock => SfxEvent::Run(BlockKind::Rock),
BlockKind::Sand => SfxEvent::Run(BlockKind::Sand),
BlockKind::Air => SfxEvent::Idle,
_ => SfxEvent::Run(BlockKind::Grass),
} }
}; };
} }
@ -230,8 +244,11 @@ impl MovementEventMapper {
SfxEvent::Swim SfxEvent::Swim
} else if physics_state.on_ground && vel.magnitude() > 0.1 { } else if physics_state.on_ground && vel.magnitude() > 0.1 {
match underfoot_block_kind { match underfoot_block_kind {
BlockKind::Snow => SfxEvent::SnowRun, BlockKind::Snow => SfxEvent::Run(BlockKind::Snow),
_ => SfxEvent::Run, BlockKind::Rock | BlockKind::WeakRock => SfxEvent::Run(BlockKind::Rock),
BlockKind::Sand => SfxEvent::Run(BlockKind::Sand),
BlockKind::Air => SfxEvent::Idle,
_ => SfxEvent::Run(BlockKind::Grass),
} }
} else { } else {
SfxEvent::Idle SfxEvent::Idle
@ -248,8 +265,11 @@ impl MovementEventMapper {
SfxEvent::Swim SfxEvent::Swim
} else if physics_state.on_ground && vel.magnitude() > 0.1 { } else if physics_state.on_ground && vel.magnitude() > 0.1 {
match underfoot_block_kind { match underfoot_block_kind {
BlockKind::Snow => SfxEvent::QuadSnowRun, BlockKind::Snow => SfxEvent::QuadRun(BlockKind::Snow),
_ => SfxEvent::QuadRun, BlockKind::Rock | BlockKind::WeakRock => SfxEvent::QuadRun(BlockKind::Rock),
BlockKind::Sand => SfxEvent::QuadRun(BlockKind::Sand),
BlockKind::Air => SfxEvent::Idle,
_ => SfxEvent::QuadRun(BlockKind::Grass),
} }
} else { } else {
SfxEvent::Idle SfxEvent::Idle

View File

@ -26,14 +26,17 @@ fn config_but_played_since_threshold_no_emit() {
// Triggered a 'Run' 0 seconds ago // Triggered a 'Run' 0 seconds ago
let previous_state = PreviousEntityState { let previous_state = PreviousEntityState {
event: SfxEvent::Run, event: SfxEvent::Run(BlockKind::Grass),
time: Instant::now(), time: Instant::now(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 0.0,
}; };
let result = let result = MovementEventMapper::should_emit(
MovementEventMapper::should_emit(&previous_state, Some((&SfxEvent::Run, &trigger_item))); &previous_state,
Some((&SfxEvent::Run(BlockKind::Grass), &trigger_item)),
);
assert_eq!(result, false); assert_eq!(result, false);
} }
@ -50,10 +53,13 @@ fn config_and_not_played_since_threshold_emits() {
time: Instant::now().checked_add(Duration::from_secs(1)).unwrap(), time: Instant::now().checked_add(Duration::from_secs(1)).unwrap(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 0.0,
}; };
let result = let result = MovementEventMapper::should_emit(
MovementEventMapper::should_emit(&previous_state, Some((&SfxEvent::Run, &trigger_item))); &previous_state,
Some((&SfxEvent::Run(BlockKind::Grass), &trigger_item)),
);
assert_eq!(result, true); assert_eq!(result, true);
} }
@ -66,16 +72,19 @@ fn same_previous_event_elapsed_emits() {
}; };
let previous_state = PreviousEntityState { let previous_state = PreviousEntityState {
event: SfxEvent::Run, event: SfxEvent::Run(BlockKind::Grass),
time: Instant::now() time: Instant::now()
.checked_sub(Duration::from_millis(500)) .checked_sub(Duration::from_millis(1800))
.unwrap(), .unwrap(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 2.0,
}; };
let result = let result = MovementEventMapper::should_emit(
MovementEventMapper::should_emit(&previous_state, Some((&SfxEvent::Run, &trigger_item))); &previous_state,
Some((&SfxEvent::Run(BlockKind::Grass), &trigger_item)),
);
assert_eq!(result, true); assert_eq!(result, true);
} }
@ -93,6 +102,7 @@ fn maps_idle() {
time: Instant::now(), time: Instant::now(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::zero(), Vec3::zero(),
BlockKind::Grass, BlockKind::Grass,
@ -114,12 +124,13 @@ fn maps_run_with_sufficient_velocity() {
time: Instant::now(), time: Instant::now(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::new(0.5, 0.8, 0.0), Vec3::new(0.5, 0.8, 0.0),
BlockKind::Grass, BlockKind::Grass,
); );
assert_eq!(result, SfxEvent::Run); assert_eq!(result, SfxEvent::Run(BlockKind::Grass));
} }
#[test] #[test]
@ -135,6 +146,7 @@ fn does_not_map_run_with_insufficient_velocity() {
time: Instant::now(), time: Instant::now(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::new(0.02, 0.0001, 0.0), Vec3::new(0.02, 0.0001, 0.0),
BlockKind::Grass, BlockKind::Grass,
@ -153,6 +165,7 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
time: Instant::now(), time: Instant::now(),
on_ground: false, on_ground: false,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::new(0.5, 0.8, 0.0), Vec3::new(0.5, 0.8, 0.0),
BlockKind::Grass, BlockKind::Grass,
@ -183,10 +196,11 @@ fn maps_roll() {
..Default::default() ..Default::default()
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Run, event: SfxEvent::Run(BlockKind::Grass),
time: Instant::now(), time: Instant::now(),
on_ground: true, on_ground: true,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::new(0.5, 0.5, 0.0), Vec3::new(0.5, 0.5, 0.0),
BlockKind::Grass, BlockKind::Grass,
@ -208,12 +222,13 @@ fn maps_land_on_ground_to_run() {
time: Instant::now(), time: Instant::now(),
on_ground: false, on_ground: false,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::zero(), Vec3::zero(),
BlockKind::Grass, BlockKind::Grass,
); );
assert_eq!(result, SfxEvent::Run); assert_eq!(result, SfxEvent::Run(BlockKind::Grass));
} }
#[test] #[test]
@ -226,6 +241,7 @@ fn maps_glider_open() {
time: Instant::now(), time: Instant::now(),
on_ground: false, on_ground: false,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::zero(), Vec3::zero(),
BlockKind::Grass, BlockKind::Grass,
@ -244,6 +260,7 @@ fn maps_glide() {
time: Instant::now(), time: Instant::now(),
on_ground: false, on_ground: false,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::zero(), Vec3::zero(),
BlockKind::Grass, BlockKind::Grass,
@ -262,6 +279,7 @@ fn maps_glider_close_when_closing_mid_flight() {
time: Instant::now(), time: Instant::now(),
on_ground: false, on_ground: false,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::zero(), Vec3::zero(),
BlockKind::Grass, BlockKind::Grass,
@ -284,6 +302,7 @@ fn maps_glider_close_when_landing() {
time: Instant::now(), time: Instant::now(),
on_ground: false, on_ground: false,
in_water: false, in_water: false,
distance_travelled: 0.0,
}, },
Vec3::zero(), Vec3::zero(),
BlockKind::Grass, BlockKind::Grass,
@ -303,7 +322,7 @@ fn maps_quadrupeds_running() {
BlockKind::Grass, BlockKind::Grass,
); );
assert_eq!(result, SfxEvent::Run); assert_eq!(result, SfxEvent::Run(BlockKind::Grass));
} }
#[test] #[test]

View File

@ -22,7 +22,7 @@
//! The following snippet details some entries in the configuration and how they //! The following snippet details some entries in the configuration and how they
//! map to the sound files: //! map to the sound files:
//! ```ignore //! ```ignore
//! Run: ( //! Run(Grass): ( // depends on underfoot block
//! files: [ //! files: [
//! "voxygen.audio.sfx.footsteps.stepgrass_1", //! "voxygen.audio.sfx.footsteps.stepgrass_1",
//! "voxygen.audio.sfx.footsteps.stepgrass_2", //! "voxygen.audio.sfx.footsteps.stepgrass_2",
@ -31,13 +31,13 @@
//! "voxygen.audio.sfx.footsteps.stepgrass_5", //! "voxygen.audio.sfx.footsteps.stepgrass_5",
//! "voxygen.audio.sfx.footsteps.stepgrass_6", //! "voxygen.audio.sfx.footsteps.stepgrass_6",
//! ], //! ],
//! threshold: 0.25, // wait 0.25s between plays //! threshold: 1.6, // travelled distance before next play
//! ), //! ),
//! Wield(Sword): ( // depends on the player's weapon //! Wield(Sword): ( // depends on the player's weapon
//! files: [ //! files: [
//! "voxygen.audio.sfx.weapon.sword_out", //! "voxygen.audio.sfx.weapon.sword_out",
//! ], //! ],
//! threshold: 0.5, //! threshold: 0.5, // wait 0.5s between plays
//! ), //! ),
//! ... //! ...
//! ``` //! ```
@ -95,7 +95,7 @@ use common::{
object, Body, CharacterAbilityType, InventoryUpdateEvent, object, Body, CharacterAbilityType, InventoryUpdateEvent,
}, },
outcome::Outcome, outcome::Outcome,
terrain::TerrainChunk, terrain::{BlockKind, TerrainChunk},
}; };
use common_sys::state::State; use common_sys::state::State;
use event_mapper::SfxEventMapper; use event_mapper::SfxEventMapper;
@ -147,10 +147,8 @@ pub enum SfxEvent {
RunningWater, RunningWater,
Idle, Idle,
Swim, Swim,
Run, Run(BlockKind),
QuadRun, QuadRun(BlockKind),
SnowRun,
QuadSnowRun,
Roll, Roll,
Sneak, Sneak,
Climb, Climb,