我发现了一个功能很好的函数,它可以检测圆形和矩形之间的碰撞,并找到接触点。然后,我使用该点来确定圆形击中矩形的哪一侧,以便反映圆形。但是,当圆的中心位于矩形内部时,该函数将最接近的点作为圆的中心,并且将其处理为碰到顶点而不是边。这是我的代码:
def collide_rect(box, ball_):
#convenience
left = box.rect.left
right = left + box.rect.width
top = box.rect.top
bottom = top + box.rect.height
#find the closest point
closest = (max(left, min(ball_.center[0], right)), max(top, min(ball_.center[1], bottom)))
dx = ball_.center[0] - closest[0]
dy = ball_.center[1] - closest[1]
#handle the collsion
if math.hypot(dx, dy) <= ball.radius:
#Hit on the top or bottom
if left <= closest[0] <= right and (closest[1] == top or closest[1] == bottom):
ball_.vector = (ball_.vector[0], -1*ball_.vector[1])
#Hit on the side
elif top <= closest[1] <= bottom and (closest[0] == left or closest[0] == right):
ball_.vector = (-1*ball_.vector[0], ball_.vector[1])
#Hit a vertex
else:
ball_.vector = (-1*ball_.vector[0], -1*ball_.vector[1])
return True
else:
return False
注意ball_.vector
是圆的方向向量,ball.radius
是类变量。任何以更好的方式找到碰撞面的帮助将不胜感激!
您可以通过在矩形上找到圆形的侧,该矩形位于直线上,该直线由圆的中心和矩形的中心给出。
矩形和圆上的点可以通过中心点之间的偏移与矩形大小的最小关系来计算。在以下算法中,矩形由中心点(r_cpt
)和大小(r_size
)定义,圆由中心点(c_cpt
)和半径(c_rad
)定义:] >
def intersectRectangleCircle(r_cpt, r_size, c_cpt, c_rad): v2_c_cpt = pygame.math.Vector2(c_cpt) v2_r_cpt = pygame.math.Vector2(r_cpt) offset = v2_c_cpt - v2_r_cpt if offset.x == 0 and offset.y == 0: return [v2_c_cpt, v2_r_cpt] if offset.x == 0: ratio = r_size[1] / abs(offset.y) elif offset.y == 0: ratio = r_size[0] / abs(offset.x) else: ratio = min(r_size[0] / abs(offset.x), r_size[1] / abs(offset.y)) ratio *= 0.5 p1 = v2_r_cpt + (offset * ratio) offset.scale_to_length(c_rad) p2 = v2_c_cpt - offset return [p1, p2]
圆的方向是从矩形的中心点到矩形轮廓上的点的向量给定的:
isect_pts = intersectRectangleCircle(rect_center, rect_size, circle_center, circle_diameter/2) dx, dy = isect_pts[0].x - rect_center[0], isect_pts[1].y - rect_center[1]
请参见示例,(
dx
,dy
)用洋红色线表示:
import pygame
import math
pygame.init()
screen = pygame.display.set_mode((500, 500))
def intersectRectangleCircle(r_cpt, r_size, c_cpt, c_rad):
v2_c_cpt = pygame.math.Vector2(c_cpt)
v2_r_cpt = pygame.math.Vector2(r_cpt)
offset = v2_c_cpt - v2_r_cpt
if offset.x == 0 and offset.y == 0:
return [v2_c_cpt, v2_r_cpt]
if offset.x == 0:
ratio = r_size[1] / abs(offset.y)
elif offset.y == 0:
ratio = r_size[0] / abs(offset.x)
else:
ratio = min(r_size[0] / abs(offset.x), r_size[1] / abs(offset.y))
ratio *= 0.5
p1 = v2_r_cpt + (offset * ratio)
offset.scale_to_length(c_rad)
p2 = v2_c_cpt - offset
return [p1, p2]
def inBetween(p1, p2, px):
v = pygame.math.Vector2(p2) - pygame.math.Vector2(p1)
d = v.length()
if d == 0:
return False
v.normalize_ip()
vx = pygame.math.Vector2(px) - pygame.math.Vector2(p1)
dx = v.dot(vx)
return dx >= 0 and dx <= d
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
rect_center = screen.get_rect().center
rect_size = screen.get_width() // 5, screen.get_height() // 10
rect = pygame.Rect(rect_center[0] - rect_size[0] // 2, rect_center[1] - rect_size[1] // 2, *rect_size)
circle_center = pygame.mouse.get_pos()
circle_diameter = min(*screen.get_size()) // 5
isect_pts = intersectRectangleCircle(rect_center, rect_size, circle_center, circle_diameter/2)
dx, dy = isect_pts[0].x - rect_center[0], isect_pts[1].y - rect_center[1]
screen.fill((255,255,255))
pygame.draw.rect(screen, (0, 0, 0), rect, 3)
pygame.draw.circle(screen, (0, 0, 0), circle_center, circle_diameter // 2, 3)
pygame.draw.line(screen, (0, 0, 255), rect_center, circle_center, 1)
pygame.draw.line(screen, (255, 0, 255), rect_center, (round(isect_pts[0].x), round(isect_pts[0].y)), 3)
for i in range(2):
px, py = round(isect_pts[i].x), round(isect_pts[i].y)
col = (255, 0, 0) if inBetween(rect_center, circle_center, (px, py)) else (0, 255, 0)
pygame.draw.line(screen, col, (px-5, py), (px+5, py), 3)
pygame.draw.line(screen, col, (px, py-5), (px, py+5), 3)
pygame.display.flip()
pygame.quit()
quit()