From 73b22dca7d65ad9220e0cd842bb42e67d881bb17 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Sat, 25 Mar 2023 15:41:32 +0100 Subject: [PATCH] Started implementing framework --- project/data/font/MilkyNice.ttf | Bin 0 -> 99732 bytes project/data/levels/1-1.csv | 43 +++++++ project/data/levels/levels.json | 10 ++ project/{ => data}/sprites/sprites.json | 0 project/{ => data}/sprites/test_1.png | Bin project/level/Level.py | 44 +++++++ project/level/LevelElementSymbols.py | 18 +++ project/level/LevelManager.py | 29 +++++ project/main.py | 126 +++++++++++++++++---- project/physics/PhysicsElementsHandler.py | 62 ++++++++++ project/sprite/BoundingBox.py | 13 +++ project/sprite/DynamicSprite.py | 25 ++++ project/{ => sprite}/PositionScale.py | 2 +- project/{ => sprite}/Sprite.py | 43 +++++-- project/{ => sprite}/Spritesheet.py | 0 project/{ => sprite}/SpritesheetManager.py | 6 +- project/sprite/StaticSprite.py | 41 +++++++ project/ui_elements/TextLabel.py | 35 ++++++ 18 files changed, 460 insertions(+), 37 deletions(-) create mode 100644 project/data/font/MilkyNice.ttf create mode 100644 project/data/levels/1-1.csv create mode 100644 project/data/levels/levels.json rename project/{ => data}/sprites/sprites.json (100%) rename project/{ => data}/sprites/test_1.png (100%) create mode 100644 project/level/Level.py create mode 100644 project/level/LevelElementSymbols.py create mode 100644 project/level/LevelManager.py create mode 100644 project/physics/PhysicsElementsHandler.py create mode 100644 project/sprite/BoundingBox.py create mode 100644 project/sprite/DynamicSprite.py rename project/{ => sprite}/PositionScale.py (82%) rename project/{ => sprite}/Sprite.py (64%) rename project/{ => sprite}/Spritesheet.py (100%) rename project/{ => sprite}/SpritesheetManager.py (91%) create mode 100644 project/sprite/StaticSprite.py create mode 100644 project/ui_elements/TextLabel.py diff --git a/project/data/font/MilkyNice.ttf b/project/data/font/MilkyNice.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a1cc8bd0260edaa3ad4b6cad514cbf8256d1124b GIT binary patch literal 99732 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 E+NwA`tIojc3FDvrgyDwTm}hr(D? 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 literal 0 HcmV?d00001 diff --git a/project/data/levels/1-1.csv b/project/data/levels/1-1.csv new file mode 100644 index 0000000..6fe1a88 --- /dev/null +++ b/project/data/levels/1-1.csv @@ -0,0 +1,43 @@ +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,X,X,X,X,X,X,X,X,X,,,,,,,,,,,,,,,,,D,,, +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +3,3,id=HEBEL,requires=HEBEL;HEBEL-2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/project/data/levels/levels.json b/project/data/levels/levels.json new file mode 100644 index 0000000..159da51 --- /dev/null +++ b/project/data/levels/levels.json @@ -0,0 +1,10 @@ +[ + { + "name": "1-1", + "theme": "ghost", + "abilities": [ + "dash" + ], + "file": "1-1.csv" + } +] \ No newline at end of file diff --git a/project/sprites/sprites.json b/project/data/sprites/sprites.json similarity index 100% rename from project/sprites/sprites.json rename to project/data/sprites/sprites.json diff --git a/project/sprites/test_1.png b/project/data/sprites/test_1.png similarity index 100% rename from project/sprites/test_1.png rename to project/data/sprites/test_1.png diff --git a/project/level/Level.py b/project/level/Level.py new file mode 100644 index 0000000..bbefd3b --- /dev/null +++ b/project/level/Level.py @@ -0,0 +1,44 @@ +LEVEL_SIZE = (71, 40) + + +class Level: + def __init__(self, name: str, theme: str, abilities: list[str], csv_grid: list[str]): + self.name = name + self.theme = theme + self.abilities = abilities + self.tiles = self.parse_csv_to_2darray(csv_grid) + + def __str__(self): + return 'name: ' + self.name + ', theme: ' + self.theme + ', allowed abilities: ' + str(self.abilities) + + def parse_csv_to_2darray(self, csv_grid: list[str]): + tiles = [] + + for line_number, line in enumerate(csv_grid): + if line_number <= LEVEL_SIZE[1]: + tiles.append([]) + for item_number, item in enumerate(line): + if item_number <= LEVEL_SIZE[0]: + tiles[line_number].append({'tile': item}) + else: + print('Level is too wide:', item_number, '>', LEVEL_SIZE[0]) + break + + elif line[0] != '': + x = int(line[0]) + y = int(line[1]) + tile = tiles[x][y] + + for i in range(2, len(line)): + if line[i] == '': + break + + split_item = line[i].split('=') + if split_item[0] == 'id': + tile[split_item[0]] = split_item[1] + elif split_item[0] == 'requires': + tile[split_item[0]] = split_item[1].split(';') + else: + raise ValueError('Incorrect attribute name: ' + split_item[0]) + tiles[x][y] = tile + return tiles diff --git a/project/level/LevelElementSymbols.py b/project/level/LevelElementSymbols.py new file mode 100644 index 0000000..07ea959 --- /dev/null +++ b/project/level/LevelElementSymbols.py @@ -0,0 +1,18 @@ +import csv + +SCREEN_WIDTH = 71 +SCREEN_HEIGHT = 40 + +STATIC = 'static' +DYNAMIC = 'dynamic' + + +class LevelElementSymbols: + SOLID_BLOCK = { + 'symbol': '#', + 'type': STATIC + } + AIR = { + 'symbol': '', + 'type': STATIC + } diff --git a/project/level/LevelManager.py b/project/level/LevelManager.py new file mode 100644 index 0000000..6cf6a30 --- /dev/null +++ b/project/level/LevelManager.py @@ -0,0 +1,29 @@ +import sys +import os +import json + +from level.Level import Level + + +class LevelManager: + def __init__(self, level_dir: str): + self.level_dir = level_dir + self.levels = [] + + def load_from_config(self, config_file: str): + print('Loading levels from sprite sheet config file', config_file) + config = json.load(open(config_file)) + + for level_data in config: + csv = self.parse_csv(self.level_dir + '/' + level_data['file']) + level = Level(level_data['name'], level_data['theme'], level_data['abilities'], csv) + self.levels.append(level) + + def parse_csv(self, file: str): + csv_array = [] + with open(file) as csvfile: + for row in csvfile: + split_row = row.split(',') + filtered_row = list(map(lambda x: x.replace('\n', ''), split_row)) + csv_array.append(filtered_row) + return csv_array diff --git a/project/main.py b/project/main.py index c7d06a2..ad255f6 100644 --- a/project/main.py +++ b/project/main.py @@ -1,35 +1,117 @@ import random import pygame +from pygame.font import Font -from PositionScale import PositionScale -from SpritesheetManager import SpritesheetManager -from Sprite import Sprite +from level.LevelManager import LevelManager +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.TextLabel import TextLabel -pygame.init() -screen = pygame.display.set_mode((300, 300)) -pygame.display.set_caption("PE GAME") -clock = pygame.time.Clock() -spritesheet_manager = SpritesheetManager("sprites", "sprites/sprites.json") +what_to_run = 'physics' -test_1_sprite = Sprite(spritesheet_manager.get_sheet("test_1")) +if what_to_run == 'level': + csv_parse_test = LevelManager('data/levels') + csv_parse_test.load_from_config('data/levels/levels.json') + print(csv_parse_test.levels[0]) -test_1_sprite.dump("debug.png") -while True: - clock.tick(5) +elif what_to_run == 'physics': + screen_transform = PositionScale((0, 0), (4, 4)) - for event in pygame.event.get(): - if event.type == pygame.QUIT: - pygame.quit() + pygame.init() + screen = pygame.display.set_mode((600, 500)) + pygame.display.set_caption("PE GAME") + clock = pygame.time.Clock() - screen.fill((0, 0, 0)) + spritesheet_manager = SpritesheetManager("data/sprites", "data/sprites/sprites.json") - test_1_sprite.tick(1) - test_1_sprite.draw(screen, PositionScale((40, 40), (3, 3))) - pygame.display.update() + test_1_sprite = DynamicSprite(spritesheet_manager.get_sheet("test_1")) + test_2_sprite = StaticSprite(spritesheet_manager.get_sheet("test_1")) - if random.randint(1, 10) == 1: - test_1_sprite.set_animation_state(random.choice(["walk_r", "walk_l", "idle", "other_test"])) - print(test_1_sprite.animation_state) + test_1_sprite.position_scale = PositionScale((10, 10), (1, 1)) + test_2_sprite.position_scale = PositionScale((100, 100), (1, 1)) + + physics_handler = PhysicsElementsHandler() + physics_handler.add_sprite(test_1_sprite) + physics_handler.add_sprite(test_2_sprite) + + while True: + clock.tick(10) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + exit(0) + + screen.fill((0, 0, 0)) + + physics_handler.tick(1) + physics_handler.draw(screen, screen_transform) + pygame.display.update() + + +elif what_to_run == 'textlabel': + screen_transform = PositionScale((0, 0), (4, 4)) + + pygame.init() + screen = pygame.display.set_mode((300, 300)) + pygame.display.set_caption("PM GAME") + clock = pygame.time.Clock() + + test = TextLabel("Hallo", 100, 100, 50, Font('data/font/MilkyNice.otf')) + + while True: + clock.tick(5) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + + screen.fill((0, 0, 0)) + + test.draw(screen, screen_transform) + pygame.display.update() + + +elif what_to_run == 'sprite': + screen_transform = PositionScale((0, 0), (4, 4)) + + pygame.init() + screen = pygame.display.set_mode((300, 300)) + pygame.display.set_caption("PE GAME") + clock = pygame.time.Clock() + + spritesheet_manager = SpritesheetManager("data/sprites", "data/sprites/sprites.json") + + test_1_sprite = Sprite(spritesheet_manager.get_sheet("test_1")) + test_2_sprite = Sprite(spritesheet_manager.get_sheet("test_1")) + + test_1_sprite.position_scale = PositionScale((10, 10), (1, 1)) + test_2_sprite.position_scale = PositionScale((60, 60), (1, 1)) + + # test_1_sprite.dump("debug.png") + + while True: + clock.tick(5) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + + screen.fill((0, 0, 0)) + + test_1_sprite.tick(1) + test_1_sprite.draw(screen, screen_transform) + test_2_sprite.tick(1) + test_2_sprite.draw(screen, screen_transform) + pygame.display.update() + + if random.randint(1, 10) == 1: + test_1_sprite.set_animation_state(random.choice(["walk_r", "walk_l", "idle", "other_test"])) + print(test_1_sprite.animation_state) diff --git a/project/physics/PhysicsElementsHandler.py b/project/physics/PhysicsElementsHandler.py new file mode 100644 index 0000000..11e8392 --- /dev/null +++ b/project/physics/PhysicsElementsHandler.py @@ -0,0 +1,62 @@ +from pygame.surface import Surface + +from sprite.DynamicSprite import DynamicSprite +from sprite.PositionScale import PositionScale +from sprite.Sprite import Sprite +from sprite.StaticSprite import StaticSprite + +MOTION_STEPS = 10 + +class PhysicsElementsHandler: + def __init__(self): + self.sprites: list[Sprite] = [] + + def add_sprite(self, sprite: Sprite): + self.sprites.append(sprite) + + def remove_sprite(self, sprite: Sprite): + self.sprites.remove(sprite) + + def tick(self, dt: float): + for sprite in self.sprites: + sprite.tick(dt) + + # handle motion and collisions. To do this: + # 1. Find all sprites that have collision enabled and store them in a list + # 2. Create a list of all sprites that are dynamic sprites + # 3. Sort the sprites by their y position + # 4. For each sprite: + # 4.1. Divide the motion into MOTION_STEPS steps + # 4.2. For each step: + # 4.2.1. Check if the sprite collides with any other sprite + # 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)] + + dynamic_sprites = [sprite for sprite in self.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: + total_motion = sprite.motion + motion_step = (total_motion[0] / MOTION_STEPS, total_motion[1] / MOTION_STEPS) + + for i in range(MOTION_STEPS): + sprite.position_scale.position = ( + sprite.position_scale.position[0] + motion_step[0], + sprite.position_scale.position[1] + motion_step[1] + ) + + for collider in colliders: + if sprite is not collider and sprite.collides_with(collider): + sprite.position_scale.position = ( + sprite.position_scale.position[0] - motion_step[0], + sprite.position_scale.position[1] - motion_step[1] + ) + + sprite.motion = (0, 0) + break + + def draw(self, screen: Surface, screen_transform: PositionScale): + for sprite in self.sprites: + sprite.draw(screen, screen_transform) diff --git a/project/sprite/BoundingBox.py b/project/sprite/BoundingBox.py new file mode 100644 index 0000000..d6eb886 --- /dev/null +++ b/project/sprite/BoundingBox.py @@ -0,0 +1,13 @@ + +class BoundingBox: + def __init__(self, x, y, width, height): + self.x = x + self.y = y + self.width = width + self.height = height + + def get_dimensions(self): + return self.width, self.height + + def get_position(self): + return self.x, self.y diff --git a/project/sprite/DynamicSprite.py b/project/sprite/DynamicSprite.py new file mode 100644 index 0000000..6dd9207 --- /dev/null +++ b/project/sprite/DynamicSprite.py @@ -0,0 +1,25 @@ +import pygame +import sys + +from sprite.Sprite import Sprite +from sprite.Spritesheet import Spritesheet +from sprite.StaticSprite import StaticSprite + +sys.path.append('./sprite') + + +class DynamicSprite(StaticSprite): + def __init__(self, spritesheet: Spritesheet): + super().__init__(spritesheet) + + self.motion = (0, 0) + self.deceleration_horizontal = 0 + self.gravity = 0 + + def tick(self, dt: float): + super().tick(dt) + + self.motion = ( + self.motion[0] - self.deceleration_horizontal * dt, + self.motion[1] + self.gravity * dt + ) diff --git a/project/PositionScale.py b/project/sprite/PositionScale.py similarity index 82% rename from project/PositionScale.py rename to project/sprite/PositionScale.py index 90fde85..1666c63 100644 --- a/project/PositionScale.py +++ b/project/sprite/PositionScale.py @@ -1,5 +1,5 @@ class PositionScale: - def __init__(self, position=(0, 0), scale=(1, 1)): + def __init__(self, position: tuple[int, int] = (0, 0), scale: tuple[int, int] = (1, 1)): self.position = position self.scale = scale diff --git a/project/Sprite.py b/project/sprite/Sprite.py similarity index 64% rename from project/Sprite.py rename to project/sprite/Sprite.py index 9ace5b2..0628199 100644 --- a/project/Sprite.py +++ b/project/sprite/Sprite.py @@ -1,10 +1,15 @@ import pygame +import sys -from PositionScale import PositionScale +from sprite.BoundingBox import BoundingBox +from sprite.PositionScale import PositionScale +from sprite.Spritesheet import Spritesheet + +sys.path.append('./sprite') class Sprite: - def __init__(self, spritesheet): + def __init__(self, spritesheet: Spritesheet): self.spritesheet = spritesheet self.animation_state = list(self.spritesheet.animations.keys())[0] @@ -18,34 +23,40 @@ class Sprite: self.image = None - def tick(self, dt): + self.is_collider = True + + def tick(self, dt: float): animation = self.spritesheet.animations[self.animation_state] if self.animated: self.animation_delay += dt - while self.animation_delay >= animation["delays"][self.animation_frame % len(animation["delays"])]: - self.animation_delay -= animation["delays"][self.animation_frame % len(animation["delays"])] - self.animation_frame = (self.animation_frame + 1) % len(animation["images"]) + while self.animation_delay >= animation['delays'][self.animation_frame % len(animation['delays'])]: + self.animation_delay -= animation['delays'][self.animation_frame % len(animation['delays'])] + self.animation_frame = (self.animation_frame + 1) % len(animation['images']) - self.image = animation["images"][self.animation_frame % len(animation["images"])] + self.image = animation['images'][self.animation_frame % len(animation['images'])] - def set_animation_state(self, state): + def set_animation_state(self, state: str): if state in self.spritesheet.animations: self.animation_state = state self.animation_delay = 0 self.tick(0) - def set_animation_frame(self, frame): + def set_animation_frame(self, frame: int): self.animation_frame = frame self.tick(0) - def draw(self, screen, screen_transform): + def draw(self, screen: pygame.Surface, screen_transform: PositionScale): if not self.visible: return if self.image is not None: target_position = screen_transform.apply_scale_to_position() + target_position = ( + target_position[0] + self.position_scale.position[0], + target_position[1] + self.position_scale.position[1] + ) target_scale = ( screen_transform.scale[0] * self.position_scale.scale[0], @@ -60,6 +71,14 @@ class Sprite: screen.blit(target_image, target_position) + def get_bounding_box(self) -> BoundingBox: + return BoundingBox( + self.position_scale.position[0], + self.position_scale.position[1], + self.image.get_width(), + self.image.get_height() + ) + def get_scaled_image(self, image, resize): return pygame.transform.scale(image, resize) @@ -71,7 +90,7 @@ class Sprite: for animation in self.spritesheet.animations.values(): max_height = 0 total_width = 0 - for image in animation["images"]: + for image in animation['images']: total_width += image.get_width() max_height = max(max_height, image.get_height()) width = max(width, total_width) @@ -83,7 +102,7 @@ class Sprite: y = 0 for animation in self.spritesheet.animations.values(): max_height = 0 - for image in animation["images"]: + for image in animation['images']: sheet.blit(image, (x, y)) x += image.get_width() max_height = max(max_height, image.get_height()) diff --git a/project/Spritesheet.py b/project/sprite/Spritesheet.py similarity index 100% rename from project/Spritesheet.py rename to project/sprite/Spritesheet.py diff --git a/project/SpritesheetManager.py b/project/sprite/SpritesheetManager.py similarity index 91% rename from project/SpritesheetManager.py rename to project/sprite/SpritesheetManager.py index 50abcb4..ea704a2 100644 --- a/project/SpritesheetManager.py +++ b/project/sprite/SpritesheetManager.py @@ -1,7 +1,9 @@ -import pygame import json +import sys -from Spritesheet import Spritesheet +from sprite.Spritesheet import Spritesheet + +sys.path.append('./sprite') # This class is used to load named sprite sheets from the img folder. diff --git a/project/sprite/StaticSprite.py b/project/sprite/StaticSprite.py new file mode 100644 index 0000000..a346c08 --- /dev/null +++ b/project/sprite/StaticSprite.py @@ -0,0 +1,41 @@ +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): + super().__init__(spritesheet) + + self.position = (0, 0) + + def collides_with(self, collider: 'StaticSprite'): + if not self.is_collider or not collider.is_collider: + return False + + self_bounds = self.get_bounding_box() + other_bounds = collider.get_bounding_box() + + self_dimensions = self_bounds.get_dimensions() + other_dimensions = other_bounds.get_dimensions() + + self_position = self_bounds.get_position() + other_position = other_bounds.get_position() + + if self_position[0] + self_dimensions[0] < other_position[0]: + return False + + if self_position[0] > other_position[0] + other_dimensions[0]: + return False + + if self_position[1] + self_dimensions[1] < other_position[1]: + return False + + if self_position[1] > other_position[1] + other_dimensions[1]: + return False + + return True diff --git a/project/ui_elements/TextLabel.py b/project/ui_elements/TextLabel.py new file mode 100644 index 0000000..8000788 --- /dev/null +++ b/project/ui_elements/TextLabel.py @@ -0,0 +1,35 @@ +import pygame +from pygame.examples.textinput import Screen +from pygame.font import Font + +from sprite.PositionScale import PositionScale + + +class TextLabel: + # pygame.font.Font('data/font/arcade.TTF') + def __init__(self, text: str, x_position: float, y_position: float, font_size: float, font: Font, + alignment: str = "left"): + 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.font_size = font_size + self.font = font + self.position_scale = PositionScale() + + def draw(self, screen: Screen, screen_transform: PositionScale): + rendered_font = self.font.render(str(self.text), True, (255, 255, 255)) + self.current_width = rendered_font.get_width() + self.current_height = rendered_font.get_height() + + if self.alignment == "right": + screen.blit(rendered_font, (self.x_position - self.current_width / 2, self.y_position)) + elif self.alignment == "right": + screen.blit(rendered_font, (self.x_position, self.y_position)) + elif self.alignment == "center": + screen.blit(rendered_font, (self.x_position - self.current_width, self.y_position)) + + def set_text(self, new_text: str): + self.text = new_text