diff --git a/pyghelper/animation_manager.py b/pyghelper/animation_manager.py index 7a05ca7..e20c41b 100644 --- a/pyghelper/animation_manager.py +++ b/pyghelper/animation_manager.py @@ -1,9 +1,5 @@ -from typing import Union - import pygame -import pyghelper.utils as utils - class Animation: """ @@ -11,10 +7,10 @@ class Animation: """ def __init__( - self, - sprites: list[pygame.Surface], - durations: Union[int, list[int]], - starting_sprite_index: int = 0 + self, + sprites: list[pygame.Surface], + durations: list[float], + starting_sprite_index: int = 0 ): """ Initialize the animation with the specified sprites and durations. @@ -32,44 +28,34 @@ def __init__( The index of the first sprite of the animation. """ + assert len(sprites) > 0 + assert len(durations) > 0 + self.sprites = sprites self.sprites_count = len(self.sprites) - self.durations = self.__correct_durations(durations, self.sprites_count) - self.clock: int = 0 - self.current_sprite_index: int = starting_sprite_index - self.cumulated_durations: list[int] = [sum(self.durations[:i]) for i in range(1, self.sprites_count + 1)] - self.animation_duration: int = self.cumulated_durations[-1] # The last cumulated sum is the total sum - def __correct_durations(self, durations: Union[int, list[int]], sprites_count: int) -> list[int]: - if type(durations) == int: - return [durations for _ in range(sprites_count)] + self.durations: list[float] = durations + self.cumulated_durations: list[float] = [sum(self.durations[:i]) for i in range(1, self.sprites_count + 1)] + self.animation_duration: float = self.cumulated_durations[-1] # The last cumulated sum is the total sum - if len(durations) < sprites_count: - missing_count = sprites_count - len(durations) - durations.extend([durations[-1]] * missing_count) - - elif len(durations) > sprites_count: - durations = durations[:sprites_count] - - return durations + self.current_sprite_index: int = starting_sprite_index + self.time: float = self.cumulated_durations[starting_sprite_index] def __get_current_sprite_index(self) -> int: - return next( - index - for index, sprite_start_time in enumerate(self.cumulated_durations) - if sprite_start_time >= self.clock - ) + for index, sprite_start_time in enumerate(self.cumulated_durations): + if sprite_start_time >= self.time: + return index - def play(self, ticks: int = 1) -> None: + def play(self, elapsed_time: float): """Play the specified number of ticks of the animation. Parameters ---------- - ticks : int, default = 1 - Number of ticks to play. + elapsed_time : float + elapsed time in second since last call """ - self.clock = (self.clock + ticks) % self.animation_duration + self.time = (self.time + elapsed_time) % self.animation_duration self.current_sprite_index = self.__get_current_sprite_index() def get_current_sprite(self) -> pygame.Surface: @@ -83,12 +69,15 @@ class AnimationManager: A class to manages multiple Animation classes simultaneously. """ - def __init__(self): + def __init__(self, animations: list[Animation] = None): """Initialize the manager.""" - self.animations: dict[str, Animation] = dict() + if animations is None: + self.animations: list[Animation] = list() + else: + self.animations = animations - def add_animation(self, animation: Animation, name: str) -> None: + def add_animation(self, animation: Animation) -> None: """ Add the specified animation to the manager. @@ -100,68 +89,29 @@ def add_animation(self, animation: Animation, name: str) -> None: Name of the animation. """ - if type(animation) != Animation: - raise TypeError("The animation should be of type Animation.") - - if name == "": - raise ValueError("Animation name cannot be empty.") + assert isinstance(animation, Animation), "The animation should be of type Animation." + self.animations.append(animation) - self.animations[name] = animation - - def remove_animation(self, name: str) -> Animation: + def remove_animation(self, animation: Animation): """ Remove and return the specified animation from the manager. Parameters ---------- - name : str - Name of the animation to remove. - """ - - if name not in self.animations: - raise ValueError(f"This animation ('{name}') does not exist.") - - return self.animations.pop(name) - - def __getitem__(self, name: str) -> Animation: - if name not in self.animations: - raise IndexError(f"This animation ('{name}') does not exist.") - - return self.animations[name] - - def get_animation(self, name: str) -> Animation: - """ - Return the animation at the specified index. Can be accessed with square brackets. - - Parameters - ---------- - name : str - Name of the animation to get. - """ - - return self[name] - - def get_current_sprite(self, name: str) -> pygame.Surface: - """ - Return the sprite of the specified animation. - - Parameters - ---------- - name : str - Name of the animation to get the sprite of. + animation : Animation + Animation object to remove """ - return self[name].get_current_sprite() + self.animations.remove(animation) - def play_all(self, ticks: int = 1) -> None: + def play_all(self, elapsed_time: float) -> None: """ Play the specified number of ticks of all the animations. Parameters ---------- - ticks : int, default = 1 - Number of ticks to play. + elapsed_time : float + elapsed time in second since last call """ - for animation in self.animations.values(): - animation.play(ticks) + [animation.play(elapsed_time) for animation in self.animations] diff --git a/pyghelper/event_manager.py b/pyghelper/event_manager.py index 98c2d29..7fbfbd8 100644 --- a/pyghelper/event_manager.py +++ b/pyghelper/event_manager.py @@ -1,10 +1,9 @@ -import inspect from typing import Callable import pygame import pyghelper.config as config -import pyghelper.utils as utils +import window class EventManager: @@ -24,39 +23,18 @@ def __init__(self, use_default_quit_callback: bool = True): for the 'QUIT' event (default: True). """ - self.premade_events = { - pygame.QUIT: None, - pygame.KEYDOWN: None, - pygame.KEYUP: None, - pygame.MOUSEMOTION: None, - pygame.MOUSEBUTTONDOWN: None, - pygame.MOUSEBUTTONUP: None, - config.MUSICENDEVENT: None - } + self.quit_callback: Callable[[], None] = None + self.key_down_callback: Callable[[dict], None] = None + self.key_up_callback: Callable[[dict], None] = None + self.mouse_motion_callback: Callable[[dict], None] = None + self.mouse_button_down_callback: Callable[[dict], None] = None + self.mouse_button_up_callback: Callable[[dict], None] = None + self.music_end_callback: Callable[[], None] = None if use_default_quit_callback: - self.premade_events[pygame.QUIT] = utils.Window.close - - self.custom_events = dict() - - - def __get_parameters_count(self, function: Callable) -> int : - return len(inspect.signature(function).parameters) - - - def __check_function(self, callback: Callable, expected_parameters_count: int) -> None: - if not callable(callback): - raise TypeError("The callback argument is not callable.") - - parameters_count = self.__get_parameters_count(callback) - if parameters_count != expected_parameters_count: - raise ValueError(f"The callback has {parameters_count} parameters instead of {expected_parameters_count}.") - - - def __set_premade_callback(self, event_type: int, callback: Callable[[dict], None], expected_parameters_count: int) -> None: - self.__check_function(callback, expected_parameters_count) - self.premade_events[event_type] = callback + self.quit_callback = window.Window.close + self.custom_events: dict[str, Callable] = dict() def set_quit_callback(self, callback: Callable[[], None]): """ @@ -69,10 +47,9 @@ def set_quit_callback(self, callback: Callable[[], None]): It should not have any parameters. """ - self.__set_premade_callback(pygame.QUIT, callback, expected_parameters_count=0) - + self.quit_callback = callback - def set_keydown_callback(self, callback: Callable[[dict], None]): + def set_key_down_callback(self, callback: Callable[[dict], None]): """ Set the callback for the 'KEYDOWN' event. @@ -83,10 +60,9 @@ def set_keydown_callback(self, callback: Callable[[dict], None]): It should have only one parameter : a dictionary containing the event data. """ - self.__set_premade_callback(pygame.KEYDOWN, callback, expected_parameters_count=1) + self.key_down_callback = callback - - def set_keyup_callback(self, callback: Callable[[dict], None]): + def set_key_up_callback(self, callback: Callable[[dict], None]): """ Set the callback for the 'KEYUP' event. @@ -97,10 +73,9 @@ def set_keyup_callback(self, callback: Callable[[dict], None]): It should have only one parameter : a dictionary containing the event data. """ - self.__set_premade_callback(pygame.KEYUP, callback, expected_parameters_count=1) - + self.key_up_callback = callback - def set_mousemotion_callback(self, callback: Callable[[dict], None]): + def set_mouse_motion_callback(self, callback: Callable[[dict], None]): """ Set the callback for the 'MOUSEMOTION' event. @@ -111,10 +86,9 @@ def set_mousemotion_callback(self, callback: Callable[[dict], None]): It should have only one parameter : a dictionary containing the event data. """ - self.__set_premade_callback(pygame.MOUSEMOTION, callback, expected_parameters_count=1) - + self.mouse_motion_callback = callback - def set_mousebuttondown_callback(self, callback: Callable[[dict], None]): + def set_mouse_button_down_callback(self, callback: Callable[[dict], None]): """ Set the callback for the 'MOUSEBUTTONDOWN' event. @@ -125,10 +99,9 @@ def set_mousebuttondown_callback(self, callback: Callable[[dict], None]): It should have only one parameter : a dictionary containing the event data. """ - self.__set_premade_callback(pygame.MOUSEBUTTONDOWN, callback, expected_parameters_count=1) + self.mouse_button_down_callback = callback - - def set_mousebuttonup_callback(self, callback: Callable[[dict], None]): + def set_mouse_button_up_callback(self, callback: Callable[[dict], None]): """ Set the callback for the 'MOUSEBUTTONUP' event. @@ -139,10 +112,9 @@ def set_mousebuttonup_callback(self, callback: Callable[[dict], None]): It should have only one parameter : a dictionary containing the event data. """ - self.__set_premade_callback(pygame.MOUSEBUTTONUP, callback, expected_parameters_count=1) - + self.mouse_button_up_callback = callback - def set_musicend_callback(self, callback: Callable[[], None]): + def set_music_end_callback(self, callback: Callable[[], None]): """ Set the callback for the music end event (see SoundManager docs). @@ -153,8 +125,7 @@ def set_musicend_callback(self, callback: Callable[[], None]): It should not have any parameters. """ - self.__set_premade_callback(config.MUSICENDEVENT, callback, expected_parameters_count=0) - + self.music_end_callback = callback def add_custom_event(self, event_name: str, callback: Callable[[dict], None]): """ @@ -174,13 +145,10 @@ def add_custom_event(self, event_name: str, callback: Callable[[dict], None]): containing the name of the event. """ - if event_name is None: - raise ValueError("Event name cannot be None.") + assert event_name is not None, "Event name cannot be None." - self.__check_function(callback, expected_parameters_count=1) self.custom_events[event_name] = callback - def listen(self) -> bool: """Listen for incoming events, and call the right function accordingly. Returns True if it could fetch events, False otherwise. @@ -190,19 +158,31 @@ def listen(self) -> bool: return False for event in pygame.event.get(): - if event.type == pygame.QUIT and self.premade_events[pygame.QUIT] is not None: - self.premade_events[pygame.QUIT]() + event_type = event.type + if event_type == pygame.QUIT and self.quit_callback is not None: + self.quit_callback() - elif event.type == pygame.USEREVENT: - event_name = event.dict.get('name', None) - if event_name in self.custom_events: - self.custom_events[event_name](event.dict) + elif event_type == pygame.KEYDOWN and self.key_down_callback is not None: + self.key_down_callback(event.dict) - elif event.type == config.MUSICENDEVENT and self.premade_events[config.MUSICENDEVENT] is not None: - self.premade_events[config.MUSICENDEVENT]() + elif event_type == pygame.KEYUP and self.key_up_callback is not None: + self.key_up_callback(event.dict) - else: - if event.type in self.premade_events and self.premade_events[event.type] is not None: - self.premade_events[event.type](event.dict) + elif event_type == pygame.MOUSEMOTION and self.mouse_motion_callback is not None: + self.mouse_motion_callback(event.dict) + + elif event_type == pygame.MOUSEBUTTONDOWN and self.mouse_button_down_callback is not None: + self.mouse_button_down_callback(event.dict) + + elif event_type == pygame.MOUSEBUTTONUP and self.mouse_button_up_callback is not None: + self.mouse_button_up_callback(event.dict) + + elif event_type == config.MUSICENDEVENT and self.music_end_callback is not None: + self.music_end_callback() + + elif event_type == pygame.USEREVENT: + event_name: str = event.dict.get('name', None) + if event_name in self.custom_events: + self.custom_events[event_name](event.dict) return True diff --git a/pyghelper/images.py b/pyghelper/images.py index 8f454a4..28d8373 100644 --- a/pyghelper/images.py +++ b/pyghelper/images.py @@ -68,8 +68,8 @@ def __get_surface(surface: Union[str, pygame.Surface]) -> pygame.Surface: @staticmethod def slice_by_columns( - sprite_sheet: Union[str, pygame.Surface], - sprites_count: int + sprite_sheet: Union[str, pygame.Surface], + sprites_count: int ) -> List[pygame.Surface]: """ slice by columns the given sprite sheet into the specified number of surfaces. @@ -102,8 +102,8 @@ def slice_by_columns( @staticmethod def slice_by_rows( - sprite_sheet: Union[str, pygame.Surface], - sprites_count: int + sprite_sheet: Union[str, pygame.Surface], + sprites_count: int ) -> List[pygame.Surface]: """ slice by rows the given sprite sheet into the specified number of surfaces. @@ -142,9 +142,9 @@ def slice_by_rows( @staticmethod def __slice_vertically_then_horizontally( - sprite_sheet: Union[str, pygame.Surface], - sprites_count_width: int, - sprites_count_height: int + sprite_sheet: Union[str, pygame.Surface], + sprites_count_width: int, + sprites_count_height: int ) -> List[List[pygame.Surface]]: """""" @@ -158,9 +158,9 @@ def __slice_vertically_then_horizontally( @staticmethod def __slice_horizontally_then_vertically( - sprite_sheet: Union[str, pygame.Surface], - sprites_count_width: int, - sprites_count_height: int + sprite_sheet: Union[str, pygame.Surface], + sprites_count_width: int, + sprites_count_height: int ) -> List[List[pygame.Surface]]: """""" @@ -174,10 +174,10 @@ def __slice_horizontally_then_vertically( @staticmethod def slice_both_ways( - sprite_sheet: Union[str, pygame.Surface], - sprites_count_width: int, - sprites_count_height: int, - by_rows_first: bool = True + sprite_sheet: Union[str, pygame.Surface], + sprites_count_width: int, + sprites_count_height: int, + by_rows_first: bool = True ) -> List[List[pygame.Surface]]: """ slice by rows and by columns the given sprite sheet into the specified number of surfaces. diff --git a/pyghelper/sound_manager.py b/pyghelper/sound_manager.py index 5d83fd0..ad53112 100644 --- a/pyghelper/sound_manager.py +++ b/pyghelper/sound_manager.py @@ -1,4 +1,3 @@ -import os import random import pygame @@ -35,11 +34,10 @@ def add_sound(self, sound_path: str, sound_name: str, volume: float = 1.0) -> No sound = pygame.mixer.Sound(sound_path) sound.set_volume(volume) - if not sound_name in self.sounds: + if sound_name not in self.sounds: self.sounds[sound_name] = [] self.sounds[sound_name].append(sound) - def play_random_sound(self, sound_name: str) -> None: """ Play a random sound among those with the specified name. @@ -58,7 +56,6 @@ def play_random_sound(self, sound_name: str) -> None: sound_to_play = random.choice(sound_candidates) sound_to_play.play() - def add_music(self, music_path: str, music_name: str) -> None: """ Add a new music to the manager. @@ -73,7 +70,6 @@ def add_music(self, music_path: str, music_name: str) -> None: self.musics[music_name] = music_path - def __play_music(self, music_path: str, loop: bool, volume: int = 1.0): # Pygame expects -1 to loop and 0 to play the music only once # So we take the negative value so when it is 'True' we send -1 @@ -81,7 +77,6 @@ def __play_music(self, music_path: str, loop: bool, volume: int = 1.0): pygame.mixer.music.play(loops=-int(loop)) pygame.mixer.music.set_volume(volume) - def play_random_music(self, loop: bool = False, volume: int = 1.0): """ Play a random music from the list. @@ -119,7 +114,6 @@ def play_music(self, music_name: str, loop: bool = False, volume: int = 1.0): self.__play_music(self.musics[music_name], loop=loop, volume=volume) - def pause_music(self): """Pause the music.""" diff --git a/pyghelper/utils.py b/pyghelper/window.py similarity index 100% rename from pyghelper/utils.py rename to pyghelper/window.py