Skip to content

Commit

Permalink
merge: more chart player effects and refactoring
Browse files Browse the repository at this point in the history
- more effects on chart player effects
- refactors chart player logic
  • Loading branch information
LiteHell authored May 21, 2024
2 parents a0a1b2a + 15a70af commit c2f8bbb
Show file tree
Hide file tree
Showing 17 changed files with 929 additions and 683 deletions.
11 changes: 6 additions & 5 deletions data-struct-lib/src/song.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub struct GameNote {
#[serde(skip, default = "JangguFace::default")]
pub face: JangguFace,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GameChart {
pub artist: String,
pub delay: u64,
Expand Down Expand Up @@ -241,10 +241,11 @@ impl GameSong {
.unwrap()
.to_string();
if let Some(video_filename) = deserialized.video_filename {
deserialized.video_filename = Some(Path::join(path, video_filename)
.to_str()
.unwrap()
.to_string()
deserialized.video_filename = Some(
Path::join(path, video_filename)
.to_str()
.unwrap()
.to_string(),
);
}
deserialized.cover_image_filename = Path::join(path, deserialized.cover_image_filename)
Expand Down
71 changes: 18 additions & 53 deletions game/src/game/game_player.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,34 @@
pub mod draw_gameplay_ui;
pub mod chart_player;
pub mod chart_player_ui;
pub mod effect_sound_player;
pub mod game_result;
pub mod janggu_state_with_tick;
pub mod judge_and_display_notes;
pub mod load_hit_sounds;
pub mod timing_judge;

use std::{path::Path, thread};

use kira::{
clock::ClockSpeed,
manager::{backend::DefaultBackend, AudioManager, AudioManagerSettings},
sound::static_sound::{StaticSoundData, StaticSoundSettings},
tween::Tween,
};
use num_rational::Rational64;
use sdl2::{image::LoadTexture, pixels::PixelFormatEnum};

use crate::{
create_streaming_iyuv_texture,
game::{
common::{event_loop_common, render_common},
game_common_context,
game_player::judge_and_display_notes::display_notes_and_judge,
},
use crate::game::{
common::{event_loop_common, render_common},
game_common_context,
};

use self::{
draw_gameplay_ui::{DisplayedSongNote, UIContent},
game_result::GameResult,
chart_player::ChartPlayer, effect_sound_player::EffectSoundPlayer, game_result::GameResult,
janggu_state_with_tick::JangguStateWithTick,
judge_and_display_notes::EffectSoundHandles,
load_hit_sounds::load_hit_sounds,
timing_judge::{NoteAccuracy, TimingJudge},
};

use bidrum_data_struct_lib::{janggu::JangguFace, song::GameSong};
use bidrum_data_struct_lib::song::GameSong;

use super::render_video::VideoFileRenderer;

pub fn is_input_effect_needed(state: &JangguStateWithTick, tick: i128) -> [Option<JangguFace>; 2] {
const TIME_DELTA: i128 = 150;
let mut faces = [None, None];
if let Some(_) = state.궁채.face {
if state.궁채.keydown_timing - tick < TIME_DELTA {
faces[0] = state.궁채.face;
}
}
if let Some(_) = state.열채.face {
if state.열채.keydown_timing - tick < TIME_DELTA {
faces[1] = state.열채.face;
}
}

faces
}

pub(crate) fn play_song(
common_context: &mut game_common_context::GameCommonContext,
song: &GameSong,
Expand All @@ -77,8 +51,7 @@ pub(crate) fn play_song(
let song_path_string = song.audio_filename.clone();

// Load hit sound data
let hit_sounds = load_hit_sounds();
let mut effect_sound_handles = EffectSoundHandles::new();
let mut effect_sounds = EffectSoundPlayer::new();

// to receive coin input while loading the audio file,
// loading should be done in separated thread.
Expand Down Expand Up @@ -129,7 +102,6 @@ pub(crate) fn play_song(

// get judge and create timing judge
let chart = song.get_chart(level).unwrap();
let mut timing_judge = TimingJudge::new(&chart);

// start the clock.
clock.start().expect("Failed to start clock");
Expand Down Expand Up @@ -169,13 +141,10 @@ pub(crate) fn play_song(
.expect("Failed to load play background image.");

// variables for displaying accuracy
let mut accuracy: Option<NoteAccuracy> = None;
let mut accuracy_tick: Option<i128> = None;

let mut janggu_state_with_tick = JangguStateWithTick::new();
let mut processed_note_ids = Vec::<u64>::new();

let mut gameplay_ui_resources = draw_gameplay_ui::GamePlayUIResources::new(&texture_creator);
let mut chart_player = ChartPlayer::new(chart, &texture_creator);

'running: loop {
let tick_now = clock.time().ticks as i128 - start_tick.ticks as i128;
Expand Down Expand Up @@ -222,20 +191,16 @@ pub(crate) fn play_song(
let input_now = common_context.read_janggu_state();
janggu_state_with_tick.update(input_now, tick_now);

effect_sounds.play_janggu_sound(&janggu_state_with_tick, &mut common_context.audio_manager);

// display notes and accuracy
if tick_now >= 0 {
display_notes_and_judge(
common_context,
&chart,
&mut timing_judge,
&janggu_state_with_tick,
&mut gameplay_ui_resources,
&mut processed_note_ids,
&mut accuracy,
&mut accuracy_tick,
&hit_sounds,
&mut effect_sound_handles,
chart_player.judge(&janggu_state_with_tick, tick_now);
chart_player.draw(
tick_now,
&mut common_context.canvas,
common_context.game_initialized_at.elapsed().as_millis(),
&janggu_state_with_tick,
);
}

Expand All @@ -258,5 +223,5 @@ pub(crate) fn play_song(
// If video_file_renderer is not None, stop playing video
video_file_renderer.stop_decoding();
}
return Some(timing_judge.get_game_result());
return Some(chart_player.game_result());
}
189 changes: 189 additions & 0 deletions game/src/game/game_player/chart_player.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use bidrum_data_struct_lib::{
janggu::JangguFace,
song::{GameChart, GameNote},
};
use sdl2::{render::Canvas, video::Window};

use crate::game::game_player::{
chart_player_ui::displayed_song_note::DisplayedSongNote, timing_judge::NoteAccuracy,
};

use super::{
chart_player_ui::{disappearing_note_effect::DisapearingNoteEffect, ChartPlayerUI},
game_result::GameResult,
janggu_state_with_tick::JangguStateWithTick,
timing_judge::TimingJudge,
};

struct ProcessedNote {
processed_note_id: u64,
processed_at_tick: i128,
accuracy: NoteAccuracy,
}

pub struct ChartPlayer<'a> {
chart: GameChart,
timing_judge: TimingJudge,
ui: ChartPlayerUI<'a>,
processed_notes: Vec<ProcessedNote>,
accuracy: Option<(NoteAccuracy, i128)>,
}

impl ChartPlayer<'_> {
pub fn new(
chart: GameChart,
texture_creator: &sdl2::render::TextureCreator<sdl2::video::WindowContext>,
) -> ChartPlayer {
ChartPlayer {
chart: chart.clone(),
timing_judge: TimingJudge::new(&chart),
ui: ChartPlayerUI::new(texture_creator),
processed_notes: vec![],
accuracy: None,
}
}

fn append_disappearing_notes(&mut self, tick: i128, note_ids: Vec<u64>) {
let left_face = self.chart.left_face.clone();
let right_face = self.chart.right_face.clone();
let faces = [left_face, right_face].concat();
let disappearing_notes = faces.iter().filter(|i| {
note_ids.contains(&i.id)
&& self.processed_notes.iter().any(|j| {
j.processed_note_id == i.id
&& j.processed_at_tick.abs_diff(tick.into())
< DisapearingNoteEffect::effect_duration()
})
});

for i in disappearing_notes {
self.ui
.disappearing_note_effects
.push_note(self.get_display_note(i, tick), tick as i128)
}
}

pub fn judge(&mut self, janggu: &JangguStateWithTick, tick: i128) {
let new_accuracies = self.timing_judge.judge(janggu, tick as u64);

if !new_accuracies.is_empty() {
self.accuracy = Some((
new_accuracies.iter().map(|x| x.accuracy).max().unwrap(),
tick,
));
for i in new_accuracies.clone() {
self.processed_notes.push(ProcessedNote {
processed_note_id: i.note_id,
processed_at_tick: tick,
accuracy: i.accuracy,
});

if !matches!(i.accuracy, NoteAccuracy::Miss) {}
}
}

self.append_disappearing_notes(
tick,
new_accuracies
.iter()
.filter(|x| !matches!(x.accuracy, NoteAccuracy::Miss))
.map(|x| x.note_id)
.collect(),
);
}

fn get_display_note(&self, note: &GameNote, tick: i128) -> DisplayedSongNote {
DisplayedSongNote {
face: note.face,
stick: note.stick,
distance: note.get_position(
self.chart.bpm,
self.chart.delay,
self.chart.bpm * 2,
tick as u64,
),
}
}

fn processed_note_ids(&self) -> Vec<u64> {
self.processed_notes
.iter()
.map(|x| x.processed_note_id)
.collect()
}

fn get_display_notes(&self, tick_now: u64) -> Vec<DisplayedSongNote> {
let mut display_notes = Vec::<DisplayedSongNote>::new();
if tick_now >= 0 {
// get positions of the notes
for i in &self.chart.left_face {
if !self.processed_note_ids().contains(&i.id) {
display_notes.push(DisplayedSongNote {
face: JangguFace::궁편,
stick: i.stick,
distance: i.get_position(
self.chart.bpm,
self.chart.delay,
self.chart.bpm * 2,
tick_now,
),
});
}
}

for i in &self.chart.right_face {
if !self.processed_note_ids().contains(&i.id) {
display_notes.push(DisplayedSongNote {
face: JangguFace::열편,
stick: i.stick,
distance: i.get_position(
self.chart.bpm,
self.chart.delay,
self.chart.bpm * 2,
tick_now,
),
});
}
}
}

display_notes
}

pub fn draw(
&mut self,
tick: i128,
canvas: &mut Canvas<Window>,
overall_tick: u128,
janggu_state_with_tick: &JangguStateWithTick,
) {
// judgement is visible for only 800 ms
const ACCURACY_DISPLAY_DURATION: u32 = 800;

// set ui accuracy effect
self.ui.accuracy = None;
self.ui.accuracy_time_progress = None;
if let Some(accuracy) = self.accuracy {
if accuracy.1.abs_diff(tick) > ACCURACY_DISPLAY_DURATION.into() {
self.accuracy = None;
} else {
self.ui.accuracy = Some(accuracy.0);
self.ui.accuracy_time_progress =
Some(accuracy.1.abs_diff(tick) as f32 / ACCURACY_DISPLAY_DURATION as f32)
}
}

// draw game play ui
if tick >= 0 {
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.overall_effect_tick = overall_tick;
self.ui.draw(canvas);
}

pub fn game_result(&self) -> GameResult {
self.timing_judge.get_game_result()
}
}
Loading

0 comments on commit c2f8bbb

Please sign in to comment.