在 Pygame 中检测多边形和矩形之间的碰撞

Detecting collisions between polygons and rectangles in Pygame

所以我正在尝试用 pygame 制作一款我们之间类型的游戏。我刚开始,所以我什么都没有,现在正在制作地图。但是,我正在努力解决的一件事是碰撞逻辑。该地图目前具有细长的八角形形状,但我认为无论形状如何,我都会使用 pygame 多边形之类的东西。当我 运行 我现在拥有的代码检查我的播放器(pygame 矩形)和墙壁(pygame 多边形)之间的碰撞时,它说:

TypeError: Argument must be rect style object

我发现这是因为 pygame 多边形返回一个矩形,但在碰撞检查器中没有按这种方式分类。我尝试了一个名为 collision 的库,感谢碰撞检测付出了巨大的努力,但玩家仍然能够穿墙而过。旁注:如果有人想看到它并可能改进我的错误,我保存了我使用这个库的代码。

总之,总结一下:

我需要一种方法来检测多边形和矩形之间的碰撞(真的,最好是 pygame)

感谢您提供的任何帮助,如果您有 question/request 请发表评论。

这是我的代码:

import pygame
pygame.init()
W, H=500, 500
screen = pygame.display.set_mode([500, 500])
running = True

bcg=(200, 200, 200)
red=(255, 0 ,0)
purp=(255, 0, 255)
wall=(100, 100, 100)

class player:
    def bg(self):        
        screen.fill(bcg)
        x,y=self.x,self.y

        self.outer=(
                (x,y),
                (x+800, y),
                (x+1200, y+200),
                (x+1200, y+600),
                (x+800, y+800),
                (x, y+800),
                (x-400, y+600),
                (x-400, y+200),
                
                (x,y),
                
                (x, y+50),
                (x-350, y+225),
                (x-350, y+575),
                (x, y+750),
                (x+800, y+750),
                (x+1150, y+575),
                (x+1150, y+225),
                (x+800, y+50),
                (x, y+50)
            )
        
        pygame.draw.polygon(screen, wall, self.outer)
        
    
    def __init__(self, color, size=20, speed=0.25):
        self.x=0
        self.y=0
        self.col=color
        self.size=size
        self.speed=speed


    def draw(self):
        s=self.size
        self.rect=pygame.Rect(W/2-s/2, H/2-s/2, self.size, self.size)
        pygame.draw.rect(screen, self.col, self.rect)

    def move(self, x, y):
        x*=self.speed
        y*=self.speed
        if not self.rect.colliderect(self.outer):
            self.x+=x
            self.y+=y
        

        

p=player(red)

while running:
    for event in pygame.event.get():
        
        if event.type == pygame.QUIT:
            running = False

    p.bg()

    keys=pygame.key.get_pressed()
    
    if keys[pygame.K_a]: p.move(1, 0)
    if keys[pygame.K_d]: p.move(-1, 0)
    if keys[pygame.K_w]: p.move(0, 1)
    if keys[pygame.K_s]: p.move(0, -1)

    p.draw()

    pygame.display.update()

pygame.quit()

编写一个函数 collideLineLine 来测试线段是否相交。这个函数的算法在问题 :

的答案中有详细解释
def collideLineLine(l1_p1, l1_p2, l2_p1, l2_p2):

    # normalized direction of the lines and start of the lines
    P  = pygame.math.Vector2(*l1_p1)
    line1_vec = pygame.math.Vector2(*l1_p2) - P
    R = line1_vec.normalize()
    Q  = pygame.math.Vector2(*l2_p1)
    line2_vec = pygame.math.Vector2(*l2_p2) - Q
    S = line2_vec.normalize()

    # normal vectors to the lines
    RNV = pygame.math.Vector2(R[1], -R[0])
    SNV = pygame.math.Vector2(S[1], -S[0])
    RdotSVN = R.dot(SNV)
    if RdotSVN == 0:
        return False

    # distance to the intersection point
    QP  = Q - P
    t = QP.dot(SNV) / RdotSVN
    u = QP.dot(RNV) / RdotSVN

    return t > 0 and u > 0 and t*t < line1_vec.magnitude_squared() and u*u < line2_vec.magnitude_squared()

编写函数colideRectLine 来测试矩形和线段是否相交。要测试线段是否与矩形相交,您必须测试它是否与矩形的 4 条边中的任何一条相交:

def colideRectLine(rect, p1, p2):
    return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
            collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
            collideLineLine(p1, p2, rect.bottomright, rect.topright) or
            collideLineLine(p1, p2, rect.topright, rect.topleft))

下一个函数 collideRectPolygon 测试多边形和矩形是否相交。这可以通过在循环中针对矩形测试多边形上的每个线段来实现:

