From cb20f408ac1799f69bd4244109202d54370a98f8 Mon Sep 17 00:00:00 2001 From: Yonokid <37304577+Yonokid@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:15:44 -0400 Subject: [PATCH] writing attract sequence --- game.py | 27 ++++++-- global_funcs.py | 179 +++++++++++++++++++++++++++++++++++++++++++++++- main.py | 7 +- title.py | 141 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+), 12 deletions(-) create mode 100644 title.py diff --git a/game.py b/game.py index 7819a3d..e9e03ca 100644 --- a/game.py +++ b/game.py @@ -111,6 +111,8 @@ class GameScreen: ray.load_texture(folder_path + 'lane_obi_img00022.png'), ray.load_texture(folder_path + 'lane_obi_img00023.png'), ray.load_texture(folder_path + 'lane_obi_img00024.png'), + ray.load_texture(folder_path + 'lane_obi_img00025.png'), + ray.load_texture(folder_path + 'lane_obi_img00025.png'), ray.load_texture(folder_path + 'lane_obi_img00025.png')] self.texture_combo_text = [ray.load_texture(folder_path + 'lane_obi_img00035.png'), @@ -168,6 +170,7 @@ class GameScreen: self.load_textures() self.load_sounds() + #Map notes to textures self.note_type_dict = {'1': self.texture_don, '2': self.texture_kat, '3': self.texture_dai_don, @@ -188,6 +191,7 @@ class GameScreen: self.song_music = ray.load_music_stream(self.tja.wave) ray.play_music_stream(self.song_music) self.start_ms = get_current_ms() - self.tja.offset*1000 + self.profiler = Profiler() def update(self): ray.update_music_stream(self.song_music) @@ -195,7 +199,7 @@ class GameScreen: self.player_1.update(self) def draw(self): - self.player_1.draw(self) + self.profiler.profile(self.player_1.draw, (self)) class Player: def __init__(self, game_screen, player_number, difficulty): @@ -393,6 +397,13 @@ class Player: self.curr_drumroll_count = 0 self.curr_balloon_count = 0 current_note = self.current_notes[0] + #Fix later + ''' + i = 0 + while current_note['note'] in {'5', '6', '7', '8'}: + i += 1 + current_note = self.current_notes[i] + ''' note_type = current_note['note'] note_ms = current_note['ms'] #If the wrong key was hit, stop checking @@ -427,6 +438,7 @@ class Player: self.draw_judge_list.append(Judgement(game_screen.current_ms, 'BAD', big)) self.bad_count += 1 self.combo = 0 + self.current_notes.popleft() def drumroll_counter_manager(self, game_screen): if self.is_drumroll and self.curr_drumroll_count > 0: @@ -434,7 +446,7 @@ class Player: self.drumroll_counter.append(DrumrollCounter(game_screen.current_ms)) if len(self.drumroll_counter) > 0: - if self.drumroll_counter[0].is_finished: + if self.drumroll_counter[0].is_finished and not self.is_drumroll: self.drumroll_counter.pop(0) else: self.drumroll_counter[0].update(game_screen, game_screen.current_ms, self.curr_drumroll_count) @@ -464,22 +476,22 @@ class Player: self.score_list[0].update(game_screen.current_ms, self.score) def key_manager(self, game_screen): - if ray.is_key_pressed(ray.KeyboardKey.KEY_F): + if ray.is_key_pressed(ray.KeyboardKey.KEY_F) or ray.is_key_pressed(ray.KeyboardKey.KEY_D): self.draw_effect_list.append(LaneHitEffect(game_screen.current_ms, 'DON')) self.draw_drum_hit_list.append(DrumHitEffect(game_screen.current_ms, 'DON', 'L')) ray.play_sound(game_screen.sound_don) self.check_note(game_screen, '1') - if ray.is_key_pressed(ray.KeyboardKey.KEY_J): + if ray.is_key_pressed(ray.KeyboardKey.KEY_J) or ray.is_key_pressed(ray.KeyboardKey.KEY_K): self.draw_effect_list.append(LaneHitEffect(game_screen.current_ms, 'DON')) self.draw_drum_hit_list.append(DrumHitEffect(game_screen.current_ms, 'DON', 'R')) ray.play_sound(game_screen.sound_don) self.check_note(game_screen, '1') - if ray.is_key_pressed(ray.KeyboardKey.KEY_D): + if ray.is_key_pressed(ray.KeyboardKey.KEY_E) or ray.is_key_pressed(ray.KeyboardKey.KEY_R): self.draw_effect_list.append(LaneHitEffect(game_screen.current_ms, 'KAT')) self.draw_drum_hit_list.append(DrumHitEffect(game_screen.current_ms, 'KAT', 'L')) ray.play_sound(game_screen.sound_kat) self.check_note(game_screen, '2') - if ray.is_key_pressed(ray.KeyboardKey.KEY_K): + if ray.is_key_pressed(ray.KeyboardKey.KEY_I) or ray.is_key_pressed(ray.KeyboardKey.KEY_U): self.draw_effect_list.append(LaneHitEffect(game_screen.current_ms, 'KAT')) self.draw_drum_hit_list.append(DrumHitEffect(game_screen.current_ms, 'KAT', 'R')) ray.play_sound(game_screen.sound_kat) @@ -498,6 +510,7 @@ class Player: self.animation_manager(game_screen, self.base_score_list) self.score_manager(game_screen) self.key_manager(game_screen) + return None def draw_animation_list(self, game_screen, animation_list): for animation in animation_list: @@ -546,7 +559,7 @@ class Player: elif note in game_screen.note_type_dict: eighth_in_ms = (60000 * 4 / game_screen.tja.bpm) / 8 if self.combo >= 50: - current_eighth = int(game_screen.current_ms // eighth_in_ms) + current_eighth = int((game_screen.current_ms - game_screen.start_ms) // eighth_in_ms) else: current_eighth = 0 if note in {'5', '6', 'drumroll_tail', 'dai_drumroll_tail', 'drumroll_body', 'dai_drumroll_body'}: diff --git a/global_funcs.py b/global_funcs.py index 58e3015..fe0d61c 100644 --- a/global_funcs.py +++ b/global_funcs.py @@ -6,6 +6,103 @@ from collections import deque #TJA Format creator is unknown. I did not create the format, but I did write the parser though. +import cProfile +import pstats +import io +import sys + +class Profiler: + def __init__(self): + self.profiler = cProfile.Profile() + self.stats = None + self.calls = 0 + self.profiled_functions = set() # Track profiled functions + self.previous_order = [] # Track previous order of functions + + def profile(self, func, *args, **kwargs): + func_name = func.__name__ + self.calls += 1 + + # Start profiling + self.profiler.enable() + func(*args, **kwargs) + self.profiler.disable() + + # Collect and update stats + if self.stats is None: + self.stats = pstats.Stats(self.profiler) + else: + self.stats.add(self.profiler) + + # Get current order of functions + current_order = self.get_current_function_order() + + # Check if a new function is added or if order has changed + if func_name not in self.profiled_functions or self.previous_order != current_order: + self.profiled_functions.add(func_name) + self.clear_screen() + + # Update the previous order to the current one + self.previous_order = current_order + + # Print the averaged stats + self.print_averaged_stats() + + def clear_screen(self): + sys.stdout.write('\033[2J\033[H') # Clear screen and move cursor to the top + sys.stdout.flush() + + def get_current_function_order(self): + # Capture stats output to determine the current order of functions + stream = io.StringIO() + stats = pstats.Stats(self.profiler, stream=stream) + stats.sort_stats(pstats.SortKey.TIME) + stats.print_stats() + + # Extract function names from the stats output + content = stream.getvalue().splitlines()[5:] # Skip header lines + function_order = [line.split()[-1] for line in content if line.strip()] + return function_order + + def print_averaged_stats(self): + # Prepare the output stream to capture stats + stream = io.StringIO() + stats = pstats.Stats(self.profiler, stream=stream) + stats.sort_stats(pstats.SortKey.TIME) + + # Print the stats, but suppress the raw counts + stats.print_stats() + + # Extract and process stats to average them + content = stream.getvalue().splitlines() + header = content[:5] # Keep the header + data = content[5:] # Data lines + if data: + # Update the total calls and times + avg_data = [] + + for line in data: + parts = line.split() + if len(parts) >= 6: + ncalls = int(parts[0].replace('/', '')) # Total calls + tottime = float(parts[1]) # Total time + percall = tottime / ncalls if ncalls > 0 else 0 + cumtime = float(parts[3]) # Cumulative time + cumpercall = cumtime / ncalls if ncalls > 0 else 0 + + # Format and add the line + avg_data.append( + f"{ncalls:>9} {tottime / self.calls:>9.3f} {percall:>9.3f} " + f"{ncalls:>9} {cumtime / self.calls:>9.3f} {cumpercall:>9.3f} {line[90:]}" + ) + + # Move cursor to the start of the line before printing + sys.stdout.write('\033[F' * (len(header) + len(avg_data))) # Move cursor up to overwrite + + # Print the header and averaged data + sys.stdout.write('\n'.join(header + avg_data) + '\n') + sys.stdout.flush() + def rounded(d): sign = 1 if (d >= 0) else -1 d = abs(d) @@ -68,6 +165,7 @@ class tja_parser: self.scroll_modifier = 1 self.current_ms = 0 self.barline_display = True + self.gogo_time = False def file_to_data(self): with open(self.file_path, 'rt', encoding='utf-8-sig') as tja_file: @@ -94,7 +192,11 @@ class tja_parser: elif 'DEMOSTART' in item: self.demo_start = float(item.split(':')[1]) elif 'COURSE' in item: course = str(item.split(':')[1]).lower() - if course == 'edit' or course == '4': + if course == 'tower' or course == '6': + self.course_data[6] = [] + if course == 'dan' or course == '5': + self.course_data[5] = [] + elif course == 'edit' or course == '4': self.course_data[4] = [] elif course == 'oni' or course == '3': self.course_data[3] = [] @@ -195,6 +297,12 @@ class tja_parser: elif '#BARLINEON' in part: self.barline_display = True continue + elif '#GOGOSTART' in part: + self.gogo_time = True + continue + elif '#GOGOEND' in part: + self.gogo_time = False + continue #Unrecognized commands will be skipped for now elif '#' in part: continue @@ -255,7 +363,10 @@ class tja_parser: if note in {'5', '6', '8'}: play_note_list[-1]['color'] = 255 if note == '8' and play_note_list[-2]['note'] in ('7', '9'): - play_note_list[-1]['balloon'] = int(balloon[balloon_index]) + if balloon_index >= len(balloon): + play_note_list[-1]['balloon'] = 0 + else: + play_note_list[-1]['balloon'] = int(balloon[balloon_index]) balloon_index += 1 self.current_ms += increment @@ -367,3 +478,67 @@ class Animation: else: self.attribute = 0 self.is_finished = True + +import cv2 + +class VideoPlayer: + def __init__(self, path, loop_start=None): + video_path = path + audio_path = path[:-4] + '.ogg' + self.loop_start = loop_start + self.cap = cv2.VideoCapture(video_path) + fps = self.cap.get(cv2.CAP_PROP_FPS) + self.frame_texture = None + self.frame_time = (1.0 / fps) * 1000 + self.start_ms = get_current_ms() + self.audio = ray.load_music_stream(audio_path) + if loop_start is None: + self.audio.looping = False + self.is_finished = [False, False] + ray.play_music_stream(self.audio) + + if not self.cap.isOpened(): + print("Error: Could not open video file.") + return + + def update(self): + current_ms = get_current_ms() + elapsed_time = current_ms - self.start_ms + ray.update_music_stream(self.audio) + if not ray.is_music_stream_playing(self.audio): + self.is_finished[1] = True + + if elapsed_time >= self.frame_time: + ret, frame = self.cap.read() + + if not ret: + # Reset to the loop start frame + if self.loop_start == None: + self.is_finished[0] = True + else: + self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.loop_start) + ret, frame = self.cap.read() # Read the frame at loop start + if not ret: + return # If still not successful, return + + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + image = ray.Image(frame_rgb.tobytes(), frame_rgb.shape[1], frame_rgb.shape[0], 1, ray.PIXELFORMAT_UNCOMPRESSED_R8G8B8) + new_texture = ray.load_texture_from_image(image) + + # Unload the previous texture if it exists + if self.frame_texture: + ray.unload_texture(self.frame_texture) + + # Assign the new texture to the instance variable + self.frame_texture = new_texture + self.start_ms = current_ms + + def draw(self): + if self.frame_texture: + ray.draw_texture(self.frame_texture, 0, 0, ray.WHITE) + + def __del__(self): + # Ensure resources are cleaned up when the instance is deleted + if self.frame_texture: + ray.unload_texture(self.frame_texture) + self.cap.release() diff --git a/main.py b/main.py index 0b04a99..c626f84 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ import sys from entry import * from game import * +from title import * class Screens: TITLE = "TITLE" @@ -21,23 +22,23 @@ def main(): ray.set_window_min_size(screen_width, screen_height) ray.init_window(screen_width, screen_height, "PyTaiko") - current_screen = Screens.ENTRY + current_screen = Screens.TITLE frames_counter = 0 ray.init_audio_device() + title_screen = TitleScreen(screen_width, screen_height) entry_screen = EntryScreen(screen_width, screen_height) game_screen = GameScreen(screen_width, screen_height) screen_mapping = { Screens.ENTRY: entry_screen, - #Screens.TITLE: title_screen, + Screens.TITLE: title_screen, #Screens.SONG_SELECT: song_select_screen, Screens.GAME: game_screen, #Screens.RESULT: result_screen } - #ray.set_target_fps(144) start_song = False while not ray.window_should_close(): ray.begin_drawing() diff --git a/title.py b/title.py new file mode 100644 index 0000000..b8b584a --- /dev/null +++ b/title.py @@ -0,0 +1,141 @@ +import pyray as ray +import numpy as np +import vlc +import cv2 +from global_funcs import Animation, VideoPlayer, get_current_ms + +class TitleScreen: + def __init__(self, width, height): + self.width = width + self.height = height + self.op_video = VideoPlayer('Videos\\OP.mp4') + self.warning = None + self.load_textures() + + def load_textures(self): + self.texture_bg = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00000.png') + self.texture_warning = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00001.png') + self.texture_warning_ch1 = [ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00004.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00009.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00016.png')] + self.texture_warning_ch1_base = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00002.png') + self.texture_warning_ch2 = [ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00005.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00006.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00007.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00008.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00010.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00011.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00012.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00013.png'), + ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00017.png')] + self.texture_warning_ch2_base = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00003.png') + self.texture_warning_bachi = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00019.png') + self.texture_warning_bachi_hit = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00018.png') + + self.sound_bachi_swipe = ray.load_sound('Sounds\\title\\SE_ATTRACT_2.ogg') + self.sound_bachi_hit = ray.load_sound('Sounds\\title\\SE_ATTRACT_3.ogg') + + def animation_manager(self): + if self.warning is not None: + self.warning.update(get_current_ms(), self) + + def update(self): + self.animation_manager() + if self.op_video is not None: + self.op_video.update() + if all(self.op_video.is_finished): + self.op_video = None + self.warning = WarningBoard(get_current_ms(), self) + if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER): + return "ENTRY" + return None + + def draw_animation(self): + if self.warning is not None: + self.warning.draw(self) + def draw(self): + if self.op_video is not None: + self.op_video.draw() + return + bg_source = ray.Rectangle(0, 0, self.texture_bg.width, self.texture_bg.height) + bg_dest = ray.Rectangle(0, 0, self.width, self.height) + ray.draw_texture_pro(self.texture_bg, bg_source, bg_dest, ray.Vector2(0,0), 0, ray.WHITE) + self.draw_animation() + +class WarningBoard: + def __init__(self, current_ms, title_screen): + self.start_ms = current_ms + self.move_animation_1 = Animation(current_ms, 266.67, 'move') + self.move_animation_1.params['start_position'] = -720 + self.move_animation_1.params['total_distance'] = title_screen.height + ((title_screen.height - title_screen.texture_warning.height)//2) + 20 + + self.move_animation_2 = Animation(current_ms, 116.67, 'move') + self.move_animation_2.params['start_position'] = 92 + 20 + self.move_animation_2.params['delay'] = 266.67 + self.move_animation_2.params['total_distance'] = -30 + + self.move_animation_3 = Animation(current_ms, 116.67, 'move') + self.move_animation_3.params['start_position'] = 82 + self.move_animation_3.params['delay'] = 383.34 + self.move_animation_3.params['total_distance'] = 10 + + self.fade_animation_1 = Animation(current_ms, 300, 'fade') + self.fade_animation_1.params['delay'] = 266.67 + self.fade_animation_1.params['initial_opacity'] = 0.0 + self.fade_animation_1.params['final_opacity'] = 1.0 + self.character_time = 0 + self.character_index_val = 0 + self.hit_played = False + + def update(self, current_ms, title_screen): + self.move_animation_1.update(current_ms) + self.move_animation_2.update(current_ms) + self.move_animation_3.update(current_ms) + self.fade_animation_1.update(current_ms) + delay = 566.67 + if delay <= current_ms - self.start_ms and self.character_index(1) != 8: + if not ray.is_sound_playing(title_screen.sound_bachi_swipe): + ray.play_sound(title_screen.sound_bachi_swipe) + elif self.character_index(1) == 8 and not self.hit_played: + self.hit_played = True + ray.play_sound(title_screen.sound_bachi_hit) + + def character_index(self, index): + elapsed_time = get_current_ms() - self.start_ms + delay = 566.67 + animation = [(300.00, 1, 0), (183.33, 2, 0), (166.67, 3, 0), (166.67, 4, 1), (166.67, 5, 1), (166.67, 6, 1), (166.67, 7, 1), + (166.67, 0, 0), (150.00, 1, 0), (133.34, 2, 0), (133.34, 3, 0), (133.34, 4, 1), (133.34, 5, 1), (133.34, 6, 1), (133.34, 7, 1), + (133.34, 0, 0), (116.67, 1, 0), (100.00, 2, 0), (100.00, 3, 0), (100.00, 4, 1), (100.00, 5, 1), (100.00, 6, 1), (100.00, 7, 1), + (100.00, 0, 0), (100.00, 1, 0), (83.330, 2, 0), (83.330, 3, 0), (83.330, 4, 1), (83.330, 5, 1), (83.330, 6, 1), (83.330, 7, 1), + (83.330, 0, 0), (83.330, 1, 0), (66.670, 2, 0), (66.670, 3, 0), (66.670, 4, 1), (66.670, 5, 1), (66.670, 6, 1), (66.670, 7, 1), + (66.670, 0, 0), (66.670, 1, 0), (66.670, 2, 0), (66.670, 3, 0), (66.670, 4, 1), (66.670, 5, 1), (66.670, 6, 1), (66.670, 7, 1), + (66.670, 0, 0), (66.670, 1, 0), (66.670, 2, 0), (66.670, 3, 0), (66.670, 4, 1), (66.670, 5, 1), (66.670, 6, 1), (66.670, 7, 1), + (66.670, 8, 2)] + if self.character_index_val == len(animation)-1: + return animation[len(animation)-1][index] + elif elapsed_time <= delay: + return 0 + elif elapsed_time >= delay + self.character_time: + new_index = animation[self.character_index_val][index] + self.character_index_val += 1 + self.character_time += animation[self.character_index_val][0] + return new_index + else: + return animation[self.character_index_val][index] + + def draw(self, title_screen): + if self.move_animation_2.is_finished: + y = self.move_animation_3.attribute + elif self.move_animation_1.is_finished: + y = self.move_animation_2.attribute + else: + y = self.move_animation_1.attribute + ray.draw_texture(title_screen.texture_warning, 0, int(y), ray.WHITE) + fade = ray.fade(ray.WHITE, self.fade_animation_1.attribute) + fade_2 = ray.fade(ray.WHITE, self.fade_animation_1.attribute if self.fade_animation_1.attribute < 0.75 else 0.75) + ray.draw_texture(title_screen.texture_warning_ch1_base, 135, int(y)+title_screen.texture_warning_ch1[0].height+110, fade_2) + ray.draw_texture(title_screen.texture_warning_ch1[self.character_index(2)], 115, int(y)+150, fade) + ray.draw_texture(title_screen.texture_warning_ch2_base, 360, int(y)+title_screen.texture_warning_ch2[0].height+60, fade_2) + ray.draw_texture(title_screen.texture_warning_ch2[self.character_index(1)], 315, int(y)+100, fade) + if self.character_index(1) == 8: + ray.draw_texture(title_screen.texture_warning_bachi, 350, int(y)+135, ray.WHITE)