From f1709229c5c5005c3e65bd1bffc51a2e8fa6c50c Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sun, 26 Mar 2023 11:13:34 +0200 Subject: [PATCH] Screen to world coordinate translation and click listeners --- project/main.py | 30 ++++++++++++++--- project/physics/SpriteManager.py | 17 ++++++++-- project/sprite/BoundingBox.py | 4 ++- project/ui_elements/ClickEvent.py | 11 +++++++ project/ui_elements/UiElement.py | 53 +++++++++++++++++++++++++------ 5 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 project/ui_elements/ClickEvent.py diff --git a/project/main.py b/project/main.py index c885658..cb9a4a0 100644 --- a/project/main.py +++ b/project/main.py @@ -11,9 +11,11 @@ from sprite.SpritesheetManager import SpritesheetManager from physics.PhysicsElementsHandler import PhysicsElementsHandler from sprite.Sprite import Sprite from sprite.StaticSprite import StaticSprite +from ui_elements.ClickEvent import ClickEvent from ui_elements.TextLabel import TextLabel +from ui_elements.UiElement import UiElement -what_to_run = 'level' +what_to_run = 'physics' def apply_frame_rate(number: float): @@ -49,20 +51,20 @@ elif what_to_run == 'physics': test_1_sprite = DynamicSprite(spritesheet_manager.get_sheet("test_1")) test_1_sprite.position_scale = PositionScale((10, -100), (1, 1)) - sprite_manager.add_sprite(DrawLayers.OBJECTS, test_1_sprite) + sprite_manager.add_ui_element(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) - sprite_manager.add_sprite(DrawLayers.OBJECTS, test_3_sprite) + sprite_manager.add_ui_element(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)) - sprite_manager.add_sprite(DrawLayers.OBJECTS, test_2_sprite) + sprite_manager.add_ui_element(DrawLayers.OBJECTS, test_2_sprite) text_1 = TextLabel("Frame: 0", 10, 110, 50, alignment="left") text_1.position_scale.scale = (0.1, 0.1) - sprite_manager.add_sprite(DrawLayers.UI, text_1) + sprite_manager.add_ui_element(DrawLayers.UI, text_1) counter = 0 while True: @@ -76,6 +78,24 @@ elif what_to_run == 'physics': elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: skip = False + elif event.type == pygame.MOUSEBUTTONUP: + screen_pos = pygame.mouse.get_pos() + world_pos = UiElement.transform_screen_to_world(screen_pos, screen_transform) + click_event = ClickEvent(world_pos, screen_pos, event) + + if event.button == 1: + test_dyn_sprite = DynamicSprite(spritesheet_manager.get_sheet("test_1")) + test_dyn_sprite.position_scale = PositionScale(world_pos, (1, 1)) + sprite_manager.add_ui_element(DrawLayers.OBJECTS, test_dyn_sprite) + elif event.button == 3: + test_dyn_sprite = StaticSprite(spritesheet_manager.get_sheet("test_1")) + test_dyn_sprite.position_scale = PositionScale(world_pos, (1, 1)) + sprite_manager.add_ui_element(DrawLayers.OBJECTS, test_dyn_sprite) + + elif event.button == 2: + for element in sprite_manager.find_elements_at_position(world_pos): + sprite_manager.remove_ui_element(element) + element.click(click_event) if skip: continue diff --git a/project/physics/SpriteManager.py b/project/physics/SpriteManager.py index 7ec200a..9634362 100644 --- a/project/physics/SpriteManager.py +++ b/project/physics/SpriteManager.py @@ -1,3 +1,5 @@ +from typing import Optional + from pygame import Surface from physics.PhysicsElementsHandler import PhysicsElementsHandler @@ -25,10 +27,10 @@ class SpriteManager: for layer in DrawLayers.DRAW_ORDER: self.layers[layer] = [] - def add_sprite(self, layer: str, sprite: UiElement): + def add_ui_element(self, layer: str, sprite: UiElement): self.layers[layer].append(sprite) - def remove_sprite(self, sprite: UiElement): + def remove_ui_element(self, sprite: UiElement): for layer in self.layers: if sprite in self.layers[layer]: self.layers[layer].remove(sprite) @@ -41,6 +43,17 @@ class SpriteManager: for sprite in self.layers[layer]: sprite.draw(screen, screen_transform) + def find_elements_at_position(self, position: tuple[float, float]) -> list[UiElement]: + elements = [] + + for layer in DrawLayers.DRAW_ORDER: + for sprite in self.layers[layer]: + bounding_box = sprite.get_bounding_box() + if bounding_box is not None and bounding_box.contains_point(position): + elements.append(sprite) + + return elements + def collision_detected(self, sprite_a: Sprite, sprite_b: Sprite): # print(f"Collision detected between {sprite_a} and {sprite_b}") pass diff --git a/project/sprite/BoundingBox.py b/project/sprite/BoundingBox.py index 135c676..fa5fe3f 100644 --- a/project/sprite/BoundingBox.py +++ b/project/sprite/BoundingBox.py @@ -1,4 +1,3 @@ - class BoundingBox: def __init__(self, x, y, width, height): self.x = x @@ -12,5 +11,8 @@ class BoundingBox: def get_position(self): return self.x, self.y + def contains_point(self, position: tuple[float, float]): + return self.x <= position[0] <= self.x + self.width and self.y <= position[1] <= self.y + self.height + def __str__(self): return f"({self.x}, {self.y}, {self.width}, {self.height})" diff --git a/project/ui_elements/ClickEvent.py b/project/ui_elements/ClickEvent.py new file mode 100644 index 0000000..b43551f --- /dev/null +++ b/project/ui_elements/ClickEvent.py @@ -0,0 +1,11 @@ +from pygame.event import Event + + +class ClickEvent: + def __init__(self, world_position: tuple[float, float], screen_position: tuple[float, float], event: Event): + self.world_position = world_position + self.screen_position = screen_position + self.event = event + + def __str__(self): + return f"ClickEvent(world_position={self.world_position}, screen_position={self.screen_position})" diff --git a/project/ui_elements/UiElement.py b/project/ui_elements/UiElement.py index 3817d96..16a0be9 100644 --- a/project/ui_elements/UiElement.py +++ b/project/ui_elements/UiElement.py @@ -4,6 +4,7 @@ from typing import Optional import pygame from pygame import Surface +from sprite.BoundingBox import BoundingBox from sprite.PositionScale import PositionScale @@ -11,14 +12,29 @@ class UiElement: def __init__(self): self.position_scale = PositionScale() self.visible = True + self.click_listeners = [] + + def add_click_listener(self, listener): + self.click_listeners.append(listener) + + def remove_click_listener(self, listener): + self.click_listeners.remove(listener) + + def click(self, click_event): + for listener in self.click_listeners: + listener(self, click_event) @abc.abstractmethod def tick(self, dt: float): - return + pass @abc.abstractmethod def render_sprite_image(self) -> Optional[Surface]: - return None + pass + + @abc.abstractmethod + def get_bounding_box(self) -> BoundingBox: + pass def draw(self, screen: pygame.Surface, screen_transform: PositionScale): if not self.visible: @@ -27,20 +43,37 @@ class UiElement: image = self.render_sprite_image() if image is not None: + target_position = UiElement.transform_world_to_screen(self.position_scale.position, screen_transform) + 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) + target_image = UiElement.get_scaled_image(image, target_size) screen.blit(target_image, target_position) - def get_scaled_image(self, image, resize): + @staticmethod + def get_scaled_image(image, resize): return pygame.transform.scale(image, resize) + + @staticmethod + def transform_screen_to_world(convert_position: tuple, screen_transform: PositionScale): + screen_scale = screen_transform.scale + screen_position = screen_transform.position + + object_position = (convert_position[0] / screen_scale[0] - screen_position[0], + convert_position[1] / screen_scale[1] - screen_position[1]) + + return object_position + + @staticmethod + def transform_world_to_screen(convert_position: tuple, screen_transform: PositionScale): + screen_scale = screen_transform.scale + screen_position = screen_transform.position + + object_position = (int((convert_position[0] + screen_position[0]) * screen_scale[0]), + (int(convert_position[1] + screen_position[1]) * screen_scale[1])) + + return object_position