2023-03-26 10:40:17 +02:00
|
|
|
from typing import Optional
|
2023-03-25 15:41:32 +01:00
|
|
|
|
|
|
|
from sprite.DynamicSprite import DynamicSprite
|
|
|
|
from sprite.Sprite import Sprite
|
|
|
|
from sprite.StaticSprite import StaticSprite
|
2023-03-26 09:51:11 +02:00
|
|
|
from ui_elements.UiElement import UiElement
|
2023-03-25 15:41:32 +01:00
|
|
|
|
|
|
|
MOTION_STEPS = 10
|
2023-03-25 17:18:43 +01:00
|
|
|
TOLERANCE = 1
|
|
|
|
|
2023-03-25 15:41:32 +01:00
|
|
|
|
|
|
|
class PhysicsElementsHandler:
|
|
|
|
def __init__(self):
|
2023-03-26 10:40:17 +02:00
|
|
|
self.collision_callbacks = []
|
|
|
|
|
|
|
|
def add_collision_callback(self, callback):
|
|
|
|
self.collision_callbacks.append(callback)
|
|
|
|
|
|
|
|
def remove_collision_callback(self, callback):
|
|
|
|
self.collision_callbacks.remove(callback)
|
2023-03-25 15:41:32 +01:00
|
|
|
|
2023-03-26 10:05:23 +02:00
|
|
|
def tick(self, dt: float, layers: dict[str, list[UiElement]]):
|
|
|
|
sprites = []
|
2023-03-26 09:51:11 +02:00
|
|
|
for layer in layers:
|
2023-03-26 10:05:23 +02:00
|
|
|
for sprite in layers[layer]:
|
|
|
|
sprite.tick(dt)
|
|
|
|
if isinstance(sprite, Sprite):
|
|
|
|
sprites.append(sprite)
|
2023-03-25 15:41:32 +01:00
|
|
|
|
|
|
|
# handle motion and collisions. To do this:
|
|
|
|
# 1. Find all sprites that have collision enabled and store them in a list
|
|
|
|
# 2. Create a list of all sprites that are dynamic sprites
|
|
|
|
# 3. Sort the sprites by their y position
|
|
|
|
# 4. For each sprite:
|
|
|
|
# 4.1. Divide the motion into MOTION_STEPS steps
|
|
|
|
# 4.2. For each step:
|
|
|
|
# 4.2.1. Check if the sprite collides with any other sprite
|
|
|
|
# 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
|
|
|
|
|
2023-03-26 09:51:11 +02:00
|
|
|
colliders = [sprite for sprite in sprites if isinstance(sprite, StaticSprite) and sprite.is_collider]
|
2023-03-25 15:41:32 +01:00
|
|
|
|
2023-03-26 09:51:11 +02:00
|
|
|
dynamic_sprites = [sprite for sprite in sprites if isinstance(sprite, DynamicSprite)]
|
2023-03-25 15:41:32 +01:00
|
|
|
sorted_dynamic_sprites = sorted(dynamic_sprites, key=lambda spr: spr.position_scale.position[1])
|
|
|
|
|
|
|
|
for sprite in sorted_dynamic_sprites:
|
2023-03-26 10:40:17 +02:00
|
|
|
collides_with = self.attempt_move(dt, sprite, colliders, MOTION_STEPS)
|
|
|
|
if collides_with is not None:
|
|
|
|
for callback in self.collision_callbacks:
|
|
|
|
callback(sprite, collides_with)
|
2023-03-25 17:18:43 +01:00
|
|
|
|
2023-03-26 10:40:17 +02:00
|
|
|
def attempt_move(self, dt: float, sprite: DynamicSprite, colliders: list[StaticSprite],
|
|
|
|
motion_steps: int) -> Optional[StaticSprite]:
|
2023-03-25 17:18:43 +01:00
|
|
|
total_motion = sprite.motion
|
2023-03-25 18:19:03 +01:00
|
|
|
motion_step = ((total_motion[0] * dt) / motion_steps, (total_motion[1] * dt) / motion_steps)
|
2023-03-25 17:18:43 +01:00
|
|
|
|
|
|
|
for i in range(motion_steps):
|
2023-03-25 18:14:47 +01:00
|
|
|
sprite.reset_touches()
|
|
|
|
|
2023-03-25 17:18:43 +01:00
|
|
|
sprite.position_scale.position = (
|
|
|
|
sprite.position_scale.position[0] + motion_step[0],
|
2023-03-25 18:14:47 +01:00
|
|
|
sprite.position_scale.position[1]
|
|
|
|
)
|
|
|
|
|
2023-03-26 10:40:17 +02:00
|
|
|
collides_with = self.check_collides(sprite, colliders)
|
|
|
|
if collides_with is not None:
|
2023-03-25 18:14:47 +01:00
|
|
|
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_right(True)
|
|
|
|
|
|
|
|
if sprite.motion[0] < 0:
|
|
|
|
sprite.set_touches_left(True)
|
|
|
|
|
|
|
|
sprite.motion = (0, sprite.motion[1])
|
2023-03-26 10:40:17 +02:00
|
|
|
return collides_with
|
2023-03-25 18:14:47 +01:00
|
|
|
|
|
|
|
sprite.position_scale.position = (
|
|
|
|
sprite.position_scale.position[0],
|
2023-03-25 17:18:43 +01:00
|
|
|
sprite.position_scale.position[1] + motion_step[1]
|
|
|
|
)
|
|
|
|
|
2023-03-26 10:40:17 +02:00
|
|
|
collides_with = self.check_collides(sprite, colliders)
|
|
|
|
if collides_with is not None:
|
2023-03-25 18:14:47 +01:00
|
|
|
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_bottom(True)
|
|
|
|
|
|
|
|
if sprite.motion[1] < 0:
|
|
|
|
sprite.set_touches_top(True)
|
|
|
|
|
|
|
|
sprite.motion = (sprite.motion[0], 0)
|
2023-03-26 10:40:17 +02:00
|
|
|
return collides_with
|
2023-03-25 17:18:43 +01:00
|
|
|
|
2023-03-26 10:40:17 +02:00
|
|
|
return None
|
2023-03-25 15:41:32 +01:00
|
|
|
|
2023-03-26 10:40:17 +02:00
|
|
|
def check_collides(self, sprite: StaticSprite, colliders: list[StaticSprite]) -> Optional[StaticSprite]:
|
2023-03-25 18:14:47 +01:00
|
|
|
for collider in colliders:
|
|
|
|
if sprite is not collider and sprite.collides_with(collider, TOLERANCE):
|
2023-03-26 10:40:17 +02:00
|
|
|
return collider
|
2023-03-25 18:14:47 +01:00
|
|
|
|
2023-03-26 10:40:17 +02:00
|
|
|
return None
|