generated from 2ndbeam/bevy-template
feat: Added BeatMap and input scanning
This commit is contained in:
parent
fa2e968b4e
commit
6e1ab7d860
3 changed files with 150 additions and 3 deletions
|
|
@ -6,7 +6,7 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.18.0", features = ["dynamic_linking", "wav"] }
|
bevy = { version = "0.18.0", features = ["debug", "dynamic_linking", "wav"] }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
|
||||||
BIN
assets/sfx/hit.wav
Normal file
BIN
assets/sfx/hit.wav
Normal file
Binary file not shown.
151
src/main.rs
151
src/main.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use std::time::Duration;
|
use std::{cmp::Ordering, collections::BTreeMap, time::Duration};
|
||||||
|
|
||||||
use bevy::{audio::PlaybackMode, prelude::*};
|
use bevy::{audio::PlaybackMode, prelude::*};
|
||||||
|
|
||||||
|
|
@ -6,6 +6,67 @@ fn duration_from_bpm(bpm: f32) -> Duration {
|
||||||
Duration::from_millis((60_000f32 / bpm) as u64)
|
Duration::from_millis((60_000f32 / bpm) as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, PartialOrd)]
|
||||||
|
enum BeatDirection {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, PartialOrd)]
|
||||||
|
struct Beat {
|
||||||
|
pub position: f32,
|
||||||
|
pub direction: BeatDirection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Beat {
|
||||||
|
pub fn new(position: f32, direction: BeatDirection) -> Self {
|
||||||
|
Self { position, direction }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accuracy(&self, compared: &Beat) -> f32 {
|
||||||
|
if self.direction != compared.direction ||
|
||||||
|
(self.position - compared.position).abs() >= 1.0 {
|
||||||
|
return 0f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(comparison) = self.position.partial_cmp(&compared.position) else {
|
||||||
|
return 0f32;
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = match comparison {
|
||||||
|
Ordering::Equal => return 1f32,
|
||||||
|
Ordering::Less => {
|
||||||
|
1f32 + compared.position - self.position
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
1f32 - (self.position - compared.position)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
(1f32 - (x - 1f32).powi(2)).sqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, PartialOrd, Debug)]
|
||||||
|
struct BeatMap(Vec<Beat>);
|
||||||
|
|
||||||
|
impl BeatMap {
|
||||||
|
fn beat_length(&self) -> f32 {
|
||||||
|
let Some(last_beat) = self.0.last() else {
|
||||||
|
return 0f32;
|
||||||
|
};
|
||||||
|
|
||||||
|
last_beat.position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Debug)]
|
||||||
|
struct BeatMapManager {
|
||||||
|
pub beatmaps: BTreeMap<String, BeatMap>,
|
||||||
|
pub input: BeatMap,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Metronome;
|
struct Metronome;
|
||||||
|
|
||||||
|
|
@ -13,6 +74,13 @@ struct Metronome;
|
||||||
struct MetronomeData {
|
struct MetronomeData {
|
||||||
pub sound: Handle<AudioSource>,
|
pub sound: Handle<AudioSource>,
|
||||||
pub timer: Timer,
|
pub timer: Timer,
|
||||||
|
pub elapsed_beats: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetronomeData {
|
||||||
|
pub fn current_beat(&self) -> f32 {
|
||||||
|
self.elapsed_beats as f32 + self.timer.fraction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RhythmPlugin;
|
pub struct RhythmPlugin;
|
||||||
|
|
@ -20,7 +88,7 @@ pub struct RhythmPlugin;
|
||||||
impl Plugin for RhythmPlugin {
|
impl Plugin for RhythmPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Startup, setup_metronome)
|
app.add_systems(Startup, setup_metronome)
|
||||||
.add_systems(Update, tick_metronome);
|
.add_systems(Update, (tick_metronome, handle_input).chain());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,8 +96,31 @@ fn setup_metronome(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
commands.insert_resource( MetronomeData {
|
commands.insert_resource( MetronomeData {
|
||||||
sound: asset_server.load("sfx/metronome.wav"),
|
sound: asset_server.load("sfx/metronome.wav"),
|
||||||
timer: Timer::new(duration_from_bpm(120f32), TimerMode::Repeating),
|
timer: Timer::new(duration_from_bpm(120f32), TimerMode::Repeating),
|
||||||
|
elapsed_beats: 0,
|
||||||
});
|
});
|
||||||
commands.spawn(Metronome);
|
commands.spawn(Metronome);
|
||||||
|
|
||||||
|
let beatmap = vec![
|
||||||
|
Beat::new(0.0, BeatDirection::Down),
|
||||||
|
Beat::new(1.0, BeatDirection::Down),
|
||||||
|
Beat::new(2.0, BeatDirection::Left),
|
||||||
|
Beat::new(2.5, BeatDirection::Up),
|
||||||
|
Beat::new(3.0, BeatDirection::Right),
|
||||||
|
Beat::new(4.0, BeatDirection::Up),
|
||||||
|
Beat::new(4.5, BeatDirection::Right),
|
||||||
|
Beat::new(5.0, BeatDirection::Down),
|
||||||
|
Beat::new(5.5, BeatDirection::Left),
|
||||||
|
Beat::new(6.5, BeatDirection::Up),
|
||||||
|
Beat::new(7.0, BeatDirection::Up),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut beatmaps = BTreeMap::new();
|
||||||
|
beatmaps.insert("test".into(), BeatMap(beatmap));
|
||||||
|
|
||||||
|
commands.insert_resource(BeatMapManager {
|
||||||
|
beatmaps,
|
||||||
|
input: BeatMap(vec![]),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick_metronome(
|
fn tick_metronome(
|
||||||
|
|
@ -39,6 +130,7 @@ fn tick_metronome(
|
||||||
metronome: Single<Entity, With<Metronome>>,
|
metronome: Single<Entity, With<Metronome>>,
|
||||||
) {
|
) {
|
||||||
if metronome_data.timer.tick(time.delta()).just_finished() {
|
if metronome_data.timer.tick(time.delta()).just_finished() {
|
||||||
|
metronome_data.elapsed_beats += 1;
|
||||||
commands.entity(*metronome).insert((
|
commands.entity(*metronome).insert((
|
||||||
AudioPlayer::new(metronome_data.sound.clone()),
|
AudioPlayer::new(metronome_data.sound.clone()),
|
||||||
PlaybackSettings {
|
PlaybackSettings {
|
||||||
|
|
@ -49,6 +141,61 @@ fn tick_metronome(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_input(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut metronome: ResMut<MetronomeData>,
|
||||||
|
mut bm: ResMut<BeatMapManager>,
|
||||||
|
) {
|
||||||
|
if metronome.current_beat() > bm.beatmaps["test"].beat_length() + 1f32
|
||||||
|
&& !bm.input.0.is_empty() {
|
||||||
|
bm.input.0.clear();
|
||||||
|
println!("track ended, cleared input");
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_directions = vec![
|
||||||
|
(KeyCode::ArrowUp, BeatDirection::Up),
|
||||||
|
(KeyCode::ArrowDown, BeatDirection::Down),
|
||||||
|
(KeyCode::ArrowLeft, BeatDirection::Left),
|
||||||
|
(KeyCode::ArrowRight, BeatDirection::Right),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (key, direction) in input_directions {
|
||||||
|
if keyboard_input.just_pressed(key) {
|
||||||
|
if bm.input.0.is_empty() {
|
||||||
|
metronome.elapsed_beats = 0;
|
||||||
|
if metronome.timer.fraction() > 0.5 {
|
||||||
|
metronome.elapsed_beats = -1;
|
||||||
|
}
|
||||||
|
println!("starting new input");
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
AudioPlayer::new(asset_server.load("sfx/hit.wav")),
|
||||||
|
PlaybackSettings {
|
||||||
|
mode: PlaybackMode::Despawn,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
bm.input.0.push(Beat::new(metronome.current_beat(), direction));
|
||||||
|
println!("pushed {:?}", bm.input.0.last().unwrap());
|
||||||
|
|
||||||
|
let last_index = bm.input.0.len() - 1;
|
||||||
|
|
||||||
|
if bm.beatmaps["test"].0.len() > last_index {
|
||||||
|
let accuracy = bm.beatmaps["test"].0[last_index].accuracy(bm.input.0.last().unwrap());
|
||||||
|
println!("accuracy: {}%", accuracy * 100f32);
|
||||||
|
} else {
|
||||||
|
println!("accuracy: 0%; too much beats!");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue