Implemented framework for player controller and input handling

main
Yan Wittmann 2023-03-26 12:46:41 +02:00
parent da20414611
commit 65c8a17543
15 changed files with 156 additions and 92 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
from physics.controllers.ElementController import ElementController
class PlayerController(ElementController):
pass

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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