From 65c8a1754393636288fb59f55b68356506f0f8c4 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sun, 26 Mar 2023 12:46:41 +0200 Subject: [PATCH] Implemented framework for player controller and input handling --- project/main.py | 38 ++++++------------- project/physics/PhysicsElementsHandler.py | 7 ++-- project/physics/SpriteManager.py | 6 ++- project/physics/TickData.py | 19 ++++++++++ .../physics/controllers/ElementController.py | 10 +++++ .../physics/controllers/PlayerController.py | 5 +++ project/sprite/DynamicSprite.py | 37 ++++++++---------- project/sprite/Sprite.py | 12 +++--- project/sprite/SpritesheetManager.py | 3 -- project/sprite/StaticSprite.py | 5 --- project/ui_elements/ClickEvent.py | 25 +++++++++++- project/ui_elements/CoordinateTransform.py | 21 ++++++++++ project/ui_elements/KeyEvent.py | 31 +++++++++++++++ project/ui_elements/TextLabel.py | 3 +- project/ui_elements/UiElement.py | 26 ++----------- 15 files changed, 156 insertions(+), 92 deletions(-) create mode 100644 project/physics/TickData.py create mode 100644 project/physics/controllers/ElementController.py create mode 100644 project/physics/controllers/PlayerController.py create mode 100644 project/ui_elements/CoordinateTransform.py create mode 100644 project/ui_elements/KeyEvent.py diff --git a/project/main.py b/project/main.py index cb9a4a0..75a7cbf 100644 --- a/project/main.py +++ b/project/main.py @@ -1,19 +1,18 @@ import random import pygame -from pygame.font import Font from level.LevelManager import LevelManager from physics.SpriteManager import SpriteManager, DrawLayers +from physics.TickData import TickData from sprite.DynamicSprite import DynamicSprite from sprite.PositionScale import PositionScale 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.KeyEvent import KeyEvent from ui_elements.TextLabel import TextLabel -from ui_elements.UiElement import UiElement what_to_run = 'physics' @@ -70,32 +69,19 @@ elif what_to_run == 'physics': while True: clock.tick(frame_rate) - skip = False - for event in pygame.event.get(): + skip = True + pygame_events: list[pygame.event.Event] = pygame.event.get() + key_events: list[KeyEvent] = KeyEvent.create_events(pygame_events) + click_events: list[ClickEvent] = ClickEvent.create_events(pygame_events, screen_transform) + + for event in pygame_events: if event.type == pygame.QUIT: pygame.quit() quit() - 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) + for key_event in key_events: + if key_event.is_keymap_down(KeyEvent.KEY_RIGHT): + skip = False if skip: continue @@ -104,7 +90,7 @@ elif what_to_run == 'physics': text_1.set_text(f"Frame: {counter}") screen.fill((0, 0, 0)) - sprite_manager.tick(apply_frame_rate(1)) + sprite_manager.tick(TickData(apply_frame_rate(1), pygame_events, key_events, click_events, screen_transform)) sprite_manager.draw(screen, screen_transform) pygame.display.update() diff --git a/project/physics/PhysicsElementsHandler.py b/project/physics/PhysicsElementsHandler.py index 1ed97d6..53005b2 100644 --- a/project/physics/PhysicsElementsHandler.py +++ b/project/physics/PhysicsElementsHandler.py @@ -1,5 +1,6 @@ from typing import Optional +from physics.TickData import TickData from sprite.DynamicSprite import DynamicSprite from sprite.Sprite import Sprite from sprite.StaticSprite import StaticSprite @@ -19,11 +20,11 @@ class PhysicsElementsHandler: def remove_collision_callback(self, callback): self.collision_callbacks.remove(callback) - def tick(self, dt: float, layers: dict[str, list[UiElement]]): + def tick(self, tick_data: TickData, layers: dict[str, list[UiElement]]): sprites = [] for layer in layers: for sprite in layers[layer]: - sprite.tick(dt) + sprite.tick(tick_data) if isinstance(sprite, Sprite): sprites.append(sprite) @@ -44,7 +45,7 @@ class PhysicsElementsHandler: sorted_dynamic_sprites = sorted(dynamic_sprites, key=lambda spr: spr.position_scale.position[1]) for sprite in sorted_dynamic_sprites: - collides_with = self.attempt_move(dt, sprite, colliders, MOTION_STEPS) + collides_with = self.attempt_move(tick_data.dt, sprite, colliders, MOTION_STEPS) if collides_with is not None: for callback in self.collision_callbacks: callback(sprite, collides_with) diff --git a/project/physics/SpriteManager.py b/project/physics/SpriteManager.py index 9634362..3b3ec38 100644 --- a/project/physics/SpriteManager.py +++ b/project/physics/SpriteManager.py @@ -1,8 +1,10 @@ from typing import Optional +import pygame from pygame import Surface from physics.PhysicsElementsHandler import PhysicsElementsHandler +from physics.TickData import TickData from sprite.PositionScale import PositionScale from sprite.Sprite import Sprite from ui_elements.UiElement import UiElement @@ -35,8 +37,8 @@ class SpriteManager: if sprite in self.layers[layer]: self.layers[layer].remove(sprite) - def tick(self, dt: float): - self.physics_handler.tick(dt, self.layers) + def tick(self, tick_data: TickData): + self.physics_handler.tick(tick_data, self.layers) def draw(self, screen: Surface, screen_transform: PositionScale): for layer in DrawLayers.DRAW_ORDER: diff --git a/project/physics/TickData.py b/project/physics/TickData.py new file mode 100644 index 0000000..32643e2 --- /dev/null +++ b/project/physics/TickData.py @@ -0,0 +1,19 @@ +import pygame + +from sprite.PositionScale import PositionScale +from ui_elements.ClickEvent import ClickEvent +from ui_elements.KeyEvent import KeyEvent + + +class TickData: + def __init__(self, dt: float, pygame_events: list[pygame.event.Event], key_events: list[KeyEvent], + click_events: list[ClickEvent], screen_transform: PositionScale): + self.dt = dt + self.pygame_events = pygame_events + self.key_events = key_events + self.click_events = click_events + self.screen_transform = screen_transform + + @staticmethod + def empty(): + pass diff --git a/project/physics/controllers/ElementController.py b/project/physics/controllers/ElementController.py new file mode 100644 index 0000000..270be20 --- /dev/null +++ b/project/physics/controllers/ElementController.py @@ -0,0 +1,10 @@ +from ui_elements.ClickEvent import ClickEvent +from ui_elements.KeyEvent import KeyEvent + + +class ElementController: + def __init__(self): + pass + + def handle_input(self, dt: float, key_events: list[KeyEvent], click_events: list[ClickEvent]): + pass diff --git a/project/physics/controllers/PlayerController.py b/project/physics/controllers/PlayerController.py new file mode 100644 index 0000000..c948746 --- /dev/null +++ b/project/physics/controllers/PlayerController.py @@ -0,0 +1,5 @@ +from physics.controllers.ElementController import ElementController + + +class PlayerController(ElementController): + pass diff --git a/project/sprite/DynamicSprite.py b/project/sprite/DynamicSprite.py index 72740ad..2570a23 100644 --- a/project/sprite/DynamicSprite.py +++ b/project/sprite/DynamicSprite.py @@ -1,11 +1,6 @@ -import pygame -import sys - -from sprite.Sprite import Sprite from sprite.Spritesheet import Spritesheet from sprite.StaticSprite import StaticSprite - -sys.path.append('./sprite') +from physics.TickData import TickData class DynamicSprite(StaticSprite): @@ -21,6 +16,21 @@ class DynamicSprite(StaticSprite): # up, right, down, left self.touches_bounding = (False, False, False, False) + def tick(self, tick_data: TickData): + super().tick(tick_data) + + deceleration_horizontal = 0 + if abs(self.motion[0]) > 0: + if self.touches_bounding[2]: + deceleration_horizontal = self.deceleration_horizontal_ground + else: + deceleration_horizontal = self.deceleration_horizontal_air + + self.motion = ( + self.motion[0] - deceleration_horizontal * self.motion[0] * tick_data.dt, + self.motion[1] + self.gravity * tick_data.dt + ) + def set_touches_bottom(self, value: bool): self.touches_bounding = (self.touches_bounding[0], self.touches_bounding[1], value, self.touches_bounding[3]) @@ -35,18 +45,3 @@ class DynamicSprite(StaticSprite): def reset_touches(self): self.touches_bounding = (False, False, False, False) - - def tick(self, dt: float): - super().tick(dt) - - deceleration_horizontal = 0 - if abs(self.motion[0]) > 0: - if self.touches_bounding[2]: - deceleration_horizontal = self.deceleration_horizontal_ground - else: - deceleration_horizontal = self.deceleration_horizontal_air - - self.motion = ( - self.motion[0] - deceleration_horizontal * self.motion[0] * dt, - self.motion[1] + self.gravity * dt - ) diff --git a/project/sprite/Sprite.py b/project/sprite/Sprite.py index ab0be39..8c4a9d6 100644 --- a/project/sprite/Sprite.py +++ b/project/sprite/Sprite.py @@ -1,13 +1,11 @@ import pygame -import sys +from physics.TickData import TickData 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(UiElement): def __init__(self, spritesheet: Spritesheet): @@ -24,11 +22,11 @@ class Sprite(UiElement): self.is_collider = True - def tick(self, dt: float): + def tick(self, tick_data: TickData): animation = self.spritesheet.animations[self.animation_state] if self.animated: - self.animation_delay += dt + self.animation_delay += tick_data.dt while self.animation_delay >= animation['delays'][self.animation_frame % len(animation['delays'])]: self.animation_delay -= animation['delays'][self.animation_frame % len(animation['delays'])] @@ -40,11 +38,11 @@ class Sprite(UiElement): if state in self.spritesheet.animations: self.animation_state = state self.animation_delay = 0 - self.tick(0) + self.tick(TickData(0, [], [], [], PositionScale())) def set_animation_frame(self, frame: int): self.animation_frame = frame - self.tick(0) + self.tick(TickData(0, [], [], [], PositionScale())) def render_sprite_image(self) -> pygame.Surface: return self.image diff --git a/project/sprite/SpritesheetManager.py b/project/sprite/SpritesheetManager.py index ea704a2..3256597 100644 --- a/project/sprite/SpritesheetManager.py +++ b/project/sprite/SpritesheetManager.py @@ -1,10 +1,7 @@ import json -import sys from sprite.Spritesheet import Spritesheet -sys.path.append('./sprite') - # This class is used to load named sprite sheets from the img folder. class SpritesheetManager: diff --git a/project/sprite/StaticSprite.py b/project/sprite/StaticSprite.py index d04ed8b..b5e48cc 100644 --- a/project/sprite/StaticSprite.py +++ b/project/sprite/StaticSprite.py @@ -1,11 +1,6 @@ -import pygame -import sys - from sprite.Sprite import Sprite from sprite.Spritesheet import Spritesheet -sys.path.append('./sprite') - class StaticSprite(Sprite): def __init__(self, spritesheet: Spritesheet): diff --git a/project/ui_elements/ClickEvent.py b/project/ui_elements/ClickEvent.py index b43551f..4b9dc3c 100644 --- a/project/ui_elements/ClickEvent.py +++ b/project/ui_elements/ClickEvent.py @@ -1,11 +1,32 @@ +import pygame from pygame.event import Event +from sprite.PositionScale import PositionScale +from ui_elements import CoordinateTransform + class ClickEvent: + CLICK_LEFT = 1 + CLICK_MIDDLE = 2 + CLICK_RIGHT = 3 + 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})" + def is_click_down(self, button: int) -> bool: + return self.event.type == pygame.MOUSEBUTTONDOWN and self.event.button == button + + def is_click_up(self, button: int) -> bool: + return self.event.type == pygame.MOUSEBUTTONUP and self.event.button == button + + @staticmethod + def create_events(event: list[Event], screen_transform: PositionScale) -> list['ClickEvent']: + return [ + ClickEvent(CoordinateTransform.transform_screen_to_world(pygame.mouse.get_pos(), screen_transform), + pygame.mouse.get_pos(), + e) + for e in event + if e.type == pygame.MOUSEBUTTONDOWN or e.type == pygame.MOUSEBUTTONUP + ] diff --git a/project/ui_elements/CoordinateTransform.py b/project/ui_elements/CoordinateTransform.py new file mode 100644 index 0000000..ddda46d --- /dev/null +++ b/project/ui_elements/CoordinateTransform.py @@ -0,0 +1,21 @@ +from sprite.PositionScale import PositionScale + + +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 + + +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 diff --git a/project/ui_elements/KeyEvent.py b/project/ui_elements/KeyEvent.py new file mode 100644 index 0000000..ad031d2 --- /dev/null +++ b/project/ui_elements/KeyEvent.py @@ -0,0 +1,31 @@ +import pygame +from pygame.event import Event + + +class KeyEvent: + KEY_LEFT: list[int] = [pygame.K_LEFT, pygame.K_a] + KEY_RIGHT: list[int] = [pygame.K_RIGHT, pygame.K_d] + KEY_UP: list[int] = [pygame.K_UP, pygame.K_w] + KEY_DOWN: list[int] = [pygame.K_DOWN, pygame.K_s] + KEY_SPACE: list[int] = [pygame.K_SPACE] + KEY_ESCAPE: list[int] = [pygame.K_ESCAPE] + KEY_ENTER: list[int] = [pygame.K_RETURN, pygame.K_KP_ENTER] + + def __init__(self, event: Event): + self.event = event + + def is_key_down(self, key: int) -> bool: + return self.event.type == pygame.KEYDOWN and self.event.key == key + + def is_key_up(self, key: int) -> bool: + return self.event.type == pygame.KEYUP and self.event.key == key + + def is_keymap_down(self, keys: list[int]) -> bool: + return self.event.type == pygame.KEYDOWN and self.event.key in keys + + def is_keymap_up(self, keys: list[int]) -> bool: + return self.event.type == pygame.KEYUP and self.event.key in keys + + @staticmethod + def create_events(event: list[Event]) -> list['KeyEvent']: + return [KeyEvent(e) for e in event if e.type == pygame.KEYDOWN or e.type == pygame.KEYUP] diff --git a/project/ui_elements/TextLabel.py b/project/ui_elements/TextLabel.py index 02ae79e..74af2e1 100644 --- a/project/ui_elements/TextLabel.py +++ b/project/ui_elements/TextLabel.py @@ -2,13 +2,14 @@ import pygame from pygame import Surface, Rect from pygame.font import Font +from physics.TickData import TickData from sprite.BoundingBox import BoundingBox from sprite.PositionScale import PositionScale from ui_elements.UiElement import UiElement class TextLabel(UiElement): - def tick(self, dt: float): + def tick(self, tick_data: TickData): pass def __init__(self, text: str, x_position: float, y_position: float, font_size: int, alignment: str = "left"): diff --git a/project/ui_elements/UiElement.py b/project/ui_elements/UiElement.py index 16a0be9..489d02a 100644 --- a/project/ui_elements/UiElement.py +++ b/project/ui_elements/UiElement.py @@ -6,6 +6,8 @@ from pygame import Surface from sprite.BoundingBox import BoundingBox from sprite.PositionScale import PositionScale +from physics.TickData import TickData +from ui_elements import CoordinateTransform class UiElement: @@ -25,7 +27,7 @@ class UiElement: listener(self, click_event) @abc.abstractmethod - def tick(self, dt: float): + def tick(self, tick_data: TickData): pass @abc.abstractmethod @@ -43,7 +45,7 @@ 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) + target_position = CoordinateTransform.transform_world_to_screen(self.position_scale.position, screen_transform) screen_scale = screen_transform.scale object_scale = self.position_scale.scale @@ -57,23 +59,3 @@ class UiElement: @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