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"
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.18.0", features = ["dynamic_linking", "wav"] }
|
||||
bevy = { version = "0.18.0", features = ["debug", "dynamic_linking", "wav"] }
|
||||
|
||||
[profile.dev]
|
||||
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::*};
|
||||
|
||||
|
|
@ -6,6 +6,67 @@ fn duration_from_bpm(bpm: f32) -> Duration {
|
|||
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)]
|
||||
struct Metronome;
|
||||
|
||||
|
|
@ -13,6 +74,13 @@ struct Metronome;
|
|||
struct MetronomeData {
|
||||
pub sound: Handle<AudioSource>,
|
||||
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;
|
||||
|
|
@ -20,7 +88,7 @@ pub struct RhythmPlugin;
|
|||
impl Plugin for RhythmPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
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 {
|
||||
sound: asset_server.load("sfx/metronome.wav"),
|
||||
timer: Timer::new(duration_from_bpm(120f32), TimerMode::Repeating),
|
||||
elapsed_beats: 0,
|
||||
});
|
||||
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(
|
||||
|
|
@ -39,6 +130,7 @@ fn tick_metronome(
|
|||
metronome: Single<Entity, With<Metronome>>,
|
||||
) {
|
||||
if metronome_data.timer.tick(time.delta()).just_finished() {
|
||||
metronome_data.elapsed_beats += 1;
|
||||
commands.entity(*metronome).insert((
|
||||
AudioPlayer::new(metronome_data.sound.clone()),
|
||||
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() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue