From 184734d4d7ea796d457c630821e084821df78e7c Mon Sep 17 00:00:00 2001 From: Leon Grothus <leon.grothus@gmail.com> Date: Mon, 29 Jan 2024 13:32:05 +0100 Subject: [PATCH] fixed score saving and score loading and changed some docs --- main.py | 4 +- source/api/components/mesh.py | 6 +- source/api/components/rigidbody.py | 4 +- source/api/components/simple_movement.py | 4 +- .../{options.py => options_manager.py} | 10 +-- source/api/management/sound_manager.py | 21 ++++-- source/api/scene/base_display.py | 66 +++++++++++++++++ source/api/{management => scene}/scene.py | 72 ++----------------- source/api/utils/transform.py | 8 +-- source/game/objects/flipper.py | 4 +- source/game/scenes/main_menu.py | 20 +++--- source/game/scenes/main_pinball.py | 11 ++- source/game/scenes/options_menu.py | 6 +- source/game/scenes/submenus/end_menu.py | 8 +-- source/game/scenes/submenus/pause_menu.py | 6 +- 15 files changed, 133 insertions(+), 117 deletions(-) rename source/api/management/{options.py => options_manager.py} (89%) create mode 100644 source/api/scene/base_display.py rename source/api/{management => scene}/scene.py (78%) diff --git a/main.py b/main.py index 0efc878..fbe87da 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import pygame from source.api.management.scene_manager import SceneManager from source.api.management.sound_manager import SoundManager import constants -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager """ This is the main file of the game. It initializes PyGame and creates the screen and clock. It also creates the sound manager and scene manager. @@ -13,7 +13,7 @@ The main event loop is also located here. # Initialize PyGame pygame.init() -options = Options() # Load options +options = OptionsManager() # Load options screen = pygame.display.set_mode(options.resolution) # Create the screen clock = pygame.time.Clock() # Create clock diff --git a/source/api/components/mesh.py b/source/api/components/mesh.py index 75b786b..b244ce4 100644 --- a/source/api/components/mesh.py +++ b/source/api/components/mesh.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from pygame import Color, Vector2 from source.api.components.component import Component -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class Mesh(Component, ABC): @@ -111,7 +111,7 @@ class CircleMesh(Mesh): dict: The serialized representation of the CircleMesh object. """ return { - "radius": self.radius/Options().asf + "radius": self.radius/OptionsManager().asf } def deserialize(self, data: dict) -> 'CircleMesh': @@ -124,7 +124,7 @@ class CircleMesh(Mesh): Returns: CircleMesh: The deserialized CircleMesh object. """ - self.radius = data["radius"]*Options().asf + self.radius = data["radius"]*OptionsManager().asf return self def rotate(self, angle: float) -> None: diff --git a/source/api/components/rigidbody.py b/source/api/components/rigidbody.py index f8b3828..9895ea3 100644 --- a/source/api/components/rigidbody.py +++ b/source/api/components/rigidbody.py @@ -5,7 +5,7 @@ from source.api.components.collider import CircleCollider, Collider, PolygonColl from source.api.components.component import Component from source.api.objects.game_object import GameObject from constants import GRAVITY, AIR_FRICTION, PADDLE_COLLISION_DAMPING, PTPF -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager from source.api.utils.utils import clamp @@ -54,7 +54,7 @@ class Rigidbody(Component): self.acceleration: Vector2 = Vector2(0, 0) self.collider: CircleCollider = None # type: ignore - self.asf = Options().asf + self.asf = OptionsManager().asf def set_parent(self, parent: GameObject) -> None: """ diff --git a/source/api/components/simple_movement.py b/source/api/components/simple_movement.py index 20521d2..de5735c 100644 --- a/source/api/components/simple_movement.py +++ b/source/api/components/simple_movement.py @@ -2,7 +2,7 @@ import pygame import math from source.api.components.component import Component -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class SimpleMovement(Component): """ @@ -44,7 +44,7 @@ class SimpleMovement(Component): self.move_type = move_type self.direction = 1 self.t = 0 - self.asf = Options().asf + self.asf = OptionsManager().asf def on_update(self, delta_time: float) -> None: """ diff --git a/source/api/management/options.py b/source/api/management/options_manager.py similarity index 89% rename from source/api/management/options.py rename to source/api/management/options_manager.py index 7af7f42..bc8cace 100644 --- a/source/api/management/options.py +++ b/source/api/management/options_manager.py @@ -4,7 +4,7 @@ from source.api.management.sound_manager import SoundManager from constants import PROJECT_PATH # Singelton -class Options: +class OptionsManager: """ Singleton class to represent the options. @@ -27,7 +27,7 @@ class Options: _instance = None - def __new__(cls) -> 'Options': + def __new__(cls) -> 'OptionsManager': """ Create a new instance of the Options class if it does not exist yet. @@ -35,7 +35,7 @@ class Options: Options: The instance of the Options class. """ if cls._instance is None: - cls._instance = super(Options, cls).__new__(cls) + cls._instance = super(OptionsManager, cls).__new__(cls) cls._instance.init() return cls._instance @@ -68,7 +68,9 @@ class Options: self.master_volume = data.get('master_volume', 50) # Default to 50 if 'master_volume' is not in the JSON file self.music_volume = data.get('music_volume', 50) # Default to 50 if 'music_volume' is not in the JSON file self.sfx_volume = data.get('sfx_volume', 50) # Default to 50 if 'sfx_volume' is not in the JSON file - self.user_name = data.get('user_name', 'Player') # Default to 'Player' if 'user_name' is not in the JSON file + self.user_name = data.get('user_name', 'Player') # Default to 'Player' if 'user_name' is not in the JSON file + if(self.user_name == ''): + self.user_name = 'Player' def save(self) -> None: """ diff --git a/source/api/management/sound_manager.py b/source/api/management/sound_manager.py index b61f32d..5f68542 100644 --- a/source/api/management/sound_manager.py +++ b/source/api/management/sound_manager.py @@ -7,19 +7,26 @@ from pygame.mixer import Sound from constants import ASSETS_PATH # Singelton - - class SoundManager: """ A class to represent the options. - This class is a singleton and can be accessed by calling Options(). + This class is a singleton and can be accessed by calling SoundManager(). Attributes: - asf (float): The application scale factor. - master_volume (float): The master volume. - music_volume (float): The music volume. - sfx_volume (float): The sound effects volume. + music_files (list): A list of music files. + current_music (str): The current music file. + options (Options): The options. + + Methods: + __new__(cls) -> 'SoundManager' + set_options(self, options) -> None + init(self) -> None + load_music(self) -> None + play_music(self) -> None + update(self, events: list[pygame.event.Event]) -> None + play_sfx(self, sound: Sound) -> None + update_volume(self) -> None """ _instance = None diff --git a/source/api/scene/base_display.py b/source/api/scene/base_display.py new file mode 100644 index 0000000..43095fe --- /dev/null +++ b/source/api/scene/base_display.py @@ -0,0 +1,66 @@ +from abc import ABC +from pathlib import Path +import pygame +from pygame.event import Event + +class BaseDisplay(ABC): + """ + Base class for all scenes. A scene is a collection of GameObjects. A scene can be serialized and deserialized. A scene can be initialized + + Attributes: + screen: pygame.Surface, the screen to draw on + scene_manager: SceneManager, the scene manager + + Methods: + __init__(self, screen: pygame.Surface, scene_manager) + awake(self) + update(self, delta_time: float, events: list[Event]) + unload(self) + """ + def __init__(self, screen: pygame.Surface, scene_manager, background_path: Path) -> None: + """ + Inits BaseDisplay with screen and scene_manager + + Arguments: + screen: pygame.Surface, the screen to draw on + scene_manager: SceneManager, the scene manager + """ + self.background = pygame.image.load(background_path).convert() # Load background image + + self.screen: pygame.Surface = screen + self.scene_manager = scene_manager + + ### Methods to be extended by the user ### + + def awake(self) -> None: + """ + Awake is called when the scene is initialized + + Returns: + None + """ + self.scaled_background = pygame.transform.scale(self.background, (self.screen.get_width(), self.screen.get_height())) # scales background image to fit screen size + + def update(self, delta_time: float, events: list[Event]) -> None: + """ + Update is called every frame + + Arguments: + delta_time: float, the time between frames + events: list[Event], the events of the frame + + Returns: + None + """ + self.screen.blit(self.scaled_background, pygame.Vector2()) # redraws background image + + def unload(self) -> None: + """ + Unload is called when the scene is unloaded + + Returns: + None + """ + + pass + diff --git a/source/api/management/scene.py b/source/api/scene/scene.py similarity index 78% rename from source/api/management/scene.py rename to source/api/scene/scene.py index d212399..0164598 100644 --- a/source/api/management/scene.py +++ b/source/api/scene/scene.py @@ -9,71 +9,10 @@ from source.api.management.sound_manager import SoundManager from source.api.objects.game_object import GameObject from source.api.components.rigidbody import Rigidbody from constants import PROJECT_PATH +from source.api.scene.base_display import BaseDisplay from source.game.objects.wall import CircleWall from source.game.objects.ball import Ball -from source.api.management.options import Options - -class BaseDisplay(ABC): - """ - Base class for all scenes. A scene is a collection of GameObjects. A scene can be serialized and deserialized. A scene can be initialized - - Attributes: - screen: pygame.Surface, the screen to draw on - scene_manager: SceneManager, the scene manager - - Methods: - __init__(self, screen: pygame.Surface, scene_manager) - awake(self) - update(self, delta_time: float, events: list[Event]) - unload(self) - """ - def __init__(self, screen: pygame.Surface, scene_manager, background_path: Path) -> None: - """ - Inits BaseDisplay with screen and scene_manager - - Arguments: - screen: pygame.Surface, the screen to draw on - scene_manager: SceneManager, the scene manager - """ - self.background = pygame.image.load(background_path).convert() # Load background image - - self.screen: pygame.Surface = screen - self.scene_manager = scene_manager - - ### Methods to be extended by the user ### - - def awake(self) -> None: - """ - Awake is called when the scene is initialized - - Returns: - None - """ - self.scaled_background = pygame.transform.scale(self.background, (self.screen.get_width(), self.screen.get_height())) # scales background image to fit screen size - - def update(self, delta_time: float, events: list[Event]) -> None: - """ - Update is called every frame - - Arguments: - delta_time: float, the time between frames - events: list[Event], the events of the frame - - Returns: - None - """ - self.screen.blit(self.scaled_background, pygame.Vector2()) # redraws background image - - def unload(self) -> None: - """ - Unload is called when the scene is unloaded - - Returns: - None - """ - - pass - +from source.api.management.options_manager import OptionsManager class Scene(BaseDisplay, ABC): """ @@ -110,6 +49,7 @@ class Scene(BaseDisplay, ABC): self.active_balls = 0 self.remaining_balls: int = 5 self.score: int = 0 + self.score_threshold = 5000 self.object_counter: int = 0 self.all_active_gos: list = [] self.all_active_rbs: list = [] @@ -126,7 +66,7 @@ class Scene(BaseDisplay, ABC): Returns: None """ - self.user_name: str = Options().user_name + self.user_name: str = OptionsManager().user_name return super().awake() def add_gameobject(self, game_object: GameObject) -> None: @@ -192,6 +132,7 @@ class Scene(BaseDisplay, ABC): data = { "user_name": self.user_name, "score": self.score, + "score_threshold": self.score_threshold, "remaining_balls": self.remaining_balls, "object_counter": self.object_counter, "all_balls": [go.serialize() for go in self.all_active_gos if isinstance(go, Ball)], @@ -234,6 +175,7 @@ class Scene(BaseDisplay, ABC): self.user_name = data["user_name"] self.score = data["score"] + self.score_threshold = data["score_threshold"] self.remaining_balls = data["remaining_balls"] self.object_counter = data["object_counter"] for ball_data in data["all_balls"]: @@ -285,4 +227,4 @@ class Scene(BaseDisplay, ABC): self.active_balls = 0 self.remaining_balls: int = 5 self.score: int = 0 - + self.score_threshold = 5000 diff --git a/source/api/utils/transform.py b/source/api/utils/transform.py index 54ff7b6..b24805e 100644 --- a/source/api/utils/transform.py +++ b/source/api/utils/transform.py @@ -2,7 +2,7 @@ from pygame import Vector2 from source.api.utils.event_value import EventValue from constants import PADDLE_SPEED, PTPF -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class Transform: """ @@ -38,7 +38,7 @@ class Transform: self.pos: Vector2 = Vector2() - self.rotation_speed: float = PADDLE_SPEED / max(Options().asf, 1) + self.rotation_speed: float = PADDLE_SPEED / max(OptionsManager().asf, 1) self.do_smooth_rotation: bool = False self.target_smooth_rotation: float = float("inf") self.rot: EventValue[float] = EventValue(0) @@ -100,7 +100,7 @@ class Transform: dict: the serialized Transform """ - asf = Options().asf + asf = OptionsManager().asf return { "pos": [self.pos.x/asf, self.pos.y/asf], # Divide by asf to get the position in pixels "rot": self.rot.get_value() # Get the rotation @@ -114,5 +114,5 @@ class Transform: data: dict, the serialized Transform """ - self.pos = Vector2(data["pos"]) * Options().asf # Multiply by asf to get the position in meters + self.pos = Vector2(data["pos"]) * OptionsManager().asf # Multiply by asf to get the position in meters self.rot.set_value(data["rot"]) # Set the rotation \ No newline at end of file diff --git a/source/game/objects/flipper.py b/source/game/objects/flipper.py index dd32b67..05c2a57 100644 --- a/source/game/objects/flipper.py +++ b/source/game/objects/flipper.py @@ -4,7 +4,7 @@ from source.api.objects.game_object import GameObject from source.api.components.mesh import PolygonMesh from source.api.components.collider import PolygonCollider from source.api.components.renderer import Renderer -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class Flipper(GameObject): @@ -40,7 +40,7 @@ class Flipper(GameObject): points: list[V2] = [V2(76, 97), V2(80, 86), V2(90, 78), V2(100, 75), V2(140, 76), V2(180, 78), V2(220, 81), V2(260, 86), V2(300, 94), V2(307, 96), V2(310, 100), V2(307, 104), V2(300, 106), V2(260, 114), V2(220, 119), V2(180, 122), V2(140, 124), V2(100, 125), V2(90, 122), V2(80, 114), V2(76, 103)] points = [point - V2(100, 100) for point in points] - asf = Options().asf + asf = OptionsManager().asf for point in points: point *= (asf * 0.55) diff --git a/source/game/scenes/main_menu.py b/source/game/scenes/main_menu.py index b59cd4d..e30e1a0 100644 --- a/source/game/scenes/main_menu.py +++ b/source/game/scenes/main_menu.py @@ -4,7 +4,7 @@ from turtle import left from pygame import Color, Surface, Vector2 import pygame from source.api.management.image_manager import ImageManager -from source.api.management.scene import BaseDisplay +from source.api.scene.scene import BaseDisplay from pygame.event import Event from source.api.ui.button import Button from source.api.ui.button_style import ButtonStyle @@ -14,7 +14,7 @@ from source.api.ui.text_box import TextBox from source.api.ui.ui_element_base import UIElementBase from constants import DEFAULT_BUTTON_STYLE, PROJECT_PATH -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class MainMenu(BaseDisplay): @@ -63,15 +63,15 @@ class MainMenu(BaseDisplay): self.image_manager = ImageManager(PROJECT_PATH / Path("data/data.png")) - asf = Options().asf - user_name = Options().user_name + asf = OptionsManager().asf + user_name = OptionsManager().user_name button_width = int(285 * asf) button_height = int(125 * asf) button_font_size = int(50 * asf) self.ui_elements.append(Text(self.screen, (.5, .05), (.5, 0), text="Pinball", - width=Options().resolution[0]*7/8)) + width=OptionsManager().resolution[0]*7/8)) button_set = self.button_style.create_button_set( (button_width, button_height), 0.03, 3, right_sided=True) @@ -95,8 +95,8 @@ class MainMenu(BaseDisplay): inactive_button=button_set[0], hover_button=button_set[1], pressed_button=button_set[2], text="Quit", font_size=button_font_size, on_click=lambda: self._quit())) - scoreboard_width = Options().resolution[0]/2 - scoreboard_height = Options().resolution[1]*.3+button_height + scoreboard_width = OptionsManager().resolution[0]/2 + scoreboard_height = OptionsManager().resolution[1]*.3+button_height scoreboard_style = self.button_style.create_button((scoreboard_width, scoreboard_height), left_sided=True) scoreboard_entries = [TextObject("Scoreboard", color=(244, 194, 63))] + self.load_scoreboard_entries() @@ -104,7 +104,7 @@ class MainMenu(BaseDisplay): background=scoreboard_style, text_objects=scoreboard_entries, margin=25*asf)) self.ui_elements.append(Text(self.screen, (.5, .995), (.5, 1), text="Credits: Leon Grothus, Hendik Süberkrüb, Leon Echsler", - width=Options().resolution[0]*15/16)) + width=OptionsManager().resolution[0]*15/16)) text_button_set = self.button_style.create_button_set( (scoreboard_width, button_height), 0.03, 2, left_sided=True) @@ -170,8 +170,8 @@ class MainMenu(BaseDisplay): def save_user_name(self, user_name: str) -> None: if user_name != "": - Options().user_name = user_name - Options().save() + OptionsManager().user_name = user_name + OptionsManager().save() def load_scoreboard_entries(self) -> list[TextObject]: """ diff --git a/source/game/scenes/main_pinball.py b/source/game/scenes/main_pinball.py index 5eaf732..0eef9e0 100644 --- a/source/game/scenes/main_pinball.py +++ b/source/game/scenes/main_pinball.py @@ -9,7 +9,7 @@ from source.api.components.change_score import ChangeScore from source.api.components.life_timer import LifeTimer from source.api.components.scale_renderer import ScaleRenderer from source.api.components.simple_movement import SimpleMovement -from source.api.management.scene import Scene +from source.api.scene.scene import Scene from source.api.ui.text import Text from source.api.ui.ui_element_base import UIElementBase from constants import ASSETS_PATH @@ -21,7 +21,7 @@ from source.game.objects.teleporter import Teleporter from source.game.objects.wall import CircleWall, PolygonWall from source.game.scenes.submenus.end_menu import EndMenu from source.game.scenes.submenus.pause_menu import PauseMenu -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager from scipy.ndimage.filters import gaussian_filter from source.api.utils import utils @@ -88,11 +88,10 @@ class MainPinball(Scene): None """ - self.score_threshold = 5000 bumper_sound = pygame.mixer.Sound(ASSETS_PATH / Path("sounds/bumper.wav")) bumper_sound01 = pygame.mixer.Sound(ASSETS_PATH / Path("sounds/bumper01.wav")) - options = Options() + options = OptionsManager() asf = options.asf self.ball_radius = 15 * asf @@ -321,7 +320,7 @@ class MainPinball(Scene): self.remaining_balls -= 1 width = self.screen.get_width() height = self.screen.get_height() - asf = Options().asf + asf = OptionsManager().asf # .add_components(Tray(5, Color(200,200,200))) self.add_gameobject(Ball(self, V2(width + self.ball_radius*2, height-250*asf), radius=self.ball_radius, forced_spawn=forced_spawn)) @@ -382,7 +381,7 @@ class MainPinball(Scene): element.update_events(events) # update the ui elements element.draw() # draw the ui elements background = self.screen.copy() # copy the screen to a surface - radius = Options().asf * 10 + radius = OptionsManager().asf * 10 # Convert the surface to an array array = pygame.surfarray.pixels3d(background) diff --git a/source/game/scenes/options_menu.py b/source/game/scenes/options_menu.py index 8efe6d4..0609c5f 100644 --- a/source/game/scenes/options_menu.py +++ b/source/game/scenes/options_menu.py @@ -1,7 +1,7 @@ from pathlib import Path from pygame import Surface import pygame -from source.api.management.scene import BaseDisplay +from source.api.scene.scene import BaseDisplay from pygame.event import Event from pygame.freetype import Font from source.api.ui.button import Button @@ -11,7 +11,7 @@ from source.api.ui.text import Text from source.api.ui.ui_element_base import UIElementBase from constants import DEFAULT_BUTTON_STYLE, DEFAULT_FONT -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class OptionsMenu(BaseDisplay): @@ -56,7 +56,7 @@ class OptionsMenu(BaseDisplay): self.font = Font(DEFAULT_FONT, 75) self.ui_elements: list[UIElementBase] = [] - self.options = Options() + self.options = OptionsManager() self.new_master_volume = self.options.master_volume self.new_music_volume = self.options.music_volume self.new_sfx_volume = self.options.sfx_volume diff --git a/source/game/scenes/submenus/end_menu.py b/source/game/scenes/submenus/end_menu.py index 18f4016..ff5fd8f 100644 --- a/source/game/scenes/submenus/end_menu.py +++ b/source/game/scenes/submenus/end_menu.py @@ -10,7 +10,7 @@ from source.api.ui.text import Text from source.api.ui.ui_element_base import UIElementBase from constants import DEFAULT_BUTTON_STYLE, DEFAULT_FONT -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class EndMenu: @@ -50,15 +50,15 @@ class EndMenu: self.font = Font(DEFAULT_FONT, 75) self.button_style = ButtonStyle(DEFAULT_BUTTON_STYLE) - asf = Options().asf + asf = OptionsManager().asf button_width = int(250 * asf) button_height = int(125 * asf) button_font_size = int(50 * asf) self.ui_elements.append(Text(self.screen, (.5, .05), (.5, 0), text="Game Over", - width=Options().resolution[0]*7/8, font=self.font)) + width=OptionsManager().resolution[0]*7/8, font=self.font)) self.ui_elements.append(Text(self.screen, (.5, .15), (.5, 0), text=f"Final Score: {final_score}", - width=Options().resolution[0]*7/8, font=self.font)) + width=OptionsManager().resolution[0]*7/8, font=self.font)) button = self.button_style.create_button_set( (button_width, button_height), 0.03, 3, right_sided=True) diff --git a/source/game/scenes/submenus/pause_menu.py b/source/game/scenes/submenus/pause_menu.py index ec2dc9f..f5d43be 100644 --- a/source/game/scenes/submenus/pause_menu.py +++ b/source/game/scenes/submenus/pause_menu.py @@ -8,7 +8,7 @@ from source.api.ui.text import Text from source.api.ui.ui_element_base import UIElementBase from constants import DEFAULT_BUTTON_STYLE, DEFAULT_FONT -from source.api.management.options import Options +from source.api.management.options_manager import OptionsManager class PauseMenu: @@ -45,13 +45,13 @@ class PauseMenu: self.font = Font(DEFAULT_FONT, 75) self.button_style = ButtonStyle(DEFAULT_BUTTON_STYLE) - asf = Options().asf + asf = OptionsManager().asf button_width = int(250 * asf) button_height = int(125 * asf) button_font_size = int(50 * asf) self.ui_elements.append(Text(self.screen, (.5, .05), (.5, 0), text="Pause", - width=Options().resolution[0]*7/8, font=self.font)) + width=OptionsManager().resolution[0]*7/8, font=self.font)) button = self.button_style.create_button_set( (button_width, button_height), 0.03, 3, right_sided=True) -- GitLab