diff --git a/.gitignore b/.gitignore index 8715492..0388114 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ Songs -__pycache__ \ No newline at end of file +__pycache__ +attract_videos \ No newline at end of file diff --git a/game.py b/game.py index e9e03ca..1098ea1 100644 --- a/game.py +++ b/game.py @@ -191,7 +191,6 @@ 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) @@ -199,7 +198,7 @@ class GameScreen: self.player_1.update(self) def draw(self): - self.profiler.profile(self.player_1.draw, (self)) + self.player_1.draw(self) class Player: def __init__(self, game_screen, player_number, difficulty): diff --git a/global_funcs.py b/global_funcs.py index fe0d61c..6a13265 100644 --- a/global_funcs.py +++ b/global_funcs.py @@ -1,6 +1,7 @@ import time import os import pyray as ray +import cv2 from collections import deque @@ -303,6 +304,8 @@ class tja_parser: elif '#GOGOEND' in part: self.gogo_time = False continue + elif '#LYRIC' in part: + continue #Unrecognized commands will be skipped for now elif '#' in part: continue @@ -408,6 +411,12 @@ class Animation: elif self.type == 'text_stretch': self.text_stretch(current_ms, self.duration) + elif self.type == 'texture_resize': + self.texture_resize(current_ms, + self.duration, + initial_size=self.params.get('initial_size', 1.0), + final_size=self.params.get('final_size', 1.0), + delay=self.params.get('delay', 0.0)) def fade(self, current_ms, duration, initial_opacity, final_opacity, delay, ease_in, ease_out): def ease_out_progress(progress, ease): @@ -478,67 +487,124 @@ class Animation: else: self.attribute = 0 self.is_finished = True - -import cv2 + def texture_resize(self, current_ms, duration, initial_size, final_size, delay): + elapsed_time = current_ms - self.start_ms + if elapsed_time < delay: + self.attribute = initial_size + elapsed_time -= delay + if elapsed_time >= duration: + self.attribute = final_size + self.is_finished = True + elif elapsed_time < duration: + progress = elapsed_time / duration + self.attribute = initial_size + ((final_size - initial_size) * progress) + else: + self.attribute = final_size + self.is_finished = True class VideoPlayer: def __init__(self, path, loop_start=None): - video_path = path - audio_path = path[:-4] + '.ogg' + self.video_path = path + self.start_ms = None 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) + self.current_frame = None + self.last_frame = self.current_frame + self.frame_index = 0 + self.frames = [] + self.cap = cv2.VideoCapture(self.video_path) + self.fps = self.cap.get(cv2.CAP_PROP_FPS) + + self.is_finished = [False, False] + audio_path = path[:-4] + '.ogg' + self.audio = ray.load_music_stream(audio_path) + + def convert_frames_background(self, index): if not self.cap.isOpened(): - print("Error: Could not open video file.") - return + raise ValueError("Error: Could not open video file.") + + total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT)) + if len(self.frames) == total_frames: + return 0 + self.cap.set(cv2.CAP_PROP_POS_FRAMES, index) + + success, frame = self.cap.read() + + timestamp = (index / self.fps * 1000) + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + new_frame = ray.Image(frame_rgb.tobytes(), frame_rgb.shape[1], frame_rgb.shape[0], 1, ray.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8) + + self.frames.append((timestamp, new_frame)) + print(len(self.frames), total_frames) + + def convert_frames(self): + ''' + if ray.file_exists(path[:-4] + '.pkl'): + self.object_file = path[:-4] + '.pkl' + with open(self.object_file, 'rb') as f: + self.frames = pickle.load(f) + print(f"Loaded {path[:-4] + '.pkl'}") + else: + self.object_file = None + if self.object_file is None: + ''' + if not self.cap.isOpened(): + raise ValueError("Error: Could not open video file.") + + frame_count = 0 + success, frame = self.cap.read() + + while success: + timestamp = (frame_count / self.fps * 1000) + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + new_frame = ray.Image(frame_rgb.tobytes(), frame_rgb.shape[1], frame_rgb.shape[0], 1, ray.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8) + + self.frames.append((timestamp, new_frame)) + + success, frame = self.cap.read() + frame_count += 1 + + self.cap.release() + print(f"Extracted {len(self.frames)} frames.") + ''' + with open(path[:-4] + '.pkl', 'wb') as f: + pickle.dump(self.frames, f) + ''' + self.start_ms = get_current_ms() def update(self): - current_ms = get_current_ms() - elapsed_time = current_ms - self.start_ms + if self.start_ms is None: + self.start_ms = get_current_ms() + ray.play_music_stream(self.audio) + if self.frames == []: + self.convert_frames() ray.update_music_stream(self.audio) - if not ray.is_music_stream_playing(self.audio): + time_played = ray.get_music_time_played(self.audio) / ray.get_music_time_length(self.audio) + if time_played > 0.95: self.is_finished[1] = True - if elapsed_time >= self.frame_time: - ret, frame = self.cap.read() + if self.frame_index == len(self.frames)-1: + self.is_finished[0] = True + return + timestamp, frame = self.frames[self.frame_index][0], self.frames[self.frame_index][1] - 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 + if self.start_ms is not None: + elapsed_time = get_current_ms() - self.start_ms + if elapsed_time >= timestamp: + self.current_frame = ray.load_texture_from_image(frame) + if self.last_frame != self.current_frame and self.last_frame is not None: + ray.unload_texture(self.last_frame) + self.frame_index += 1 + self.last_frame = self.current_frame def draw(self): - if self.frame_texture: - ray.draw_texture(self.frame_texture, 0, 0, ray.WHITE) + if self.current_frame is not None: + ray.draw_texture(self.current_frame, 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() + if hasattr(self, 'current_frame') and self.current_frame: + ray.unload_texture(self.current_frame) + if hasattr(self, 'last_frame') and self.last_frame: + ray.unload_texture(self.last_frame) diff --git a/main.py b/main.py index c626f84..2f2e010 100644 --- a/main.py +++ b/main.py @@ -42,12 +42,15 @@ def main(): start_song = False while not ray.window_should_close(): ray.begin_drawing() - ray.clear_background(ray.WHITE) + screen = screen_mapping[current_screen] + if screen == title_screen: + ray.clear_background(ray.BLACK) + else: + ray.clear_background(ray.WHITE) if ray.is_key_pressed(ray.KeyboardKey.KEY_F11): ray.toggle_fullscreen() - screen = screen_mapping[current_screen] if screen == game_screen and not start_song: game_screen.init_tja(sys.argv[1], sys.argv[2]) start_song = True diff --git a/title.py b/title.py index b8b584a..7490ae2 100644 --- a/title.py +++ b/title.py @@ -2,6 +2,8 @@ import pyray as ray import numpy as np import vlc import cv2 +import os +import random from global_funcs import Animation, VideoPlayer, get_current_ms class TitleScreen: @@ -9,7 +11,14 @@ class TitleScreen: self.width = width self.height = height self.op_video = VideoPlayer('Videos\\OP.mp4') + self.attract_videos = [] + for root, folder, files in os.walk('Videos\\attract_videos'): + for file in files: + if file.endswith('.mp4'): + self.attract_videos.append(VideoPlayer(root + '\\' + file)) + self.current_attract_video = None self.warning = None + self.scene = None self.load_textures() def load_textures(self): @@ -32,39 +41,61 @@ class TitleScreen: 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.texture_warning_x_1 = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00014.png') + self.texture_warning_x_2 = ray.load_texture('Graphics\\lumendata\\attract\\keikoku\\keikoku_img00015.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') + self.sound_warning_message = ray.load_sound('Sounds\\title\\VO_ATTRACT_3.ogg') + self.sound_warning_error = ray.load_sound('Sounds\\title\\SE_ATTRACT_1.ogg') - def animation_manager(self): - if self.warning is not None: - self.warning.update(get_current_ms(), self) + self.texture_black = ray.load_texture('Graphics\\lumendata\\attract\\movie\\movie_img00000.png') - def update(self): - self.animation_manager() + def scene_manager(self): if self.op_video is not None: + self.scene = 'Opening Video' self.op_video.update() if all(self.op_video.is_finished): self.op_video = None self.warning = WarningBoard(get_current_ms(), self) + elif self.warning is not None: + self.scene = 'Warning Board' + self.warning.update(get_current_ms(), self) + if self.warning.is_finished: + self.warning = None + self.current_attract_video = random.choice(self.attract_videos) + elif self.current_attract_video is not None: + self.scene = 'Attract Video' + self.current_attract_video.update() + if all(self.current_attract_video.is_finished): + self.current_attract_video = None + self.op_video = VideoPlayer('Videos\\OP.mp4') + + + def update(self): + self.scene_manager() 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() + elif self.warning is not None: + 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.warning.draw(self) + elif self.current_attract_video is not None: + self.current_attract_video.draw() + + ray.draw_text(f"Scene: {self.scene}", 20, 40, 20, ray.BLUE) class WarningBoard: def __init__(self, current_ms, title_screen): self.start_ms = current_ms + self.error_time = 4250 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 @@ -83,22 +114,104 @@ class WarningBoard: 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.fade_animation_2 = Animation(current_ms, 500, 'fade') + self.fade_animation_2.params['initial_opacity'] = 0.0 + self.fade_animation_2.params['final_opacity'] = 1.0 + self.fade_animation_2.params['delay'] = 500 + + self.fade_animation_3 = Animation(current_ms, 50, 'fade') + self.fade_animation_3.params['delay'] = 16.67 + self.fade_animation_3.params['initial_opacity'] = 0.75 + + self.resize_animation_1 = Animation(current_ms, 166.67, 'texture_resize') + self.resize_animation_1.params['initial_size'] = 1.0 + self.resize_animation_1.params['final_size'] = 1.5 + self.resize_animation_1.params['delay'] = self.error_time + + self.resize_animation_2 = Animation(current_ms, 166.67, 'texture_resize') + self.resize_animation_2.params['initial_size'] = 1.5 + self.resize_animation_2.params['final_size'] = 1.0 + self.resize_animation_2.params['delay'] = self.error_time + 166.67 + + self.fade_animation_4 = Animation(current_ms, 166.67, 'fade') + self.fade_animation_4.params['delay'] = self.error_time + self.fade_animation_4.params['initial_opacity'] = 0.0 + self.fade_animation_4.params['final_opacity'] = 1.0 + + self.fade_animation_5 = Animation(current_ms, 166.67, 'fade') + self.fade_animation_5.params['delay'] = self.error_time + 166.67 + 166.67 + self.fade_animation_5.params['initial_opacity'] = 1.0 + self.fade_animation_5.params['final_opacity'] = 0.0 + + self.fade_animation_6 = Animation(current_ms, 166.67, 'fade') + self.fade_animation_6.params['delay'] = self.error_time + 166.67 + 166.67 + self.fade_animation_6.params['initial_opacity'] = 0.0 + self.fade_animation_6.params['final_opacity'] = 1.0 + + self.resize_animation_3 = Animation(current_ms, 233.34, 'texture_resize') + self.resize_animation_3.params['initial_size'] = 0.5 + self.resize_animation_3.params['final_size'] = 1.5 + + self.fade_animation_7 = Animation(current_ms, 116.67, 'fade') + self.fade_animation_7.params['initial_opacity'] = 0.0 + self.fade_animation_7.params['final_opacity'] = 1.0 + + self.fade_animation_8 = Animation(current_ms, 116.67, 'fade') + self.fade_animation_8.params['delay'] = 116.67 + self.fade_animation_8.params['initial_opacity'] = 1.0 + self.fade_animation_8.params['final_opacity'] = 0.0 + + self.source_rect = ray.Rectangle(0, 0, title_screen.texture_black.width, title_screen.texture_black.height) + self.dest_rect = ray.Rectangle(0, 0, title_screen.width, title_screen.height) + self.character_time = 0 self.character_index_val = 0 self.hit_played = False + self.error_played = False + self.is_finished = False + + self.attract_frame_index = 0 + + def load_next_attract(self, title_screen): + if title_screen.current_attract_video.convert_frames_background(self.attract_frame_index) == 0: + return 0 + self.attract_frame_index += 1 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) + self.fade_animation_2.update(current_ms) + self.fade_animation_3.update(current_ms) + self.fade_animation_4.update(current_ms) + self.fade_animation_5.update(current_ms) + self.fade_animation_6.update(current_ms) + self.resize_animation_1.update(current_ms) + self.resize_animation_2.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): + elapsed_time = current_ms - self.start_ms + if self.character_index(1) != 8: + self.fade_animation_2.params['delay'] = elapsed_time + 500 + if delay <= elapsed_time and not ray.is_sound_playing(title_screen.sound_bachi_swipe): + ray.play_sound(title_screen.sound_warning_message) 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) + elif self.character_index(1) == 8: + if not self.hit_played: + self.hit_played = True + ray.play_sound(title_screen.sound_bachi_hit) + self.resize_animation_3.start_ms = current_ms + self.fade_animation_8.start_ms = current_ms + self.resize_animation_3.update(current_ms) + self.fade_animation_7.update(current_ms) + self.fade_animation_8.update(current_ms) + + if self.error_time + 166.67 <= elapsed_time and not self.error_played: + self.error_played = True + ray.play_sound(title_screen.sound_warning_error) + if self.fade_animation_2.is_finished: + self.is_finished = True def character_index(self, index): elapsed_time = get_current_ms() - self.start_ms @@ -118,6 +231,8 @@ class WarningBoard: elif elapsed_time >= delay + self.character_time: new_index = animation[self.character_index_val][index] self.character_index_val += 1 + self.fade_animation_3.start_ms = get_current_ms() + self.fade_animation_3.duration = int(animation[self.character_index_val][0]) self.character_time += animation[self.character_index_val][0] return new_index else: @@ -133,9 +248,45 @@ class WarningBoard: 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_x_2, 150, 200, ray.fade(ray.WHITE, self.fade_animation_6.attribute)) 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) + if 0 < self.character_index(1): + ray.draw_texture(title_screen.texture_warning_ch2[self.character_index(1)-1], 315, int(y)+100, ray.fade(ray.WHITE, self.fade_animation_3.attribute)) 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) + + if self.resize_animation_1.is_finished: + scale = self.resize_animation_2.attribute + width = title_screen.texture_warning_x_1.width + height = title_screen.texture_warning_x_1.height + x_x = 150 + (width//2) - ((width * scale)//2) + x_y = 200 + (height//2) - ((height * scale)//2) + fade = ray.fade(ray.WHITE, self.fade_animation_5.attribute) + else: + scale = self.resize_animation_1.attribute + width = title_screen.texture_warning_x_1.width + height = title_screen.texture_warning_x_1.height + x_x = 150 + (width//2) - ((width * scale)//2) + x_y = 200 + (height//2) - ((height * scale)//2) + fade = ray.fade(ray.WHITE, self.fade_animation_4.attribute) + x_source = ray.Rectangle(0, 0, width, height) + x_dest = ray.Rectangle(x_x, x_y, width*scale, height*scale) + ray.draw_texture_pro(title_screen.texture_warning_x_1, x_source, x_dest, ray.Vector2(0,0), 0, fade) + + scale = self.resize_animation_3.attribute + width = title_screen.texture_warning_bachi_hit.width + height = title_screen.texture_warning_bachi_hit.height + hit_x = 350 + (width//2) - ((width * scale)//2) + hit_y = 225 + (height//2) - ((height * scale)//2) + hit_source = ray.Rectangle(0, 0, width, height) + hit_dest = ray.Rectangle(hit_x, hit_y, width*scale, height*scale) + if self.fade_animation_7.is_finished: + fade = ray.fade(ray.WHITE, self.fade_animation_8.attribute) + else: + fade = ray.fade(ray.WHITE, self.fade_animation_7.attribute) + ray.draw_texture_pro(title_screen.texture_warning_bachi_hit, hit_source, hit_dest, ray.Vector2(0,0), 0, fade) + + ray.draw_texture_pro(title_screen.texture_black, self.source_rect, self.dest_rect, ray.Vector2(0,0), 0, ray.fade(ray.WHITE, self.fade_animation_2.attribute))