如何在pygame中检查直线和矩形之间的碰撞?

6
我目前正在使用pygame创建一个Python游戏,我的人工智能当前可以“看到”穿过墙壁并射击我的角色,但是人工智能不应该射击。我的问题是:如何防止这种情况?我已经考虑了一条从我的人工智能到我的角色的线碰撞,在这条线与墙壁相碰时,人工智能不会射击。任何帮助将不胜感激,非常感谢!

1
你可以使用循环,例如 for x in range(ai_x, player_x) 来获取AI到玩家连线上的每个点,并检查该循环中每个点与墙壁的碰撞。 - furas
视线可以是对角线吗?任何角度都可以吗?还是只能水平/垂直? - Valentino
3个回答

3
这是一个很好的问题!
你的矩形可以看作是由4条线组成:

(x, y)        → (x+width, y)        # top
(x+width, y)  → (x+width, y+height) # right
(x, y+height) → (x+width, y+height) # bottom
(x, y)        → (x, y+height)       # left 

使用两条线相交的公式,可以确定这些线是否相交(但要注意平行线!)。
然而,该公式(在链接的维基百科文章中指定)确定线是否在2D平面上的任何地方相交,因此需要进一步改进。显然,代码可以快速丢弃发生在窗口尺寸之外的任何交点。
一旦确定了“无限平面”碰撞点(这是一个相当快速的确定),则可以确定更精细的交点。使用Bresenham算法,枚举交叉线中的所有点,并将它们与基于正方形每侧的1像素矩形进行比较。这将告诉您哪个矩形侧面相交。
如果只需知道是否击中矩形,请检查每条线段中的每个点与pygame.Rect.collidepoint()的整个矩形是否相交。

enter image description here

当然,一旦您生成了所有这些点,就很容易不必理会2D线碰撞,但对于长线而言,代码必须进行大量检查。因此,首先测试2D交点确实可以加快速度。


1
检测矩形和线之间的碰撞最简单的方法是使用 pygame.Rect.clipline

返回被裁剪为完全位于矩形内部的线的坐标。如果线不与矩形重叠,则返回一个空元组。

e.g.:

rect = pygme.Rect(x, y, width, height)
if rect.clipline((x1, y1), (x2, y2)):
    print("hit")

最简示例

import pygame

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

rect = pygame.Rect(180, 180, 40, 40)
speed = 5
lines = [((20, 300), (150, 20)), ((250, 20), (380, 250)), ((50, 350), (350, 300))]

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 

    keys = pygame.key.get_pressed()
    rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
    rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed
    rect.centerx %= window.get_width()
    rect.centery %= window.get_height()

    color = "red" if any(rect.clipline(*line) for line in lines) else "green"

    window.fill(0)
    pygame.draw.rect(window, color, rect)
    for line in lines:
        pygame.draw.line(window, "white", *line)
    pygame.display.flip()

pygame.quit()
exit()

0

基本上,pygame 没有任何方法或功能可以检测与线段的碰撞,这就是为什么我不得不想出下面要展示的解决方案。

使用以下 链接,在“公式/给定每条线段上的两个点”部分,您可以找到一个公式,以知道两条线是否相交,如果相交,则确切地在哪里。

基本思路是检查光源中的每个射线是否与矩形的四条边之一相交,如果是,则光线应该在矩形的同一侧结束。

import pygame, math

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Rays')
pygame.mouse.set_visible(False)

DENSITY = 500
RADIUS = 1000

run = True
while run:
    screen.fill('black')

    rect = pygame.Rect(50, 200, 100, 50)
    pygame.draw.rect(screen, 'red', rect)

    for i in range(DENSITY):
        mouse_pos = pygame.mouse.get_pos()
        pos_fin = (RADIUS * math.cos(2*math.pi / DENSITY * i) + mouse_pos[0], RADIUS * math.sin(2*math.pi / DENSITY * i) + mouse_pos[1])
        if rect.collidepoint(mouse_pos) == False:
            for extrem_1, extrem_2 in [(rect.bottomright, rect.topright), (rect.topright, rect.topleft), (rect.topleft, rect.bottomleft), (rect.bottomleft, rect.bottomright)]:
                deno = (mouse_pos[0] - pos_fin[0]) * (extrem_1[1] - extrem_2[1]) - (mouse_pos[1] - pos_fin[1]) * (extrem_1[0] - extrem_2[0])
                if deno != 0:
                    param_1 = ((extrem_2[0] - mouse_pos[0]) * (mouse_pos[1] - pos_fin[1]) - (extrem_2[1] - mouse_pos[1]) * (mouse_pos[0] - pos_fin[0]))/deno
                    param_2 = ((extrem_2[0] - mouse_pos[0]) * (extrem_2[1] - extrem_1[1]) - (extrem_2[1] - mouse_pos[1]) * (extrem_2[0] - extrem_1[0]))/deno
                    if 0 <= param_1 <= 1 and 0 <= param_2 <= 1:
                        p_x = mouse_pos[0] + param_2 * (pos_fin[0] - mouse_pos[0])
                        p_y = mouse_pos[1] + param_2 * (pos_fin[1] - mouse_pos[1])
                        pos_fin = (p_x, p_y)
            pygame.draw.aaline(screen, 'white', mouse_pos, pos_fin)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    
    pygame.display.update()
pygame.quit()

这可能不是最好的、最优化的代码,但最终你应该能得到一个可用的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接