add a loading bar

This commit is contained in:
Yonokid
2025-07-24 17:35:19 -04:00
parent 1111d0d15d
commit 07867f3ee4
7 changed files with 167 additions and 12 deletions

View File

@@ -9,7 +9,6 @@ from raylib.defines import (
RL_SRC_ALPHA, RL_SRC_ALPHA,
) )
from libs import song_hash
from libs.audio import audio from libs.audio import audio
from libs.utils import ( from libs.utils import (
get_config, get_config,
@@ -19,6 +18,7 @@ from libs.utils import (
from scenes.devtest import DevScreen from scenes.devtest import DevScreen
from scenes.entry import EntryScreen from scenes.entry import EntryScreen
from scenes.game import GameScreen from scenes.game import GameScreen
from scenes.loading import LoadScreen
from scenes.result import ResultScreen from scenes.result import ResultScreen
from scenes.settings import SettingsScreen from scenes.settings import SettingsScreen
from scenes.song_select import SongSelectScreen from scenes.song_select import SongSelectScreen
@@ -33,6 +33,7 @@ class Screens:
RESULT = "RESULT" RESULT = "RESULT"
SETTINGS = "SETTINGS" SETTINGS = "SETTINGS"
DEV_MENU = "DEV_MENU" DEV_MENU = "DEV_MENU"
LOADING = "LOADING"
def create_song_db(): def create_song_db():
with sqlite3.connect('scores.db') as con: with sqlite3.connect('scores.db') as con:
@@ -57,8 +58,6 @@ def create_song_db():
print("Scores database created successfully") print("Scores database created successfully")
def main(): def main():
create_song_db()
global_data.song_hashes = song_hash.build_song_hashes()
global_data.config = get_config() global_data.config = get_config()
screen_width: int = global_data.config["video"]["screen_width"] screen_width: int = global_data.config["video"]["screen_width"]
screen_height: int = global_data.config["video"]["screen_height"] screen_height: int = global_data.config["video"]["screen_height"]
@@ -74,13 +73,16 @@ def main():
if global_data.config["video"]["fullscreen"]: if global_data.config["video"]["fullscreen"]:
ray.maximize_window() ray.maximize_window()
current_screen = Screens.TITLE current_screen = Screens.LOADING
audio.init_audio_device() audio.init_audio_device()
create_song_db()
title_screen = TitleScreen(screen_width, screen_height) title_screen = TitleScreen(screen_width, screen_height)
entry_screen = EntryScreen(screen_width, screen_height) entry_screen = EntryScreen(screen_width, screen_height)
song_select_screen = SongSelectScreen(screen_width, screen_height) song_select_screen = SongSelectScreen(screen_width, screen_height)
load_screen = LoadScreen(screen_width, screen_height, song_select_screen)
game_screen = GameScreen(screen_width, screen_height) game_screen = GameScreen(screen_width, screen_height)
result_screen = ResultScreen(screen_width, screen_height) result_screen = ResultScreen(screen_width, screen_height)
settings_screen = SettingsScreen(screen_width, screen_height) settings_screen = SettingsScreen(screen_width, screen_height)
@@ -93,7 +95,8 @@ def main():
Screens.GAME: game_screen, Screens.GAME: game_screen,
Screens.RESULT: result_screen, Screens.RESULT: result_screen,
Screens.SETTINGS: settings_screen, Screens.SETTINGS: settings_screen,
Screens.DEV_MENU: dev_screen Screens.DEV_MENU: dev_screen,
Screens.LOADING: load_screen
} }
target = ray.load_render_texture(screen_width, screen_height) target = ray.load_render_texture(screen_width, screen_height)
ray.set_texture_filter(target.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR) ray.set_texture_filter(target.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR)

View File

@@ -28,6 +28,12 @@ class Background:
self.footer.draw() self.footer.draw()
self.donbg.draw() self.donbg.draw()
def unload(self):
self.donbg.unload()
self.bg_normal.unload()
self.bg_fever.unload()
self.footer.unload()
class DonBG: class DonBG:
@staticmethod @staticmethod
@@ -52,6 +58,11 @@ class DonBGBase:
if self.move.is_finished: if self.move.is_finished:
self.move.restart() self.move.restart()
def unload(self):
for texture_group in self.textures:
for texture in self.textures[texture_group]:
ray.unload_texture(texture)
class DonBG1(DonBGBase): class DonBG1(DonBGBase):
def __init__(self, index: int, screen_width: int, screen_height: int, player_num: int): def __init__(self, index: int, screen_width: int, screen_height: int, player_num: int):
super().__init__(index, screen_width, screen_height, player_num) super().__init__(index, screen_width, screen_height, player_num)
@@ -114,6 +125,11 @@ class BGNormalBase:
self.name = 'bg_nomal_a_' + str(index).zfill(2) self.name = 'bg_nomal_a_' + str(index).zfill(2)
self.textures = (load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.name}.zip'))) self.textures = (load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.name}.zip')))
def unload(self):
for texture_group in self.textures:
for texture in self.textures[texture_group]:
ray.unload_texture(texture)
class BGNormal1(BGNormalBase): class BGNormal1(BGNormalBase):
def __init__(self, index: int, screen_width: int, screen_height: int): def __init__(self, index: int, screen_width: int, screen_height: int):
super().__init__(index, screen_width, screen_height) super().__init__(index, screen_width, screen_height)
@@ -284,6 +300,11 @@ class BGFeverBase:
self.vertical_move = Animation.create_move(1300, start_position=0, total_distance=50, reverse_delay=0) self.vertical_move = Animation.create_move(1300, start_position=0, total_distance=50, reverse_delay=0)
self.horizontal_move = Animation.create_move(5000, start_position=0, total_distance=self.textures[self.name][2].width) self.horizontal_move = Animation.create_move(5000, start_position=0, total_distance=self.textures[self.name][2].width)
def unload(self):
for texture_group in self.textures:
for texture in self.textures[texture_group]:
ray.unload_texture(texture)
class BGFever4(BGFeverBase): class BGFever4(BGFeverBase):
def __init__(self, index: int, screen_width: int, screen_height: int): def __init__(self, index: int, screen_width: int, screen_height: int):
super().__init__(index, screen_width, screen_height) super().__init__(index, screen_width, screen_height)
@@ -308,5 +329,9 @@ class Footer:
self.screen_height = screen_height self.screen_height = screen_height
self.name = 'dodai_a_' + str(index).zfill(2) self.name = 'dodai_a_' + str(index).zfill(2)
self.textures = (load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.name}.zip'))) self.textures = (load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.name}.zip')))
def unload(self):
for texture_group in self.textures:
for texture in self.textures[texture_group]:
ray.unload_texture(texture)
def draw(self): def draw(self):
ray.draw_texture(self.textures[self.name][0], 0, self.screen_height - self.textures[self.name][0].height + 20, ray.WHITE) ray.draw_texture(self.textures[self.name][0], 0, self.screen_height - self.textures[self.name][0].height + 20, ray.WHITE)