def collideRectPolygon(rect, polygon):
    for i in range(len(polygon)-1):
        if colideRectLine(rect, polygon[i], polygon[i+1]):
            return True
    return False

终于可以使用collideRectPolygon进行碰撞测试了。但是请注意,对于测试,您需要像玩家移动一样使用多边形:

class player:
    def bg(self):        
        screen.fill(bcg)
        self.outer = self.createPolygon(self.x, self.y)
        pygame.draw.polygon(screen, wall, self.outer)
  
    def createPolygon(self, x, y):
        return [
            (x,y), (x+800, y), (x+1200, y+200), (x+1200, y+600), 
            (x+800, y+800), (x, y+800), (x-400, y+600), (x-400, y+200),           
            (x,y), (x, y+50), (x-350, y+225), (x-350, y+575), 
            (x, y+750), (x+800, y+750), (x+1150, y+575), (x+1150, y+225),
            (x+800, y+50),(x, y+50)]
    
    # [...]

    def move(self, x, y):
        
        x *= self.speed
        y *= self.speed
        polygon = self.createPolygon(self.x + x, self.y + y)
        if not collideRectPolygon(self.rect, polygon):
            self.x += x
            self.y += y

另见 Collision and Intersection - Rectangle and polygon


最小示例:

repl.it/@Rabbid76/PyGame-CollisionPolygonRectangle


完整示例:

import pygame
pygame.init()
W, H=500, 500
screen = pygame.display.set_mode([500, 500])
running = True

bcg=(200, 200, 200)
red=(255, 0 ,0)
purp=(255, 0, 255)
wall=(100, 100, 100)

def collideLineLine(l1_p1, l1_p2, l2_p1, l2_p2):

    # normalized direction of the lines and start of the lines
    P  = pygame.math.Vector2(*l1_p1)
    line1_vec = pygame.math.Vector2(*l1_p2) - P
    R = line1_vec.normalize()
    Q  = pygame.math.Vector2(*l2_p1)
    line2_vec = pygame.math.Vector2(*l2_p2) - Q
    S = line2_vec.normalize()

    # normal vectors to the lines
    RNV = pygame.math.Vector2(R[1], -R[0])
    SNV = pygame.math.Vector2(S[1], -S[0])
    RdotSVN = R.dot(SNV)
    if RdotSVN == 0:
        return False

    # distance to the intersection point
    QP  = Q - P
    t = QP.dot(SNV) / RdotSVN
    u = QP.dot(RNV) / RdotSVN

    return t > 0 and u > 0 and t*t < line1_vec.magnitude_squared() and u*u < line2_vec.magnitude_squared()

def colideRectLine(rect, p1, p2):
    return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
            collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
            collideLineLine(p1, p2, rect.bottomright, rect.topright) or
            collideLineLine(p1, p2, rect.topright, rect.topleft))

def collideRectPolygon(rect, polygon):
    for i in range(len(polygon)-1):
        if colideRectLine(rect, polygon[i], polygon[i+1]):
            return True
    return False

class player:
    def bg(self):        
        screen.fill(bcg)
        self.outer = self.createPolygon(self.x, self.y)
        pygame.draw.polygon(screen, wall, self.outer)
  
    def createPolygon(self, x, y):
        return [
            (x,y), (x+800, y), (x+1200, y+200), (x+1200, y+600), 
            (x+800, y+800), (x, y+800), (x-400, y+600), (x-400, y+200),           
            (x,y), (x, y+50), (x-350, y+225), (x-350, y+575), 
            (x, y+750), (x+800, y+750), (x+1150, y+575), (x+1150, y+225),
            (x+800, y+50),(x, y+50)]
    
    def __init__(self, color, size=20, speed=0.25):
        self.x=0
        self.y=0
        self.col=color
        self.size=size
        self.speed=speed

    def draw(self):
        s=self.size
        self.rect=pygame.Rect(W/2-s/2, H/2-s/2, self.size, self.size)
        pygame.draw.rect(screen, self.col, self.rect)

    def move(self, x, y):
        
        x *= self.speed
        y *= self.speed
        polygon = self.createPolygon(self.x + x, self.y + y)
        if not collideRectPolygon(self.rect, polygon):
            self.x += x
            self.y += y
            
p=player(red)
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    p.bg()

    keys=pygame.key.get_pressed()
    
    if keys[pygame.K_a]: p.move(1, 0)
    if keys[pygame.K_d]: p.move(-1, 0)
    if keys[pygame.K_w]: p.move(0, 1)
    if keys[pygame.K_s]: p.move(0, -1)

    p.draw()
    pygame.display.update()

pygame.quit()

或者用 4 条线定义矩形,并检测您是在这些线的上方还是下方,在矩形完全对齐的 2 帧中,您只需使用正常的矩形碰撞。