Generalized UI Elements

main
Yan Wittmann 2023-03-26 09:51:11 +02:00
parent 8cbf623247
commit 72fd82c5a6
7 changed files with 157 additions and 67 deletions

View File

@ -1,2 +1,7 @@
# sep-se-platformer # 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)

View File

@ -4,6 +4,7 @@ import pygame
from pygame.font import Font from pygame.font import Font
from level.LevelManager import LevelManager from level.LevelManager import LevelManager
from physics.SpriteManager import SpriteManager, DrawLayers
from sprite.DynamicSprite import DynamicSprite from sprite.DynamicSprite import DynamicSprite
from sprite.PositionScale import PositionScale from sprite.PositionScale import PositionScale
from sprite.SpritesheetManager import SpritesheetManager from sprite.SpritesheetManager import SpritesheetManager
@ -42,21 +43,24 @@ elif what_to_run == 'physics':
spritesheet_manager = SpritesheetManager("data/sprites", "data/sprites/sprites.json") 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 = DynamicSprite(spritesheet_manager.get_sheet("test_1"))
test_1_sprite.position_scale = PositionScale((10, -100), (1, 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 = DynamicSprite(spritesheet_manager.get_sheet("test_1"))
test_3_sprite.position_scale = PositionScale((130, 100), (1, 1)) test_3_sprite.position_scale = PositionScale((130, 100), (1, 1))
test_3_sprite.motion = (-9, -10) 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 = StaticSprite(spritesheet_manager.get_sheet("test_1"))
test_2_sprite.position_scale = PositionScale((10, 80), (1, 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: while True:
clock.tick(frame_rate) clock.tick(frame_rate)
@ -75,8 +79,8 @@ elif what_to_run == 'physics':
screen.fill((0, 0, 0)) screen.fill((0, 0, 0))
physics_handler.tick(apply_frame_rate(1)) sprite_manager.tick(apply_frame_rate(1))
physics_handler.draw(screen, screen_transform) sprite_manager.draw(screen, screen_transform)
pygame.display.update() pygame.display.update()
@ -88,9 +92,15 @@ elif what_to_run == 'textlabel':
pygame.display.set_caption("PM GAME") pygame.display.set_caption("PM GAME")
clock = pygame.time.Clock() clock = pygame.time.Clock()
test1 = TextLabel("Das ist ein Test", 400, 0, 50, alignment="left") test1 = TextLabel("Das ist ein Test", 100, 0, 50, alignment="left")
test2 = TextLabel("Das ist ein Test", 400, 10, 50, alignment="center") test2 = TextLabel("Das ist ein Test", 100, 50, 50, alignment="left")
test3 = TextLabel("Das ist ein Test", 400, 20, 50, alignment="right") 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: while True:
clock.tick(5) clock.tick(5)

View File

@ -4,6 +4,7 @@ from sprite.DynamicSprite import DynamicSprite
from sprite.PositionScale import PositionScale from sprite.PositionScale import PositionScale
from sprite.Sprite import Sprite from sprite.Sprite import Sprite
from sprite.StaticSprite import StaticSprite from sprite.StaticSprite import StaticSprite
from ui_elements.UiElement import UiElement
MOTION_STEPS = 10 MOTION_STEPS = 10
TOLERANCE = 1 TOLERANCE = 1
@ -11,16 +12,14 @@ TOLERANCE = 1
class PhysicsElementsHandler: class PhysicsElementsHandler:
def __init__(self): def __init__(self):
self.sprites: list[Sprite] = [] pass
def add_sprite(self, sprite: Sprite): def tick_all_layers(self, dt: float, layers: dict[str, list[UiElement]]):
self.sprites.append(sprite) for layer in layers:
self.tick(dt, layers[layer])
def remove_sprite(self, sprite: Sprite): def tick(self, dt: float, sprites: list[UiElement]):
self.sprites.remove(sprite) for sprite in sprites:
def tick(self, dt: float):
for sprite in self.sprites:
sprite.tick(dt) sprite.tick(dt)
# handle motion and collisions. To do this: # 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.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 # 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]) sorted_dynamic_sprites = sorted(dynamic_sprites, key=lambda spr: spr.position_scale.position[1])
for sprite in sorted_dynamic_sprites: for sprite in sorted_dynamic_sprites:
@ -97,7 +96,3 @@ class PhysicsElementsHandler:
return True return True
return False return False
def draw(self, screen: Surface, screen_transform: PositionScale):
for sprite in self.sprites:
sprite.draw(screen, screen_transform)

View File

@ -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)

View File

@ -4,21 +4,20 @@ import sys
from sprite.BoundingBox import BoundingBox from sprite.BoundingBox import BoundingBox
from sprite.PositionScale import PositionScale from sprite.PositionScale import PositionScale
from sprite.Spritesheet import Spritesheet from sprite.Spritesheet import Spritesheet
from ui_elements.UiElement import UiElement
sys.path.append('./sprite') sys.path.append('./sprite')
class Sprite: class Sprite(UiElement):
def __init__(self, spritesheet: Spritesheet): def __init__(self, spritesheet: Spritesheet):
super().__init__()
self.spritesheet = spritesheet self.spritesheet = spritesheet
self.animation_state = list(self.spritesheet.animations.keys())[0] self.animation_state = list(self.spritesheet.animations.keys())[0]
self.animation_frame = 0 self.animation_frame = 0
self.animation_delay = 0 self.animation_delay = 0
self.position_scale = PositionScale()
self.visible = True
self.animated = True self.animated = True
self.image = None self.image = None
@ -47,22 +46,8 @@ class Sprite:
self.animation_frame = frame self.animation_frame = frame
self.tick(0) self.tick(0)
def draw(self, screen: pygame.Surface, screen_transform: PositionScale): def render_sprite_image(self) -> pygame.Surface:
if not self.visible: return self.image
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 get_bounding_box(self) -> BoundingBox: def get_bounding_box(self) -> BoundingBox:
return BoundingBox( return BoundingBox(
@ -72,9 +57,6 @@ class Sprite:
self.image.get_height() * self.position_scale.scale[1] 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): def dump(self, file):
# re-attach all the images to a single surface and save it to a file # re-attach all the images to a single surface and save it to a file
width = 0 width = 0

View File

@ -3,20 +3,29 @@ from pygame import Surface, Rect
from pygame.font import Font from pygame.font import Font
from sprite.PositionScale import PositionScale from sprite.PositionScale import PositionScale
from ui_elements.UiElement import UiElement
class TextLabel: class TextLabel(UiElement):
def __init__(self, text: str, x_position: float, y_position: float, font_size: int, def tick(self, dt: float):
alignment: str = "left"): pass
def __init__(self, text: str, x_position: float, y_position: float, font_size: int, alignment: str = "left"):
super().__init__()
self.text = text self.text = text
self.x_position = x_position self.x_position = x_position
self.y_position = y_position self.y_position = y_position
self.alignment = alignment
self.current_width = 0 self.current_width = 0
self.current_height = 0 self.current_height = 0
self.alignment = alignment
self.font_size = font_size self.font_size = font_size
self.font = Font('data/font/MilkyNice.ttf', self.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): def draw(self, screen: Surface, screen_transform: PositionScale):
rendered_font = self.font.render(str(self.text), True, pygame.Color('white')) 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() self.current_height = rendered_font.get_height()
if self.alignment == "right": 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": 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": 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], super().draw(screen, screen_transform)
screen_transform.scale[1] * self.position_scale.scale[1])
target_position = ((self.position_scale.position[0] + screen_transform.position[0]) * target_scale[0], def set_text(self, new_text: str):
(self.position_scale.position[1] + screen_transform.position[1]) * target_scale[1]) self.text = new_text
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

View File

@ -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)