@dataclass
class Collision:
time: float = 1
normal: Vector2 = Vector2(0, 0)
@staticmethod
def not_occur() -> 'Collision':
return Collision(1)
def __bool__(self):
return self.time < 1
@property
def rem_time(self) -> float:
return 1 - self.time
# ....
def collide_dt(self, other: 'Collider', dt: float) -> Collision:
"""Detect continuous collision over move and return collision object."""
if not self.broadphase(other, dt):
return Collision.not_occur()
minkowski = other.minkowski_diff(self)
relative_velocity = (self.velocity - other.velocity) * dt
intersections = minkowski.clipline((0, 0), relative_velocity)
if not intersections: return Collision.not_occur()
total_distance = Vector2(relative_velocity).length()
if total_distance == 0: return Collision.not_occur()
distances = [Vector2(point).length() for point in intersections]
idx = 0 if distances[0] <= distances[1] else 1
entry_point = intersections[idx]
entry_distance = distances[idx]
# .bottom and .right lie one pixel outside of its actual border.
normal = Vector2(0, 0)
if entry_point[0] == minkowski.left:
normal.x = -1
elif entry_point[0] == minkowski.right - 1:
normal.x = 1
elif entry_point[1] == minkowski.top:
normal.y = -1
elif entry_point[1] == minkowski.bottom - 1:
normal.y = 1
else:
raise ValueError("collision normal can't be determined")
return Collision(entry_distance / total_distance, normal)
# ...
# test collider thingy
self.collider.velocity = (0, 0)
if pressed[pg.K_a]:
self.collider.velocity.x = -200
if pressed[pg.K_d]:
self.collider.velocity.x = 200
if pressed[pg.K_w]:
self.collider.velocity.y = -200
if pressed[pg.K_s]:
self.collider.velocity.y = 200
collision = self.collider.collide_dt(self.obstacle, dt)
self.collider.move_ip(self.collider.velocity * dt)
response = self.collider.velocity.dot(collision.normal) * -collision.normal
self.collider.move_ip(response * collision.rem_time * dt)
I suppose this issue is somehow related to this:
The rect.bottom and rect.right attributes of a pygame.Rect object for storing rectangular coordinates always lie one pixel outside of its actual border.
that I found in documentation of pygame.Rect module but I can't figure out how.
Collision is detected in right way when the white square collides with top of left side of red square but wrongly when its right or bottom and even when you will try to move in opposite direction it won't allow you for it.
Here is the entire program in one file so you can run it and test by yourself (the important code is in Game.update and Collider class, other things are irrelevant): https://hastebin.com/ubiyitahes.rb
Do you have any idea for possible fix?