View File

@@ -56,6 +56,8 @@ def build_song_hashes(output_dir=Path("cache")):
print('Pulled latest from', root_path) print('Pulled latest from', root_path)
all_tja_files.extend(root_path.rglob("*.tja")) all_tja_files.extend(root_path.rglob("*.tja"))
global_data.total_songs = len(all_tja_files)
files_to_process = [] files_to_process = []
# O(n) pass to identify which files need processing # O(n) pass to identify which files need processing
@@ -86,6 +88,10 @@ def build_song_hashes(output_dir=Path("cache")):
del path_to_hash[tja_path_str] del path_to_hash[tja_path_str]
# Process only files that need updating # Process only files that need updating
song_count = 0
total_songs = len(files_to_process)
if total_songs > 0:
global_data.total_songs = total_songs
for tja_path in files_to_process: for tja_path in files_to_process:
tja_path_str = str(tja_path) tja_path_str = str(tja_path)
current_modified = tja_path.stat().st_mtime current_modified = tja_path.stat().st_mtime
@@ -120,6 +126,8 @@ def build_song_hashes(output_dir=Path("cache")):
# Update both indexes # Update both indexes
path_to_hash[tja_path_str] = hash_val path_to_hash[tja_path_str] = hash_val
global_data.song_paths[tja_path] = hash_val global_data.song_paths[tja_path] = hash_val
song_count += 1
global_data.song_progress = song_count / total_songs
# Save both files # Save both files
with open(output_path, "w", encoding="utf-8") as f: with open(output_path, "w", encoding="utf-8") as f:

View File

@@ -234,6 +234,9 @@ class GlobalData:
config: dict = field(default_factory=lambda: dict()) config: dict = field(default_factory=lambda: dict())
song_hashes: dict[str, list[dict]] = field(default_factory=lambda: dict()) #Hash to path song_hashes: dict[str, list[dict]] = field(default_factory=lambda: dict()) #Hash to path
song_paths: dict[Path, str] = field(default_factory=lambda: dict()) #path to hash song_paths: dict[Path, str] = field(default_factory=lambda: dict()) #path to hash
song_progress: float = 0.0
total_songs: int = 0
global_data = GlobalData() global_data = GlobalData()
class OutlinedText: class OutlinedText:

View File

@@ -142,6 +142,7 @@ class GameScreen:
self.song_started = False self.song_started = False
self.end_ms = 0 self.end_ms = 0
self.movie = None self.movie = None
self.background.unload()
return next_screen return next_screen
def write_score(self): def write_score(self):

108
scenes/loading.py Normal file
View File

@@ -0,0 +1,108 @@
import threading
import pyray as ray
from libs.song_hash import build_song_hashes
from libs.utils import global_data
from scenes.song_select import SongSelectScreen
class LoadScreen:
def __init__(self, width: int, height: int, song_select_screen: SongSelectScreen):
self.width = width
self.height = height
self.screen_init = False
self.songs_loaded = False
self.navigator_started = False
self.loading_complete = False
self.song_select_screen = song_select_screen
# Progress bar settings
self.progress_bar_width = width * 0.6
self.progress_bar_height = 20
self.progress_bar_x = (width - self.progress_bar_width) // 2
self.progress_bar_y = height * 0.7
# Thread references
self.loading_thread = None
self.navigator_thread = None
def _load_song_hashes(self):
"""Background thread function to load song hashes"""
try:
global_data.song_hashes = build_song_hashes()
self.songs_loaded = True
except Exception as e:
print(f"Error loading song hashes: {e}")
self.songs_loaded = True
def _load_navigator(self):
"""Background thread function to load navigator"""
try:
self.song_select_screen.load_navigator()
self.loading_complete = True
except Exception as e:
print(f"Error loading navigator: {e}")
self.loading_complete = True
def on_screen_start(self):
if not self.screen_init:
self.loading_thread = threading.Thread(target=self._load_song_hashes)
self.loading_thread.daemon = True
self.loading_thread.start()
self.screen_init = True
def on_screen_end(self, next_screen: str):
self.screen_init = False
if self.loading_thread and self.loading_thread.is_alive():
self.loading_thread.join(timeout=1.0)
if self.navigator_thread and self.navigator_thread.is_alive():
self.navigator_thread.join(timeout=1.0)
return next_screen
def update(self):
self.on_screen_start()
if self.songs_loaded and not self.navigator_started:
self.navigator_thread = threading.Thread(target=self._load_navigator)
self.navigator_thread.daemon = True
self.navigator_thread.start()
self.navigator_started = True
if self.loading_complete:
return self.on_screen_end('TITLE')
def draw(self):
ray.draw_rectangle(0, 0, self.width, self.height, ray.BLACK)
# Draw progress bar background
ray.draw_rectangle(
int(self.progress_bar_x),
int(self.progress_bar_y),
int(self.progress_bar_width),
int(self.progress_bar_height),
ray.DARKGRAY
)
# Draw progress bar fill
progress = max(0.0, min(1.0, global_data.song_progress))
fill_width = self.progress_bar_width * progress
if fill_width > 0:
ray.draw_rectangle(
int(self.progress_bar_x),
int(self.progress_bar_y),
int(fill_width),
int(self.progress_bar_height),
ray.RED
)
# Draw border
ray.draw_rectangle_lines(
int(self.progress_bar_x),
int(self.progress_bar_y),
int(self.progress_bar_width),
int(self.progress_bar_height),
ray.WHITE
)

View File

