2023-03-28 20:01:09 +02:00
|
|
|
import random
|
2023-03-26 10:40:17 +02:00
|
|
|
from typing import Optional
|
2023-03-25 15:41:32 +01:00
|
|
|
|
2023-03-26 17:01:28 +02:00
|
|
|
from physics.CollisionDirection import CollisionDirection
|
2023-03-26 12:46:41 +02:00
|
|
|
from physics.TickData import TickData
|
2023-03-28 19:35:42 +02:00
|
|
|
from sprite.BoundingBox import BoundingBox
|
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
|
|
|
|
2023-03-28 19:46:54 +02:00
|
|
|
MAX_COLLIDER_DISTANCE = 50
|
|
|
|
MAX_COLLIDER_CHECK_SPRITES = 10
|
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 12:46:41 +02:00
|
|
|
def tick(self, tick_data: TickData, layers: dict[str, list[UiElement]]):
|
2023-03-26 10:05:23 +02:00
|
|
|
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]:
|
2023-03-26 12:46:41 +02:00
|
|
|
sprite.tick(tick_data)
|
2023-03-26 10:05:23 +02:00
|
|
|
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
|
2023-03-28 19:46:54 +02:00
|
|
|
# 4. Find the MAX_COLLIDER_CHECK_SPRITES sprites that are closest to the sprite that are colliders but not the
|
2023-03-28 19:35:42 +02:00
|
|
|
# sprite itself
|
2023-03-28 19:46:54 +02:00
|
|
|
# 4.1 Filter out all that are further than MAX_COLLIDER_DISTANCE
|
|
|
|
# 5. For each sprite:
|
|
|
|
# 5.1. Divide the motion into MOTION_STEPS steps
|
|
|
|
# 5.2. For each step:
|
|
|
|
# 5.2.1. Check if the sprite collides with any other sprite from the list of colliders generated in step 4
|
|
|
|
# 5.2.2. If it does, move the sprite back to the previous position and stop the motion
|
|
|
|
# 5.2.3. If it doesn't, move the sprite to the new position
|
2023-03-25 15:41:32 +01:00
|
|
|
|
2023-03-27 14:38:52 +02:00
|
|
|
colliders = [sprite for sprite in sprites
|
|
|
|
if isinstance(sprite, StaticSprite) and (sprite.is_collider or sprite.register_collisions)]
|
|
|
|
|
2023-03-26 17:01:28 +02:00
|
|
|
for collider in colliders:
|
|
|
|
collider.reset_collides_with()
|
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])
|
|
|
|
|
2023-03-28 20:01:09 +02:00
|
|
|
skip_sprites = []
|
|
|
|
for sprite in sorted_dynamic_sprites:
|
|
|
|
if sprite.last_effective_motion[1] == 0 and sprite.last_effective_motion[0] == 0 \
|
|
|
|
and random.randint(0, 100) > 50:
|
|
|
|
skip_sprites.append(sprite)
|
|
|
|
continue
|
|
|
|
sorted_dynamic_sprites = [sprite for sprite in sorted_dynamic_sprites if sprite not in skip_sprites]
|
|
|
|
|
2023-03-28 19:35:42 +02:00
|
|
|
closest_sprites: dict[UiElement, list[tuple[int, StaticSprite]]] = {}
|
|
|
|
|
|
|
|
buffered_bounding_boxes: dict[UiElement, BoundingBox] = {}
|
|
|
|
for collider in colliders:
|
|
|
|
buffered_bounding_boxes[collider] = collider.get_bounding_box()
|
|
|
|
|
2023-03-25 15:41:32 +01:00
|
|
|
for sprite in sorted_dynamic_sprites:
|
2023-03-28 19:35:42 +02:00
|
|
|
closest_sprites[sprite] = []
|
|
|
|
current_closest_sprites = closest_sprites[sprite]
|
|
|
|
|
|
|
|
if sprite not in buffered_bounding_boxes:
|
|
|
|
buffered_bounding_boxes[sprite] = sprite.get_bounding_box()
|
|
|
|
|
|
|
|
for collider in colliders:
|
|
|
|
if collider is sprite:
|
|
|
|
continue
|
|
|
|
|
|
|
|
distance = int(buffered_bounding_boxes[collider].distance(buffered_bounding_boxes[sprite]))
|
|
|
|
|
|
|
|
if len(current_closest_sprites) < MAX_COLLIDER_CHECK_SPRITES:
|
|
|
|
current_closest_sprites.append((distance, collider))
|
|
|
|
else:
|
|
|
|
max_index = -1
|
|
|
|
max_distance = -1
|
|
|
|
for i in range(len(current_closest_sprites)):
|
|
|
|
if distance < current_closest_sprites[i][0]:
|
|
|
|
if current_closest_sprites[i][0] > max_distance:
|
|
|
|
max_distance = current_closest_sprites[i][0]
|
|
|
|
max_index = i
|
|
|
|
|
|
|
|
if max_index != -1:
|
|
|
|
current_closest_sprites[max_index] = (distance, collider)
|
|
|
|
|
2023-03-28 19:46:54 +02:00
|
|
|
for sprite in closest_sprites:
|
|
|
|
closest_sprites[sprite] = [closest_sprite for closest_sprite in closest_sprites[sprite]
|
|
|
|
if closest_sprite[0] < MAX_COLLIDER_DISTANCE]
|
|
|
|
|
2023-03-28 19:35:42 +02:00
|
|
|
# set visible false for all those that are not in the closest_sprites list
|
2023-03-28 19:39:26 +02:00
|
|
|
# for collider in colliders:
|
|
|
|
# found = False
|
|
|
|
# for sprite in sorted_dynamic_sprites:
|
|
|
|
# for _, c in closest_sprites[sprite]:
|
|
|
|
# if c is collider:
|
|
|
|
# found = True
|
|
|
|
# break
|
|
|
|
# if found:
|
|
|
|
# break
|
|
|
|
# collider.visible = not found
|
|
|
|
|
|
|
|
# for sprite in sorted_dynamic_sprites:
|
|
|
|
# sprite.visible = True
|
2023-03-28 19:35:42 +02:00
|
|
|
|
|
|
|
for sprite in sorted_dynamic_sprites:
|
|
|
|
collides_with = self.attempt_move(tick_data,
|
|
|
|
sprite,
|
|
|
|
[collider for _, collider in closest_sprites[sprite]],
|
|
|
|
MOTION_STEPS)
|
2023-03-26 10:40:17 +02:00
|
|
|
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-27 18:37:30 +02:00
|
|
|
def attempt_move(self, tick_data: TickData, sprite: DynamicSprite, colliders: list[StaticSprite],
|
2023-03-26 10:40:17 +02:00
|
|
|
motion_steps: int) -> Optional[StaticSprite]:
|
2023-03-25 17:18:43 +01:00
|
|
|
total_motion = sprite.motion
|
2023-03-27 18:37:30 +02:00
|
|
|
motion_step = ((total_motion[0] * tick_data.dt) / motion_steps, (total_motion[1] * tick_data.dt) / motion_steps)
|
2023-03-25 17:18:43 +01:00
|
|
|
|
2023-03-26 17:01:28 +02:00
|
|
|
collides_with_last = None
|
|
|
|
collided = [False, False]
|
2023-03-28 20:01:09 +02:00
|
|
|
effective_motion = [0, 0]
|
2023-03-25 18:14:47 +01:00
|
|
|
|
2023-03-26 17:01:28 +02:00
|
|
|
for i in range(motion_steps):
|
|
|
|
if not collided[0]:
|
2023-03-25 18:14:47 +01:00
|
|
|
sprite.position_scale.position = (
|
2023-03-26 17:01:28 +02:00
|
|
|
sprite.position_scale.position[0] + motion_step[0],
|
2023-03-25 18:14:47 +01:00
|
|
|
sprite.position_scale.position[1]
|
|
|
|
)
|
|
|
|
|
2023-03-28 19:35:42 +02:00
|
|
|
collides_with = self.check_collides(sprite, colliders)
|
2023-03-28 20:01:09 +02:00
|
|
|
if len(collides_with) == 0:
|
|
|
|
effective_motion[0] += motion_step[0]
|
|
|
|
|
2023-03-27 14:38:52 +02:00
|
|
|
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:
|
|
|
|
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 collider.is_collider:
|
|
|
|
sprite.position_scale.position = (
|
|
|
|
sprite.position_scale.position[0] - motion_step[0],
|
|
|
|
sprite.position_scale.position[1]
|
|
|
|
)
|
|
|
|
|
2023-03-27 18:52:49 +02:00
|
|
|
sprite.motion = (sprite.motion[0] * sprite.bounce_factor, sprite.motion[1])
|
2023-03-27 14:38:52 +02:00
|
|
|
|
|
|
|
collides_with_last = collider
|
|
|
|
collided[0] = True
|
2023-03-26 17:01:28 +02:00
|
|
|
|
|
|
|
if not collided[1]:
|
2023-03-25 18:14:47 +01:00
|
|
|
sprite.position_scale.position = (
|
|
|
|
sprite.position_scale.position[0],
|
2023-03-26 17:01:28 +02:00
|
|
|
sprite.position_scale.position[1] + motion_step[1]
|
2023-03-25 18:14:47 +01:00
|
|
|
)
|
|
|
|
|
2023-03-28 19:35:42 +02:00
|
|
|
collides_with = self.check_collides(sprite, colliders)
|
2023-03-28 20:01:09 +02:00
|
|
|
if len(collides_with) == 0:
|
|
|
|
effective_motion[1] += motion_step[1]
|
|
|
|
|
2023-03-27 14:38:52 +02:00
|
|
|
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:
|
|
|
|
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 collider.is_collider:
|
|
|
|
sprite.position_scale.position = (
|
|
|
|
sprite.position_scale.position[0],
|
|
|
|
sprite.position_scale.position[1] - motion_step[1]
|
|
|
|
)
|
|
|
|
|
2023-03-27 18:52:49 +02:00
|
|
|
sprite.motion = (sprite.motion[0], sprite.motion[1] * sprite.bounce_factor)
|
2023-03-27 14:38:52 +02:00
|
|
|
|
|
|
|
collides_with_last = collider
|
|
|
|
collided[1] = True
|
2023-03-26 17:01:28 +02:00
|
|
|
|
2023-03-28 20:01:09 +02:00
|
|
|
sprite.last_effective_motion = (sprite.motion[0], sprite.motion[1])
|
|
|
|
|
2023-03-26 17:01:28 +02:00
|
|
|
return collides_with_last
|
2023-03-25 15:41:32 +01:00
|
|
|
|
2023-03-28 19:46:54 +02:00
|
|
|
def check_collides(self, sprite: StaticSprite, colliders: list[StaticSprite]) -> list[StaticSprite]:
|
2023-03-27 14:38:52 +02:00
|
|
|
collides_with = []
|
|
|
|
|
2023-03-25 18:14:47 +01:00
|
|
|
for collider in colliders:
|
2023-03-26 17:01:28 +02:00
|
|
|
if sprite is not collider:
|
|
|
|
if sprite.collides_with(collider, TOLERANCE):
|
2023-03-27 14:38:52 +02:00
|
|
|
collides_with.append(collider)
|
2023-03-27 18:52:49 +02:00
|
|
|
if len(collides_with) > 5:
|
2023-03-27 14:47:12 +02:00
|
|
|
break
|
2023-03-25 18:14:47 +01:00
|
|
|
|
2023-03-27 14:38:52 +02:00
|
|
|
return collides_with
|