diff --git a/project/data/levels/1-1.csv b/project/data/levels/1-1.csv index da5f96c..d736825 100644 --- a/project/data/levels/1-1.csv +++ b/project/data/levels/1-1.csv @@ -12,21 +12,21 @@ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,x,x,x,x,x,x,x,x,x,,,,,,,,,,,,,,,,,D,,, +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,x,x,x,x,x,x,x,x,x,,,,,,,,,,,,,,,,,D,,,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# @@ -38,6 +38,4 @@ #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# -#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -3,3,id=HEBEL,requires=HEBEL;HEBEL-2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# \ No newline at end of file diff --git a/project/main.py b/project/main.py index 8bbac7a..0c11c11 100644 --- a/project/main.py +++ b/project/main.py @@ -6,7 +6,7 @@ from level.LevelManager import LevelManager from level.elements.LoadedLevel import LoadedLevel from physics.SpriteManager import SpriteManager, DrawLayers from physics.TickData import TickData -from physics.controllers.PlayerSprite import PlayerSprite +from physics.sprites.PlayerSprite import PlayerSprite from sprite.PositionScale import PositionScale from sprite.SpritesheetManager import SpritesheetManager from sprite.Sprite import Sprite @@ -47,6 +47,16 @@ if what_to_run == 'level': generated_level = LoadedLevel(sprite_manager, spritesheet_manager) generated_level.load_level(parsed_levels_manager.levels[0]) + ghost_character = PlayerSprite(spritesheet_manager.get_sheet("ghost_character")) + ghost_character.position_scale = PositionScale((90, 50), (1, 1)) + sprite_manager.add_ui_element(DrawLayers.OBJECTS, ghost_character) + + text_1 = TextLabel("Frame: 0", 2, 110, 50, alignment="left") + text_1.position_scale.scale = (0.3, 0.3) + sprite_manager.add_ui_element(DrawLayers.UI, text_1) + + ghost_character.debug_label = text_1 + while True: clock.tick(frame_rate) diff --git a/project/physics/CollisionDirection.py b/project/physics/CollisionDirection.py new file mode 100644 index 0000000..dd98a93 --- /dev/null +++ b/project/physics/CollisionDirection.py @@ -0,0 +1,14 @@ +class CollisionDirection: + LEFT = 0 + RIGHT = 1 + TOP = 2 + BOTTOM = 3 + DIRECTION_NAMES = ['LEFT', 'RIGHT', 'TOP', 'BOTTOM'] + + def __init__(self, direction: int, primary_sprite, secondary_sprite): + self.direction = direction + self.primary_sprite = primary_sprite + self.secondary_sprite = secondary_sprite + + def to_string(self): + return 'CollisionDirection.' + CollisionDirection.DIRECTION_NAMES[self.direction] diff --git a/project/physics/PhysicsElementsHandler.py b/project/physics/PhysicsElementsHandler.py index 53005b2..26da8b3 100644 --- a/project/physics/PhysicsElementsHandler.py +++ b/project/physics/PhysicsElementsHandler.py @@ -1,5 +1,6 @@ from typing import Optional +from physics.CollisionDirection import CollisionDirection from physics.TickData import TickData from sprite.DynamicSprite import DynamicSprite from sprite.Sprite import Sprite @@ -40,6 +41,8 @@ class PhysicsElementsHandler: # 4.2.3. If it doesn't, move the sprite to the new position colliders = [sprite for sprite in sprites if isinstance(sprite, StaticSprite) and sprite.is_collider] + for collider in colliders: + collider.reset_collides_with() 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]) @@ -55,56 +58,65 @@ class PhysicsElementsHandler: total_motion = sprite.motion motion_step = ((total_motion[0] * dt) / motion_steps, (total_motion[1] * dt) / motion_steps) + collides_with_last = None + collided = [False, False] + sprite.reset_collides_with() + for i in range(motion_steps): - sprite.reset_touches() - - sprite.position_scale.position = ( - sprite.position_scale.position[0] + motion_step[0], - sprite.position_scale.position[1] - ) - - collides_with = self.check_collides(sprite, colliders) - if collides_with is not None: + if not collided[0]: sprite.position_scale.position = ( - sprite.position_scale.position[0] - motion_step[0], + sprite.position_scale.position[0] + motion_step[0], sprite.position_scale.position[1] ) - if sprite.motion[0] > 0: - sprite.set_touches_right(True) + collides_with = self.check_collides(sprite, colliders) + if collides_with is not None: + sprite.position_scale.position = ( + sprite.position_scale.position[0] - motion_step[0], + sprite.position_scale.position[1] + ) - if sprite.motion[0] < 0: - sprite.set_touches_left(True) + if sprite.motion[0] > 0: + sprite.add_collides_with(CollisionDirection(CollisionDirection.RIGHT, sprite, collides_with)) - sprite.motion = (0, sprite.motion[1]) - return collides_with + if sprite.motion[0] < 0: + sprite.add_collides_with(CollisionDirection(CollisionDirection.LEFT, sprite, collides_with)) - sprite.position_scale.position = ( - sprite.position_scale.position[0], - sprite.position_scale.position[1] + motion_step[1] - ) + sprite.motion = (0, sprite.motion[1]) - collides_with = self.check_collides(sprite, colliders) - if collides_with is not None: + collides_with_last = collides_with + collided[0] = True + + if not collided[1]: sprite.position_scale.position = ( sprite.position_scale.position[0], - sprite.position_scale.position[1] - motion_step[1] + sprite.position_scale.position[1] + motion_step[1] ) - if sprite.motion[1] > 0: - sprite.set_touches_bottom(True) + collides_with = self.check_collides(sprite, colliders) + if collides_with is not None: + sprite.position_scale.position = ( + sprite.position_scale.position[0], + sprite.position_scale.position[1] - motion_step[1] + ) - if sprite.motion[1] < 0: - sprite.set_touches_top(True) + if sprite.motion[1] > 0: + sprite.add_collides_with(CollisionDirection(CollisionDirection.BOTTOM, sprite, collides_with)) - sprite.motion = (sprite.motion[0], 0) - return collides_with + if sprite.motion[1] < 0: + sprite.add_collides_with(CollisionDirection(CollisionDirection.TOP, sprite, collides_with)) - return None + sprite.motion = (sprite.motion[0], 0) + + collides_with_last = collides_with + collided[1] = True + + return collides_with_last def check_collides(self, sprite: StaticSprite, colliders: list[StaticSprite]) -> Optional[StaticSprite]: for collider in colliders: - if sprite is not collider and sprite.collides_with(collider, TOLERANCE): - return collider + if sprite is not collider: + if sprite.collides_with(collider, TOLERANCE): + return collider return None diff --git a/project/physics/SpriteManager.py b/project/physics/SpriteManager.py index 3b3ec38..85ddc00 100644 --- a/project/physics/SpriteManager.py +++ b/project/physics/SpriteManager.py @@ -1,6 +1,5 @@ from typing import Optional -import pygame from pygame import Surface from physics.PhysicsElementsHandler import PhysicsElementsHandler @@ -57,5 +56,11 @@ class SpriteManager: return elements def collision_detected(self, sprite_a: Sprite, sprite_b: Sprite): - # print(f"Collision detected between {sprite_a} and {sprite_b}") pass + + def find_sprite_by_uuid(self, uuid: str) -> Optional[UiElement]: + for layer in DrawLayers.DRAW_ORDER: + for sprite in self.layers[layer]: + if sprite.uuid == uuid: + return sprite + return None diff --git a/project/physics/controllers/PlayerSprite.py b/project/physics/controllers/PlayerSprite.py deleted file mode 100644 index 5828166..0000000 --- a/project/physics/controllers/PlayerSprite.py +++ /dev/null @@ -1,24 +0,0 @@ -from physics.TickData import TickData -from sprite.DynamicSprite import DynamicSprite -from sprite.Spritesheet import Spritesheet -from ui_elements.KeyManager import KeyManager -from ui_elements.TextLabel import TextLabel - - -class PlayerSprite(DynamicSprite): - def __init__(self, spritesheet: Spritesheet): - super().__init__(spritesheet) - self.jump_time = -1 - self.allowed_jump_time = 20 - self.debug_label = TextLabel('', -1, -1) - - def tick(self, tick_data: TickData): - super().tick(tick_data) - - if tick_data.key_manager.is_keymap_down(KeyManager.KEY_RIGHT): - self.motion = (self.motion[0] + 2, self.motion[1]) - - if tick_data.key_manager.is_keymap_down(KeyManager.KEY_LEFT): - self.motion = (self.motion[0] - 2, self.motion[1]) - - self.debug_label.set_text(f'jump: {self.jump_time}, x: {round(self.motion[0], 2)}, y: {round(self.motion[1], 2)}, touches: {self.touches_bounding}') diff --git a/project/physics/sprites/DeathBox.py b/project/physics/sprites/DeathBox.py new file mode 100644 index 0000000..ff5bc51 --- /dev/null +++ b/project/physics/sprites/DeathBox.py @@ -0,0 +1,11 @@ +from physics.TickData import TickData +from sprite.Spritesheet import Spritesheet +from sprite.StaticSprite import StaticSprite + + +class DeathBox(StaticSprite): + def __init__(self, spritesheet: Spritesheet): + super().__init__(spritesheet) + + def tick(self, tick_data: TickData): + super().tick(tick_data) diff --git a/project/physics/sprites/PlayerSprite.py b/project/physics/sprites/PlayerSprite.py new file mode 100644 index 0000000..08666b0 --- /dev/null +++ b/project/physics/sprites/PlayerSprite.py @@ -0,0 +1,45 @@ +from physics.CollisionDirection import CollisionDirection +from physics.TickData import TickData +from sprite.DynamicSprite import DynamicSprite +from sprite.Spritesheet import Spritesheet +from ui_elements.KeyManager import KeyManager +from ui_elements.TextLabel import TextLabel + + +class PlayerSprite(DynamicSprite): + def __init__(self, spritesheet: Spritesheet): + super().__init__(spritesheet) + self.debug_label = TextLabel('', -1, -1) + + self.jump_time = -1 + self.allowed_jump_time = 12 + + self.acceleration_horizontal = 2 + + self.deceleration_horizontal_air = 0.02 + self.deceleration_horizontal_ground = 0.3 + self.gravity = 9.81 / 10 + + self.max_motion_horizontal_via_input = 5 + + def tick(self, tick_data: TickData): + super().tick(tick_data) + + if tick_data.key_manager.is_keymap_down(KeyManager.KEY_RIGHT): + if self.motion[0] < self.max_motion_horizontal_via_input: + self.motion = (self.motion[0] + self.acceleration_horizontal, self.motion[1]) + + if tick_data.key_manager.is_keymap_down(KeyManager.KEY_LEFT): + if self.motion[0] > -self.max_motion_horizontal_via_input: + self.motion = (self.motion[0] - self.acceleration_horizontal, self.motion[1]) + + if tick_data.key_manager.is_keymap_down(KeyManager.KEY_UP): + if self.jump_time < 0 and self.get_collides_with_direction(CollisionDirection.BOTTOM): + self.jump_time = self.allowed_jump_time + self.motion = (self.motion[0], self.motion[1] - 7) + if self.jump_time >= 0: + self.motion = (self.motion[0], self.motion[1] - 0.5) + if self.jump_time >= 0: + self.jump_time -= 1 + + self.debug_label.set_text(f'jump: {self.jump_time}, x: {round(self.motion[0], 2)}, y: {round(self.motion[1], 2)}, touches: {list(map(lambda x: x.to_string(), self.get_collides_with()))}') diff --git a/project/sprite/DynamicSprite.py b/project/sprite/DynamicSprite.py index 65dc1c2..357ed27 100644 --- a/project/sprite/DynamicSprite.py +++ b/project/sprite/DynamicSprite.py @@ -1,4 +1,4 @@ -from sprite.BoundingBox import BoundingBox +from physics.CollisionDirection import CollisionDirection from sprite.Spritesheet import Spritesheet from sprite.StaticSprite import StaticSprite from physics.TickData import TickData @@ -10,39 +10,23 @@ class DynamicSprite(StaticSprite): self.motion = (0, 0) + self.apply_base_deceleration = True self.deceleration_horizontal_air = 0.02 self.deceleration_horizontal_ground = 0.3 self.gravity = 9.81 / 10 - # 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 + if self.apply_base_deceleration: + deceleration_horizontal = 0 + if abs(self.motion[0]) > 0: + if self.get_collides_with_direction(CollisionDirection.BOTTOM): + 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]) - - def set_touches_right(self, value: bool): - self.touches_bounding = (self.touches_bounding[0], value, self.touches_bounding[2], self.touches_bounding[3]) - - def set_touches_left(self, value: bool): - self.touches_bounding = (self.touches_bounding[0], self.touches_bounding[1], self.touches_bounding[2], value) - - def set_touches_top(self, value: bool): - self.touches_bounding = (value, self.touches_bounding[1], self.touches_bounding[2], self.touches_bounding[3]) - - def reset_touches(self): - self.touches_bounding = (False, False, False, False) + self.motion = ( + self.motion[0] - deceleration_horizontal * self.motion[0] * tick_data.dt, + self.motion[1] + self.gravity * tick_data.dt + ) diff --git a/project/sprite/PositionScale.py b/project/sprite/PositionScale.py index d3f6be2..850cfc5 100644 --- a/project/sprite/PositionScale.py +++ b/project/sprite/PositionScale.py @@ -1,7 +1,7 @@ class PositionScale: def __init__(self, position: tuple[float, float] = (0, 0), scale: tuple[float, float] = (1, 1)): - self.position = position - self.scale = scale + self.position: tuple[float, float] = position + self.scale: tuple[float, float] = scale def apply_scale_to_position(self): return self.position[0] * self.scale[0], self.position[1] * self.scale[1] diff --git a/project/sprite/Sprite.py b/project/sprite/Sprite.py index f6a5eb6..f422ace 100644 --- a/project/sprite/Sprite.py +++ b/project/sprite/Sprite.py @@ -1,5 +1,8 @@ +from typing import Optional + import pygame +from physics.CollisionDirection import CollisionDirection from physics.TickData import TickData from sprite.BoundingBox import BoundingBox from sprite.PositionScale import PositionScale @@ -22,6 +25,22 @@ class Sprite(UiElement): self.image = None self.is_collider = True + self.collides_with_elements: list[CollisionDirection] = [] + + def add_collides_with(self, collision_direction: CollisionDirection): + self.collides_with_elements.append(collision_direction) + + def reset_collides_with(self): + self.collides_with_elements = [] + + def get_collides_with(self) -> list[CollisionDirection]: + return self.collides_with_elements + + def get_collides_with_direction(self, direction: int) -> Optional[CollisionDirection]: + for collision_direction in self.collides_with_elements: + if collision_direction.direction == direction: + return collision_direction + return None def tick(self, tick_data: TickData): animation = self.spritesheet.animations[self.animation_state] diff --git a/project/ui_elements/UiElement.py b/project/ui_elements/UiElement.py index 489d02a..104874b 100644 --- a/project/ui_elements/UiElement.py +++ b/project/ui_elements/UiElement.py @@ -1,4 +1,5 @@ import abc +import uuid from typing import Optional import pygame @@ -16,6 +17,8 @@ class UiElement: self.visible = True self.click_listeners = [] + self.uuid = uuid.uuid4() + def add_click_listener(self, listener): self.click_listeners.append(listener)