@@ -33,6 +33,7 @@ class SongSelectScreen:
self.screen_width = screen_width self.screen_width = screen_width
self.screen_height = screen_height self.screen_height = screen_height
def load_navigator(self):
self.navigator = FileNavigator(self.root_dir) self.navigator = FileNavigator(self.root_dir)
def load_textures(self): def load_textures(self):
@@ -47,7 +48,6 @@ class SongSelectScreen:
self.sound_ura_switch = audio.load_sound(sounds_dir / 'song_select' / 'SE_SELECT [4].ogg') self.sound_ura_switch = audio.load_sound(sounds_dir / 'song_select' / 'SE_SELECT [4].ogg')
audio.set_sound_volume(self.sound_ura_switch, 0.25) audio.set_sound_volume(self.sound_ura_switch, 0.25)
self.sound_bgm = audio.load_sound(sounds_dir / "song_select" / "JINGLE_GENRE [1].ogg") self.sound_bgm = audio.load_sound(sounds_dir / "song_select" / "JINGLE_GENRE [1].ogg")
#self.sound_cancel = audio.load_sound(sounds_dir / "cancel.wav")
def on_screen_start(self): def on_screen_start(self):
if not self.screen_init: if not self.screen_init:
@@ -369,14 +369,15 @@ class SongBox:
615: 532, 615: 532,
} }
def __init__(self, name: str, texture_index: int, is_dir: bool, tja: Optional[TJAParser] = None, def __init__(self, name: str, texture_index: int, is_dir: bool, tja: Optional[TJAParser] = None,
tja_count: Optional[int] = None, box_texture: Optional[ray.Texture] = None, name_texture_index: Optional[int] = None): tja_count: Optional[int] = None, box_texture: Optional[str] = None, name_texture_index: Optional[int] = None):
self.text_name = name self.text_name = name
self.texture_index = texture_index self.texture_index = texture_index
if name_texture_index is None: if name_texture_index is None:
self.name_texture_index = texture_index self.name_texture_index = texture_index
else: else:
self.name_texture_index = name_texture_index self.name_texture_index = name_texture_index
self.box_texture = box_texture self.box_texture_path = box_texture
self.box_texture = None
self.scores = dict() self.scores = dict()
self.crown = dict() self.crown = dict()
self.position = -11111 self.position = -11111
@@ -398,11 +399,8 @@ class SongBox:
self.genre_distance = 0 self.genre_distance = 0
self.tja_count = tja_count self.tja_count = tja_count
self.tja_count_text = None self.tja_count_text = None
if self.tja_count is not None and self.tja_count != 0:
self.tja_count_text = OutlinedText(str(self.tja_count), 35, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5)#, horizontal_spacing=1.2)
self.tja = tja self.tja = tja
self.hash = dict() self.hash = dict()
self.update(False)
def reset(self): def reset(self):
if self.black_name is not None: if self.black_name is not None:
@@ -477,6 +475,10 @@ class SongBox:
self.open_anim = Animation.create_move(133, start_position=0, total_distance=150, delay=83.33) self.open_anim = Animation.create_move(133, start_position=0, total_distance=150, delay=83.33)
self.open_fade = Animation.create_fade(200, initial_opacity=0, final_opacity=1.0) self.open_fade = Animation.create_fade(200, initial_opacity=0, final_opacity=1.0)
self.wait = get_current_ms() self.wait = get_current_ms()
if self.tja_count is not None and self.tja_count > 0 and self.tja_count_text is None:
self.tja_count_text = OutlinedText(str(self.tja_count), 35, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5)#, horizontal_spacing=1.2)
if self.box_texture is None and self.box_texture_path is not None:
self.box_texture = ray.load_texture(self.box_texture_path)
elif not self.is_open: elif not self.is_open:
if self.black_name is not None: if self.black_name is not None:
@@ -1021,6 +1023,7 @@ class FileNavigator:
self.history = [] self.history = []
self.box_open = False self.box_open = False
self.genre_bg = None self.genre_bg = None
self.song_count = 0
# Generate all objects upfront # Generate all objects upfront
self._generate_all_objects() self._generate_all_objects()
@@ -1062,7 +1065,7 @@ class FileNavigator:
name, texture_index, collection = self._parse_box_def(dir_path) name, texture_index, collection = self._parse_box_def(dir_path)
box_png_path = dir_path / "box.png" box_png_path = dir_path / "box.png"
if box_png_path.exists(): if box_png_path.exists():
box_texture = ray.load_texture(str(box_png_path)) box_texture = str(box_png_path)
# Count TJA files for this directory # Count TJA files for this directory
tja_count = self._count_tja_files(dir_path) tja_count = self._count_tja_files(dir_path)
@@ -1104,6 +1107,8 @@ class FileNavigator:
song_key = str(tja_path) song_key = str(tja_path)
if song_key not in self.all_song_files: if song_key not in self.all_song_files:
song_obj = SongFile(tja_path, tja_path.name, texture_index) song_obj = SongFile(tja_path, tja_path.name, texture_index)
self.song_count += 1
global_data.song_progress = self.song_count / global_data.total_songs
if song_obj.is_recent: if song_obj.is_recent:
self.new_items.append(SongFile(tja_path, tja_path.name, 620, name_texture_index=texture_index)) self.new_items.append(SongFile(tja_path, tja_path.name, 620, name_texture_index=texture_index))
self.all_song_files[song_key] = song_obj self.all_song_files[song_key] = song_obj
@@ -1128,6 +1133,8 @@ class FileNavigator:
if song_key not in self.all_song_files: if song_key not in self.all_song_files:
try: try:
song_obj = SongFile(tja_path, tja_path.name, 620) song_obj = SongFile(tja_path, tja_path.name, 620)
self.song_count += 1
global_data.song_progress = self.song_count / global_data.total_songs
self.all_song_files[song_key] = song_obj self.all_song_files[song_key] = song_obj
except Exception as e: except Exception as e:
print(f"Error creating SongFile for {tja_path}: {e}") print(f"Error creating SongFile for {tja_path}: {e}")