diff --git a/game/src/game/display_result.rs b/game/src/game/display_result.rs index b18a669..214dd04 100644 --- a/game/src/game/display_result.rs +++ b/game/src/game/display_result.rs @@ -3,7 +3,6 @@ use std::path::Path; use sdl2::{ event::Event, keyboard::Keycode, - pixels::Color, rect::Rect, render::{Canvas, TextureQuery}, video::Window, diff --git a/game/src/game/game_player/chart_player.rs b/game/src/game/game_player/chart_player.rs index 344145c..86fdd13 100644 --- a/game/src/game/game_player/chart_player.rs +++ b/game/src/game/game_player/chart_player.rs @@ -2,6 +2,7 @@ use bidrum_data_struct_lib::{ janggu::JangguFace, song::{GameChart, GameNote}, }; +use num_rational::Rational64; use sdl2::{render::Canvas, video::Window}; use crate::constants::{ACCURACY_DISPLAY_DURATION, DEFAULT_BPM}; @@ -11,7 +12,9 @@ use crate::game::game_player::{ }; use super::{ - chart_player_ui::{disappearing_note_effect::DisapearingNoteEffect, ChartPlayerUI}, + chart_player_ui::{ + disappearing_note_effect::DisapearingNoteEffect, BeatGuideline, ChartPlayerUI, + }, game_result::GameResult, janggu_state_with_tick::JangguStateWithTick, timing_judge::TimingJudge, @@ -152,6 +155,49 @@ impl ChartPlayer<'_> { display_notes } + fn beat_guideline(&self, tick: i128) -> Option { + if tick < 0 { + return None; + } + + // bpm = beat / minute + // minute-per-beat = 1 / bpm + // timing-in-minute = beat * minute-per-beat + // timing-in-millisecond = timing-in-minute (minute) * ( 60000(millisecond) / 1(minute) ) + // timing = timing-in-millisecond + let timing_of_one_beat = Rational64::new(60000, self.chart.bpm as i64); + + // beat_per_millisecond = (display_bpm / 60000) + // millisecond_per_beat = 1/ beat_per_millisecond + // speed = 1 / millisecond_per_beat + let speed_ratio = Rational64::new((self.chart.bpm * DEFAULT_BPM) as i64, 60000); + + let length_ratio = timing_of_one_beat * speed_ratio; + let length = *length_ratio.numer() as f64 / *length_ratio.denom() as f64; + + let even_beat = + ((*timing_of_one_beat.denom() * tick as i64) / *timing_of_one_beat.numer()) % 2 == 0; + + let position = { + //position_ratio = tick % timing_of_one_beat as i128; + let mut position_ratio = Rational64::new( + (*timing_of_one_beat.denom() * tick as i64) % *timing_of_one_beat.numer(), + *timing_of_one_beat.denom(), + ); + + position_ratio = length_ratio - position_ratio; + position_ratio = position_ratio / timing_of_one_beat; + position_ratio *= length_ratio; + *position_ratio.numer() as f64 / *position_ratio.denom() as f64 + }; + + Some(BeatGuideline { + length, + position, + even_beat, + }) + } + pub fn draw( &mut self, tick: i128, @@ -180,6 +226,7 @@ impl ChartPlayer<'_> { self.ui.disappearing_note_effects.update_base_tick(tick); self.ui.input_effect.update(janggu_state_with_tick, tick); self.ui.notes = self.get_display_notes(tick as u64); + self.ui.beat_guideline = self.beat_guideline(tick); } self.ui.overall_effect_tick = overall_tick; self.ui.draw(canvas); diff --git a/game/src/game/game_player/chart_player_ui.rs b/game/src/game/game_player/chart_player_ui.rs index 33417bc..bbf9a3b 100644 --- a/game/src/game/game_player/chart_player_ui.rs +++ b/game/src/game/game_player/chart_player_ui.rs @@ -29,6 +29,12 @@ use self::{ use super::timing_judge::NoteAccuracy; +pub struct BeatGuideline { + pub position: f64, + pub length: f64, + pub even_beat: bool, +} + pub struct ChartPlayerUI<'a> { pub notes: Vec, pub accuracy: Option, @@ -37,6 +43,7 @@ pub struct ChartPlayerUI<'a> { pub input_effect: InputEffect, pub overall_effect_tick: u128, pub disappearing_note_effects: DisapearingNoteEffect, + pub beat_guideline: Option, resources: ChartPlayerUIResources<'a>, } @@ -56,6 +63,7 @@ impl ChartPlayerUI<'_> { input_effect: InputEffect::new(), overall_effect_tick: 0, disappearing_note_effects: DisapearingNoteEffect::new(), + beat_guideline: None, resources: resources, }; } @@ -140,10 +148,11 @@ impl ChartPlayerUI<'_> { let background_width = (viewport.width() - janggu_width_min) / 2; let background_y = (canvas.viewport().height() as i32 - (background_height_without_border as i32)) / 2; - for background_x in [ + let background_x = [ 0, /* x coordinate of left background */ background_width as i32 + janggu_width_min as i32, /* x coordinate of right background */ - ] { + ]; + for background_x in background_x { let background_alpha = { // is the face hitted? let hitting = if background_x == 0 { @@ -203,7 +212,7 @@ impl ChartPlayerUI<'_> { .unwrap(); } - // draw judgement line + // judgement line info / max note width is required for beat guideline let judgement_line_height = max_stick_note_height; let judgement_line_padding_px = 20; let judgement_line_width = ((judgement_line_texture.query().width as f32 @@ -214,6 +223,54 @@ impl ChartPlayerUI<'_> { background_width as i32 - judgement_line_width as i32 - judgement_line_padding_px, /* left judgement line */ background_width as i32 + janggu_width_min as i32 + judgement_line_padding_px, /* right judgement line */ ]; + let note_width_max = std::cmp::max(left_stick_note_width, right_stick_note_width); + + // draw beat guideline + if let Some(beat_guideline) = &self.beat_guideline { + let mut position = beat_guideline.position % beat_guideline.length; + while position < 0.0 { + position += beat_guideline.length; + } + let thicknesses: [u32; 2] = [4, 4]; + let mut white = beat_guideline.even_beat; + loop { + let distance_between_centers = (position * note_width_max as f64) as i32; + let thickness = thicknesses[if white { 0 } else { 1 }]; + let color = if white { + Color::RGBA(255, 255, 255, 60) + } else { + Color::RGBA(255, 255, 255, 30) + }; + + if distance_between_centers + > (background_width + (judgement_line_width + thickness) / 2) as i32 + { + break; + } + for line_x in [ + judgement_line_xposes[0] - distance_between_centers + + (judgement_line_width / 2 + thickness / 2) as i32, + judgement_line_xposes[1] + + distance_between_centers + + (judgement_line_width / 2 - thickness / 2) as i32, + ] { + canvas.set_draw_color(color); + canvas + .fill_rect(Rect::new( + line_x, + background_y, + thickness, + background_height_without_border, + )) + .unwrap(); + } + + position += beat_guideline.length; + white = !white; + } + } + + // draw judgement line for judgement_line_xpos in judgement_line_xposes { canvas .copy( @@ -229,9 +286,6 @@ impl ChartPlayerUI<'_> { .unwrap(); } - // load textures for the notes and accuracy - let note_width_max = std::cmp::max(left_stick_note_width, right_stick_note_width); - // draw note let mut draw_note = |i: &DisplayedSongNote, disappearing_effect: Option| { let note_texture = match i.stick {