diff --git a/project/data/levels/1-1.csv b/project/data/levels/1-1.csv index 8915d8a..acacf11 100644 --- a/project/data/levels/1-1.csv +++ b/project/data/levels/1-1.csv @@ -25,8 +25,8 @@ #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# -#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# -#,,,,,,,,,,P,,,,,,P,,,,,,,,,,,,,,,,,,,,,,,,,,x,x,x,x,x,x,x,x,x,,,,,,,,,,,,,,,,,D,,,# +#,,,,,,,,,,,,,,,,,L,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,,,,,,,,,,P,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,x,x,x,x,x,x,x,x,x,,,,,,,,,,,,,,,,,D,,,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,r,,,,,,,,,,,l,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,r,,,,,,,,,,,,,,l,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,r,,,,,,,,,,,,,,l,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# @@ -40,5 +40,5 @@ #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,r,,,,,,,,,,,,,,l,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,r,,,,,,,,,,,,,,l,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -16,28,id=HEBEL,emitter_state=false,debug=1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -10,28,id=HEBEL,emitter_state=false,debug=2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, \ No newline at end of file +16,28,id=HEBEL,emitter_state=false,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +10,28,id=HEBEL,emitter_state=false,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, \ No newline at end of file diff --git a/project/level/Level.py b/project/level/Level.py index 8dbb759..e5d5286 100644 --- a/project/level/Level.py +++ b/project/level/Level.py @@ -35,7 +35,7 @@ class Level: continue split_item = line[i].split('=') - if split_item[0] in ['id', 'emitter_state', 'debug']: + if split_item[0] in ['id', 'emitter_state', 'debug', 'requires_and', 'requires_or', 'requires_xor']: tile[split_item[0]] = split_item[1] elif split_item[0] == 'requires': tile[split_item[0]] = split_item[1].split(';') diff --git a/project/level/LevelElementSymbols.py b/project/level/LevelElementSymbols.py deleted file mode 100644 index b5ad9c0..0000000 --- a/project/level/LevelElementSymbols.py +++ /dev/null @@ -1,64 +0,0 @@ - -STATIC = 'static' -DYNAMIC = 'dynamic' - - -class LevelElementSymbols: - SOLID_BLOCK = { - 'type': STATIC, - 'sprite_id': '_block_full', - 'themed': True - } - SOLID_BLOCK_TOP = { - - } - SOLID_BLOCK_LEFT = { - 'type': STATIC, - 'sprite_id': '_block_left' - } - SOLID_BLOCK_RIGHT = { - 'type': STATIC, - 'sprite_id': '_block_right' - } - AIR = { - 'type': STATIC, - 'sprite_id': 'air' - } - GATE = { - 'type':STATIC, - 'sprite_id': 'gate' - } - GOAL_DOOR = { - 'type': STATIC, - 'sprite_id': 'goal_door' - } - SMALL_SPIKE = { - 'type': STATIC, - 'sprite_id': 'small_spike' - } - LEVER = { - 'type': STATIC, - 'sprite_id': 'lever' - } - PLAYER = { - 'type': DYNAMIC, - 'sprite_id': 'player' - } - - dict = { - '#': SOLID_BLOCK, - '+': SOLID_BLOCK_TOP, - 'l': SOLID_BLOCK_RIGHT, - 'r': SOLID_BLOCK_LEFT, - '': AIR, - 'G': GATE, - 'D': GOAL_DOOR, - 'x': SMALL_SPIKE, - 'L': LEVER, - 'P': PLAYER - } - - BLOCKS_LIST = [SOLID_BLOCK, SOLID_BLOCK_RIGHT, SOLID_BLOCK_LEFT] - - INTERACTABLE_LIST = [LEVER] - diff --git a/project/level/elements/ButtonInputLevelElement.py b/project/level/elements/ButtonInputLevelElement.py index a71ef67..073dbfb 100644 --- a/project/level/elements/ButtonInputLevelElement.py +++ b/project/level/elements/ButtonInputLevelElement.py @@ -12,6 +12,9 @@ class ButtonInputLevelElement(InputLevelElement): super().__init__(tile, level) self.is_currently_stood_on = False + self.is_collider = False + self.register_collisions = True + def load(self, sprite_manager: SpriteManager, spritesheet_manager: SpritesheetManager, level: Level): self.spritesheet = spritesheet_manager.get_sheet('pressureplate') self.set_animation_state('on' if self.emitter_state else 'off') @@ -20,26 +23,16 @@ class ButtonInputLevelElement(InputLevelElement): def tick(self, tick_data: TickData): super().tick(tick_data) - collisions = self.get_collides_with_direction(CollisionDirection.TOP) + collisions = self.get_collides_with_direction(CollisionDirection.INSIDE) - # debug 1: only activate when player is standing on it - if self.tile['debug'] == '1': - if collisions and not self.is_currently_stood_on: - self.is_currently_stood_on = True - self.set_active(True) - elif not collisions and self.is_currently_stood_on: - self.is_currently_stood_on = False - self.set_active(False) - - # debug 2: toggle active state when player is standing on it - elif self.tile['debug'] == '2': - if collisions and not self.is_currently_stood_on: - self.is_currently_stood_on = True - self.set_active(not self.emitter_state) - elif not collisions and self.is_currently_stood_on: - self.is_currently_stood_on = False + # set active state only when player/object is standing on it + if collisions and not self.is_currently_stood_on: + self.is_currently_stood_on = True + self.set_active(True) + elif not collisions and self.is_currently_stood_on: + self.is_currently_stood_on = False + self.set_active(False) def set_active(self, active: bool): self.emitter_state = active self.set_animation_state('on' if self.emitter_state else 'off') - diff --git a/project/level/elements/LeverInputLevelElement.py b/project/level/elements/LeverInputLevelElement.py new file mode 100644 index 0000000..a21dd93 --- /dev/null +++ b/project/level/elements/LeverInputLevelElement.py @@ -0,0 +1,37 @@ +from level.Level import Level +from level.elements.InputLevelElement import InputLevelElement +from physics.CollisionDirection import CollisionDirection +from physics.SpriteManager import SpriteManager +from physics.TickData import TickData +from sprite.SpritesheetManager import SpritesheetManager + + +class LeverInputLevelElement(InputLevelElement): + + def __init__(self, tile: dict, level: Level): + super().__init__(tile, level) + self.is_currently_stood_on = False + + self.is_collider = False + self.register_collisions = True + + def load(self, sprite_manager: SpriteManager, spritesheet_manager: SpritesheetManager, level: Level): + self.spritesheet = spritesheet_manager.get_sheet('lever') + self.set_animation_state('on' if self.emitter_state else 'off') + self.position_scale.position = self.tile['position'] + + def tick(self, tick_data: TickData): + super().tick(tick_data) + + collisions = self.get_collides_with_direction(CollisionDirection.INSIDE) + + # toggle active state when player/object is standing on it + if collisions and not self.is_currently_stood_on: + self.is_currently_stood_on = True + self.set_active(not self.emitter_state) + elif not collisions and self.is_currently_stood_on: + self.is_currently_stood_on = False + + def set_active(self, active: bool): + self.emitter_state = active + self.set_animation_state('on' if self.emitter_state else 'off') diff --git a/project/level/elements/LoadedLevel.py b/project/level/elements/LoadedLevel.py index e548985..3aa9172 100644 --- a/project/level/elements/LoadedLevel.py +++ b/project/level/elements/LoadedLevel.py @@ -1,5 +1,6 @@ from level.Level import Level from level.elements.ButtonInputLevelElement import ButtonInputLevelElement +from level.elements.LeverInputLevelElement import LeverInputLevelElement from level.elements.SimpleBlockLevelElement import SimpleBlockLevelElement from level.elements.UnknownTileLevelElement import UnknownTileLevelElement from physics import ConstantsParser @@ -16,7 +17,7 @@ TILES = { '|': SimpleBlockLevelElement, 'P': ButtonInputLevelElement, - 'L': ButtonInputLevelElement, # TODO + 'L': LeverInputLevelElement, } @@ -52,7 +53,7 @@ class LoadedLevel: if isinstance(element, SimpleBlockLevelElement): if self.is_surrounded_by_blocks(world_position, level.tiles): element.is_collider = False - # self.sprite_manager.remove_ui_element(element) + self.sprite_manager.remove_ui_element(element) def is_surrounded_by_blocks(self, position, tiles): for i in range(-1, 2): diff --git a/project/main.py b/project/main.py index 98f6d76..243d50e 100644 --- a/project/main.py +++ b/project/main.py @@ -4,6 +4,7 @@ import pygame from level.LevelManager import LevelManager from level.elements.LoadedLevel import LoadedLevel +from physics import ConstantsParser from physics.SpriteManager import SpriteManager, DrawLayers from physics.TickData import TickData from physics.sprites.PlayerSprite import PlayerSprite @@ -32,7 +33,7 @@ if what_to_run == 'level': screen_transform = PositionScale((0, 0), (1.5, 1.5)) pygame.init() - screen = pygame.display.set_mode((12 * 71 * 1.5, 12 * 40 * 1.5)) + screen = pygame.display.set_mode((12 * ConstantsParser.CONFIG.level_size[0] * screen_transform.scale[0], 12 * ConstantsParser.CONFIG.level_size[1] * screen_transform.scale[1])) pygame.display.set_caption("PM GAME") clock = pygame.time.Clock() frame_rate = 30 @@ -51,13 +52,13 @@ if what_to_run == 'level': ghost_character.position_scale = PositionScale((90, 50), (1, 1)) sprite_manager.add_ui_element(DrawLayers.OBJECTS, ghost_character) - calculated_frame_rate_text = TextLabel("Frame rate: 0", 2, 110, 70, alignment="left") + calculated_frame_rate_text = TextLabel("0 FPS", 2, 2, 70, alignment="left") calculated_frame_rate_text.position_scale.scale = (0.3, 0.3) sprite_manager.add_ui_element(DrawLayers.UI, calculated_frame_rate_text) while True: clock.tick(frame_rate) - calculated_frame_rate_text.text = f"Frame rate: {round(clock.get_fps())}" + calculated_frame_rate_text.text = f"{round(clock.get_fps())} FPS" pygame_events: list[pygame.event.Event] = pygame.event.get() key_manager.update_key_events(pygame_events) diff --git a/project/physics/CollisionDirection.py b/project/physics/CollisionDirection.py index dd98a93..d6b5e30 100644 --- a/project/physics/CollisionDirection.py +++ b/project/physics/CollisionDirection.py @@ -3,7 +3,8 @@ class CollisionDirection: RIGHT = 1 TOP = 2 BOTTOM = 3 - DIRECTION_NAMES = ['LEFT', 'RIGHT', 'TOP', 'BOTTOM'] + INSIDE = 4 + DIRECTION_NAMES = ['LEFT', 'RIGHT', 'TOP', 'BOTTOM', 'INSIDE'] def __init__(self, direction: int, primary_sprite, secondary_sprite): self.direction = direction diff --git a/project/physics/PhysicsElementsHandler.py b/project/physics/PhysicsElementsHandler.py index 5ed1399..e3a135a 100644 --- a/project/physics/PhysicsElementsHandler.py +++ b/project/physics/PhysicsElementsHandler.py @@ -40,7 +40,9 @@ class PhysicsElementsHandler: # 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 sprites if isinstance(sprite, StaticSprite) and sprite.is_collider] + colliders = [sprite for sprite in sprites + if isinstance(sprite, StaticSprite) and (sprite.is_collider or sprite.register_collisions)] + for collider in colliders: collider.reset_collides_with() @@ -68,29 +70,45 @@ class PhysicsElementsHandler: sprite.position_scale.position[1] ) + # print('Elements: ', list(filter(lambda spr: str(type(spr)) == "", colliders))) + 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] - ) + for collider in collides_with: + if collider is not None: + if sprite.is_collider and collider.is_collider: + if sprite.motion[0] > 0: + if sprite.register_collisions: + sprite.add_collides_with( + CollisionDirection(CollisionDirection.RIGHT, sprite, collider)) + if collider.register_collisions: + collider.add_collides_with( + CollisionDirection(CollisionDirection.LEFT, collider, sprite)) - if sprite.motion[0] > 0: - sprite.add_collides_with( - CollisionDirection(CollisionDirection.RIGHT, sprite, collides_with)) - collides_with.add_collides_with( - CollisionDirection(CollisionDirection.LEFT, collides_with, sprite)) + if sprite.motion[0] < 0: + if sprite.register_collisions: + sprite.add_collides_with( + CollisionDirection(CollisionDirection.LEFT, sprite, collider)) + if collider.register_collisions: + collider.add_collides_with( + CollisionDirection(CollisionDirection.RIGHT, collider, sprite)) + else: + if sprite.register_collisions: + sprite.add_collides_with( + CollisionDirection(CollisionDirection.INSIDE, sprite, collider)) + if collider.register_collisions: + collider.add_collides_with( + CollisionDirection(CollisionDirection.INSIDE, collider, sprite)) - if sprite.motion[0] < 0: - sprite.add_collides_with( - CollisionDirection(CollisionDirection.LEFT, sprite, collides_with)) - collides_with.add_collides_with( - CollisionDirection(CollisionDirection.RIGHT, collides_with, sprite)) + if collider.is_collider: + sprite.position_scale.position = ( + sprite.position_scale.position[0] - motion_step[0], + sprite.position_scale.position[1] + ) - sprite.motion = (0, sprite.motion[1]) + sprite.motion = (0, sprite.motion[1]) - collides_with_last = collides_with - collided[0] = True + collides_with_last = collider + collided[0] = True if not collided[1]: sprite.position_scale.position = ( @@ -99,35 +117,51 @@ class PhysicsElementsHandler: ) 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] - ) + for collider in collides_with: + if collider is not None: + if sprite.is_collider and collider.is_collider: + if sprite.motion[1] > 0: + if sprite.register_collisions: + sprite.add_collides_with( + CollisionDirection(CollisionDirection.BOTTOM, sprite, collider)) + if collider.register_collisions: + collider.add_collides_with( + CollisionDirection(CollisionDirection.TOP, collider, sprite)) - if sprite.motion[1] > 0: - sprite.add_collides_with( - CollisionDirection(CollisionDirection.BOTTOM, sprite, collides_with)) - collides_with.add_collides_with( - CollisionDirection(CollisionDirection.TOP, collides_with, sprite)) + if sprite.motion[1] < 0: + if sprite.register_collisions: + sprite.add_collides_with( + CollisionDirection(CollisionDirection.TOP, sprite, collider)) + if collider.register_collisions: + collider.add_collides_with( + CollisionDirection(CollisionDirection.BOTTOM, collider, sprite)) + else: + if sprite.register_collisions: + sprite.add_collides_with( + CollisionDirection(CollisionDirection.INSIDE, sprite, collider)) + if collider.register_collisions: + collider.add_collides_with( + CollisionDirection(CollisionDirection.INSIDE, collider, sprite)) - if sprite.motion[1] < 0: - sprite.add_collides_with( - CollisionDirection(CollisionDirection.TOP, sprite, collides_with)) - collides_with.add_collides_with( - CollisionDirection(CollisionDirection.BOTTOM, collides_with, sprite)) + if collider.is_collider: + sprite.position_scale.position = ( + sprite.position_scale.position[0], + sprite.position_scale.position[1] - motion_step[1] + ) - sprite.motion = (sprite.motion[0], 0) + sprite.motion = (sprite.motion[0], 0) - collides_with_last = collides_with - collided[1] = True + collides_with_last = collider + collided[1] = True return collides_with_last - def check_collides(self, sprite: StaticSprite, colliders: list[StaticSprite]) -> Optional[StaticSprite]: + def check_collides(self, sprite: StaticSprite, colliders: list[StaticSprite]) -> list[StaticSprite]: + collides_with = [] + for collider in colliders: if sprite is not collider: if sprite.collides_with(collider, TOLERANCE): - return collider + collides_with.append(collider) - return None + return collides_with diff --git a/project/sprite/Sprite.py b/project/sprite/Sprite.py index ce16ded..2ab9185 100644 --- a/project/sprite/Sprite.py +++ b/project/sprite/Sprite.py @@ -28,6 +28,7 @@ class Sprite(UiElement): self.image = None self.is_collider = True + self.register_collisions = True self.collides_with_elements: list[CollisionDirection] = [] def add_collides_with(self, collision_direction: CollisionDirection): @@ -84,12 +85,16 @@ class Sprite(UiElement): def get_bounding_box(self) -> BoundingBox: if self.image is None: - print('get_bounding_box: No image for sprite: ' + str(self)) - return BoundingBox(0, 0, 0, 0) + print('Sprite.refresh_bounding_box: No image for sprite: ' + str(self)) + return self.bounding_box - return BoundingBox( - self.position_scale.position[0], - self.position_scale.position[1], - self.image.get_width() * self.position_scale.scale[0], - self.image.get_height() * self.position_scale.scale[1] - ) + if self.bounding_box.x != self.position_scale.position[0] or \ + self.bounding_box.y != self.position_scale.position[1]: + self.bounding_box = BoundingBox( + self.position_scale.position[0], + self.position_scale.position[1], + self.image.get_width() * self.position_scale.scale[0], + self.image.get_height() * self.position_scale.scale[1] + ) + + return self.bounding_box diff --git a/project/sprite/StaticSprite.py b/project/sprite/StaticSprite.py index cf1827b..bcd3745 100644 --- a/project/sprite/StaticSprite.py +++ b/project/sprite/StaticSprite.py @@ -9,9 +9,6 @@ class StaticSprite(Sprite): super().__init__(spritesheet) def collides_with(self, collider: 'StaticSprite', tolerance: float = 0.0): - if not self.is_collider or not collider.is_collider: - return False - self_bounds = self.get_bounding_box() other_bounds = collider.get_bounding_box() diff --git a/project/ui_elements/TextLabel.py b/project/ui_elements/TextLabel.py index d46b948..bf047b5 100644 --- a/project/ui_elements/TextLabel.py +++ b/project/ui_elements/TextLabel.py @@ -1,5 +1,5 @@ import pygame -from pygame import Surface, Rect +from pygame import Surface from pygame.font import Font from physics.TickData import TickData @@ -47,24 +47,6 @@ class TextLabel(UiElement): def set_text(self, new_text: str): self.text = new_text - def collides_point(self, position): - x, y = self.get_bounding_box().get_position() - # check for the collision on the x-axis - if position[1] < y: - return False - if position[1] > y + self.current_height: - return False - - # the y-axis check is dependent on the alignment - - if position[0] < x: - return False - if position[0] > x + self.current_width: - return False - - # if the point is not outside the text, it is inside the text - return True - def get_bounding_box(self) -> BoundingBox: bounding_box_x = self.x_position if self.alignment == "right": @@ -72,5 +54,5 @@ class TextLabel(UiElement): elif self.alignment == "center": bounding_box_x = self.x_position - self.current_width / 2 - return BoundingBox(bounding_box_x, self.y_position, self.current_width, self.current_height) - + self.bounding_box = BoundingBox(bounding_box_x, self.y_position, self.current_width, self.current_height) + return self.bounding_box diff --git a/project/ui_elements/UiElement.py b/project/ui_elements/UiElement.py index 104874b..664e21e 100644 --- a/project/ui_elements/UiElement.py +++ b/project/ui_elements/UiElement.py @@ -17,6 +17,8 @@ class UiElement: self.visible = True self.click_listeners = [] + self.bounding_box = BoundingBox(0, 0, 0, 0) + self.uuid = uuid.uuid4() def add_click_listener(self, listener): @@ -48,7 +50,8 @@ class UiElement: image = self.render_sprite_image() if image is not None: - target_position = CoordinateTransform.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