Partial collision

main
Yan Wittmann 2023-03-27 14:38:52 +02:00
parent 44d3ab01bf
commit f400b374fd
13 changed files with 156 additions and 166 deletions

View File

@ -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,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
16,28,id=HEBEL,emitter_state=false,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
10,28,id=HEBEL,emitter_state=false,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1
25 # #
26 # #
27 # #
28 # L #
29 # P P x x x x x x x x x D #
30 # # # # # # # # # # # # # # # # # # # # # # # r l # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
31 # # # # # # # # # # # # # # # # # # # # # # # r l # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
32 # # # # # # # # # # # # # # # # # # # # # # # r l # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
40 # # # # # # # # # # # # # # # # # # # # # # # r l # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
41 # # # # # # # # # # # # # # # # # # # # # # # r l # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
42
43 16 28 id=HEBEL emitter_state=false debug=1
44 10 28 id=HEBEL emitter_state=false debug=2

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)) == "<class 'level.elements.LeverInputLevelElement.LeverInputLevelElement'>", 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

View File

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

View File

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

View File

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

View File

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