From 72fd82c5a6d5f0b79dc61f1682f77357b72c6d18 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sun, 26 Mar 2023 09:51:11 +0200 Subject: [PATCH] Generalized UI Elements --- README.md | 5 ++ project/main.py | 28 +++++++---- project/physics/PhysicsElementsHandler.py | 23 ++++----- project/physics/SpriteManager.py | 41 ++++++++++++++++ project/sprite/Sprite.py | 28 ++--------- project/ui_elements/TextLabel.py | 41 ++++++++-------- project/ui_elements/UiElement.py | 58 +++++++++++++++++++++++ 7 files changed, 157 insertions(+), 67 deletions(-) create mode 100644 project/physics/SpriteManager.py create mode 100644 project/ui_elements/UiElement.py diff --git a/README.md b/README.md index ad0c159..8a67c1f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # sep-se-platformer +Links: + +- [doc: Software design](https://docs.google.com/document/d/1GpieqcuPjXC1P-a0mXGLe61XF8xzZ_h2taeojuUqToA/edit#heading=h.a6o0m6lt88hs) +- [tab: Arbeitspakete](https://docs.google.com/spreadsheets/d/1zyScof0CLPPSmdI0Dq3QWAdLAvyMhk_QFo7VJDFPymk/edit#gid=0) +- [tab: Levels](https://docs.google.com/spreadsheets/d/1kIfnlJLYZ0p-zY8Jl699yGgnn6wju2nfAVBx9WdDkQI/edit#gid=741912808) diff --git a/project/main.py b/project/main.py index bd4d077..b1b6db5 100644 --- a/project/main.py +++ b/project/main.py @@ -4,6 +4,7 @@ import pygame from pygame.font import Font from level.LevelManager import LevelManager +from physics.SpriteManager import SpriteManager, DrawLayers from sprite.DynamicSprite import DynamicSprite from sprite.PositionScale import PositionScale from sprite.SpritesheetManager import SpritesheetManager @@ -42,21 +43,24 @@ elif what_to_run == 'physics': spritesheet_manager = SpritesheetManager("data/sprites", "data/sprites/sprites.json") - physics_handler = PhysicsElementsHandler() + sprite_manager = SpriteManager() test_1_sprite = DynamicSprite(spritesheet_manager.get_sheet("test_1")) test_1_sprite.position_scale = PositionScale((10, -100), (1, 1)) - physics_handler.add_sprite(test_1_sprite) + sprite_manager.add_sprite(DrawLayers.OBJECTS, test_1_sprite) test_3_sprite = DynamicSprite(spritesheet_manager.get_sheet("test_1")) test_3_sprite.position_scale = PositionScale((130, 100), (1, 1)) test_3_sprite.motion = (-9, -10) - physics_handler.add_sprite(test_3_sprite) + sprite_manager.add_sprite(DrawLayers.OBJECTS, test_3_sprite) test_2_sprite = StaticSprite(spritesheet_manager.get_sheet("test_1")) test_2_sprite.position_scale = PositionScale((10, 80), (1, 1)) - physics_handler.add_sprite(test_2_sprite) + sprite_manager.add_sprite(DrawLayers.OBJECTS, test_2_sprite) + text_1 = TextLabel("Das ist ein Test", 10, 80, 50, alignment="left") + text_1.position_scale.scale = (0.1, 0.1) + sprite_manager.add_sprite(DrawLayers.UI, text_1) while True: clock.tick(frame_rate) @@ -75,8 +79,8 @@ elif what_to_run == 'physics': screen.fill((0, 0, 0)) - physics_handler.tick(apply_frame_rate(1)) - physics_handler.draw(screen, screen_transform) + sprite_manager.tick(apply_frame_rate(1)) + sprite_manager.draw(screen, screen_transform) pygame.display.update() @@ -88,9 +92,15 @@ elif what_to_run == 'textlabel': pygame.display.set_caption("PM GAME") clock = pygame.time.Clock() - test1 = TextLabel("Das ist ein Test", 400, 0, 50, alignment="left") - test2 = TextLabel("Das ist ein Test", 400, 10, 50, alignment="center") - test3 = TextLabel("Das ist ein Test", 400, 20, 50, alignment="right") + test1 = TextLabel("Das ist ein Test", 100, 0, 50, alignment="left") + test2 = TextLabel("Das ist ein Test", 100, 50, 50, alignment="left") + test3 = TextLabel("Das ist ein Test", 100, 20, 50, alignment="left") + + test1.position_scale.scale = (0.2, 0.2) + test2.position_scale.scale = (0.4, 0.4) + test3.position_scale.scale = (0.2, 0.2) + + test3.visible = False while True: clock.tick(5) diff --git a/project/physics/PhysicsElementsHandler.py b/project/physics/PhysicsElementsHandler.py index 793faf5..9ac0b7d 100644 --- a/project/physics/PhysicsElementsHandler.py +++ b/project/physics/PhysicsElementsHandler.py @@ -4,6 +4,7 @@ from sprite.DynamicSprite import DynamicSprite from sprite.PositionScale import PositionScale from sprite.Sprite import Sprite from sprite.StaticSprite import StaticSprite +from ui_elements.UiElement import UiElement MOTION_STEPS = 10 TOLERANCE = 1 @@ -11,16 +12,14 @@ TOLERANCE = 1 class PhysicsElementsHandler: def __init__(self): - self.sprites: list[Sprite] = [] + pass - def add_sprite(self, sprite: Sprite): - self.sprites.append(sprite) + def tick_all_layers(self, dt: float, layers: dict[str, list[UiElement]]): + for layer in layers: + self.tick(dt, layers[layer]) - def remove_sprite(self, sprite: Sprite): - self.sprites.remove(sprite) - - def tick(self, dt: float): - for sprite in self.sprites: + def tick(self, dt: float, sprites: list[UiElement]): + for sprite in sprites: sprite.tick(dt) # handle motion and collisions. To do this: @@ -34,9 +33,9 @@ class PhysicsElementsHandler: # 4.2.2. If it does, move the sprite back to the previous position and stop the motion # 4.2.3. If it doesn't, move the sprite to the new position - colliders = [sprite for sprite in self.sprites if sprite.is_collider and isinstance(sprite, StaticSprite)] + colliders = [sprite for sprite in sprites if isinstance(sprite, StaticSprite) and sprite.is_collider] - dynamic_sprites = [sprite for sprite in self.sprites if isinstance(sprite, DynamicSprite)] + dynamic_sprites = [sprite for sprite in sprites if isinstance(sprite, DynamicSprite)] sorted_dynamic_sprites = sorted(dynamic_sprites, key=lambda spr: spr.position_scale.position[1]) for sprite in sorted_dynamic_sprites: @@ -97,7 +96,3 @@ class PhysicsElementsHandler: return True return False - - def draw(self, screen: Surface, screen_transform: PositionScale): - for sprite in self.sprites: - sprite.draw(screen, screen_transform) diff --git a/project/physics/SpriteManager.py b/project/physics/SpriteManager.py new file mode 100644 index 0000000..9540c81 --- /dev/null +++ b/project/physics/SpriteManager.py @@ -0,0 +1,41 @@ +from pygame import Surface + +from physics.PhysicsElementsHandler import PhysicsElementsHandler +from sprite.PositionScale import PositionScale +from sprite.Sprite import Sprite +from ui_elements.UiElement import UiElement + + +class DrawLayers: + BACKGROUND = 'background' + LEVEL = 'level' + OBJECTS = 'objects' + PLAYER = 'player' + UI = 'ui' + + DRAW_ORDER = [BACKGROUND, LEVEL, OBJECTS, PLAYER, UI] + + +class SpriteManager: + def __init__(self): + self.physics_handler = PhysicsElementsHandler() + + self.layers: dict[str, list[UiElement]] = {} + for layer in DrawLayers.DRAW_ORDER: + self.layers[layer] = [] + + def add_sprite(self, layer: str, sprite: UiElement): + self.layers[layer].append(sprite) + + def remove_sprite(self, sprite: UiElement): + for layer in self.layers: + if sprite in self.layers[layer]: + self.layers[layer].remove(sprite) + + def tick(self, dt: float): + self.physics_handler.tick_all_layers(dt, self.layers) + + def draw(self, screen: Surface, screen_transform: PositionScale): + for layer in DrawLayers.DRAW_ORDER: + for sprite in self.layers[layer]: + sprite.draw(screen, screen_transform) diff --git a/project/sprite/Sprite.py b/project/sprite/Sprite.py index 39f36a8..ab0be39 100644 --- a/project/sprite/Sprite.py +++ b/project/sprite/Sprite.py @@ -4,21 +4,20 @@ import sys from sprite.BoundingBox import BoundingBox from sprite.PositionScale import PositionScale from sprite.Spritesheet import Spritesheet +from ui_elements.UiElement import UiElement sys.path.append('./sprite') -class Sprite: +class Sprite(UiElement): def __init__(self, spritesheet: Spritesheet): + super().__init__() self.spritesheet = spritesheet self.animation_state = list(self.spritesheet.animations.keys())[0] self.animation_frame = 0 self.animation_delay = 0 - self.position_scale = PositionScale() - - self.visible = True self.animated = True self.image = None @@ -47,22 +46,8 @@ class Sprite: self.animation_frame = frame self.tick(0) - def draw(self, screen: pygame.Surface, screen_transform: PositionScale): - if not self.visible: - return - - if self.image is not None: - target_scale = (screen_transform.scale[0] * self.position_scale.scale[0], - screen_transform.scale[1] * self.position_scale.scale[1]) - - target_position = ((self.position_scale.position[0] + screen_transform.position[0]) * target_scale[0], - (self.position_scale.position[1] + screen_transform.position[1]) * target_scale[1]) - - target_size = (int(target_scale[0] * self.image.get_width()), - int(target_scale[1] * self.image.get_height())) - - target_image = self.get_scaled_image(self.image, target_size) - screen.blit(target_image, target_position) + def render_sprite_image(self) -> pygame.Surface: + return self.image def get_bounding_box(self) -> BoundingBox: return BoundingBox( @@ -72,9 +57,6 @@ class Sprite: self.image.get_height() * self.position_scale.scale[1] ) - def get_scaled_image(self, image, resize): - return pygame.transform.scale(image, resize) - def dump(self, file): # re-attach all the images to a single surface and save it to a file width = 0 diff --git a/project/ui_elements/TextLabel.py b/project/ui_elements/TextLabel.py index 6fed68f..3e86973 100644 --- a/project/ui_elements/TextLabel.py +++ b/project/ui_elements/TextLabel.py @@ -3,20 +3,29 @@ from pygame import Surface, Rect from pygame.font import Font from sprite.PositionScale import PositionScale +from ui_elements.UiElement import UiElement -class TextLabel: - def __init__(self, text: str, x_position: float, y_position: float, font_size: int, - alignment: str = "left"): +class TextLabel(UiElement): + def tick(self, dt: float): + pass + + def __init__(self, text: str, x_position: float, y_position: float, font_size: int, alignment: str = "left"): + super().__init__() self.text = text + self.x_position = x_position self.y_position = y_position - self.alignment = alignment self.current_width = 0 self.current_height = 0 + + self.alignment = alignment + self.font_size = font_size self.font = Font('data/font/MilkyNice.ttf', self.font_size) - self.position_scale = PositionScale() + + def render_sprite_image(self) -> pygame.Surface: + return self.font.render(str(self.text), True, pygame.Color('white')) def draw(self, screen: Surface, screen_transform: PositionScale): rendered_font = self.font.render(str(self.text), True, pygame.Color('white')) @@ -25,23 +34,13 @@ class TextLabel: self.current_height = rendered_font.get_height() if self.alignment == "right": - screen.blit(rendered_font, (self.x_position - self.current_width, self.y_position)) + self.position_scale.position = (self.x_position - self.current_width, self.y_position) elif self.alignment == "left": - screen.blit(rendered_font, (self.x_position, self.y_position)) + self.position_scale.position = (self.x_position, self.y_position) elif self.alignment == "center": - screen.blit(rendered_font, (self.x_position - self.current_width / 2, self.y_position)) + self.position_scale.position = (self.x_position - self.current_width / 2, self.y_position) - target_scale = (screen_transform.scale[0] * self.position_scale.scale[0], - screen_transform.scale[1] * self.position_scale.scale[1]) + super().draw(screen, screen_transform) - target_position = ((self.position_scale.position[0] + screen_transform.position[0]) * target_scale[0], - (self.position_scale.position[1] + screen_transform.position[1]) * target_scale[1]) - - target_size = (int(target_scale[0] * self.x_position), - int(target_scale[1] * self.y_position)) - - target_image = pygame.transform.scale(rendered_font, target_size) - - -def set_text(self, new_text: str): - self.text = new_text + def set_text(self, new_text: str): + self.text = new_text diff --git a/project/ui_elements/UiElement.py b/project/ui_elements/UiElement.py new file mode 100644 index 0000000..3e61ab5 --- /dev/null +++ b/project/ui_elements/UiElement.py @@ -0,0 +1,58 @@ +import abc +from typing import Optional + +import pygame +from pygame import Surface + +from sprite.PositionScale import PositionScale + + +class UiElement: + def __init__(self): + self.position_scale = PositionScale() + self.visible = True + + @abc.abstractmethod + def tick(self, dt: float): + return + + @abc.abstractmethod + def render_sprite_image(self) -> Optional[Surface]: + return None + + def draw(self, screen: pygame.Surface, screen_transform: PositionScale): + if not self.visible: + return + + image = self.render_sprite_image() + + if image is not None: + # target_scale = (screen_transform.scale[0] * self.position_scale.scale[0], + # screen_transform.scale[1] * self.position_scale.scale[1]) + + # target_position = ((self.position_scale.position[0] + screen_transform.position[0]) * target_scale[0], + # (self.position_scale.position[1] + screen_transform.position[1]) * target_scale[1]) + + # target_size = (int(target_scale[0] * image.get_width()), + # int(target_scale[1] * image.get_height())) + + # target_image = self.get_scaled_image(image, target_size) + # screen.blit(target_image, target_position) + + screen_scale = screen_transform.scale + screen_position = screen_transform.position + + object_scale = self.position_scale.scale + object_position = self.position_scale.position + + target_position = ((object_position[0] + screen_position[0]) * screen_scale[0], + (object_position[1] + screen_position[1]) * screen_scale[1]) + + target_size = (int(screen_scale[0] * object_scale[0] * image.get_width()), + int(screen_scale[1] * object_scale[1] * image.get_height())) + + target_image = self.get_scaled_image(image, target_size) + screen.blit(target_image, target_position) + + def get_scaled_image(self, image, resize): + return pygame.transform.scale(image, resize)