如何在Pygame中检测碰撞?

39

使用下面的类我已经创建了一份子弹列表和一个精灵列表。如何检测子弹是否与精灵相撞,然后删除该精灵及其相应的子弹?

#Define the sprite class
class Sprite:

    def __init__(self,x,y, name):
        self.x=x

        self.y=y

        self.image = pygame.image.load(name)

        self.rect = self.image.get_rect()

    def render(self):
        window.blit(self.image, (self.x,self.y))


# Define the bullet class to create bullets          
class Bullet:

    def __init__(self,x,y):
        self.x = x + 23
        self.y = y
        self.bullet = pygame.image.load("user_bullet.BMP")
        self.rect = self.bullet.get_rect()

    def render(self):
        window.blit(self.bullet, (self.x, self.y))

我想指出pygame中有一个Sprite类 - 我不确定在您的代码中重新定义它是否是一个好主意。此外,它们真的是目标吗(用更好的词来说),因为精灵只是屏幕上具有图形表示的对象(因此您的子弹也是一个精灵)。 - Tony Suffolk 66
5个回答

84
在PyGame中,使用pygame.Rect对象进行碰撞检测。Rect对象提供了各种方法来检测对象之间的碰撞。即使是矩形和圆形对象之间的碰撞,例如球拍和球之间的碰撞,也可以通过两个矩形对象之间的碰撞来检测,即球的边界矩形与球拍相交。
一些示例:
  • pygame.Rect.collidepoint:

    Test if a point is inside a rectangle

    repl.it/@Rabbid76/PyGame-collidepoint

    import pygame
    
    pygame.init()
    window = pygame.display.set_mode((250, 250))
    rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(100, 100)
    
    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
    
        point = pygame.mouse.get_pos()
        collide = rect.collidepoint(point)
        color = (255, 0, 0) if collide else (255, 255, 255)
    
        window.fill(0)
        pygame.draw.rect(window, color, rect)
        pygame.display.flip()
    
    pygame.quit()
    exit()
    
  • pygame.Rect.colliderect

    Test if two rectangles overlap

    See also How to detect collisions between two rectangular objects or images in pygame

    repl.it/@Rabbid76/PyGame-colliderect

    colliderect

    import pygame
    
    pygame.init()
    window = pygame.display.set_mode((250, 250))
    rect1 = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
    rect2 = pygame.Rect(0, 0, 75, 75)
    
    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
    
        rect2.center = pygame.mouse.get_pos()
        collide = rect1.colliderect(rect2)
        color = (255, 0, 0) if collide else (255, 255, 255)
    
        window.fill(0)
        pygame.draw.rect(window, color, rect1)
        pygame.draw.rect(window, (0, 255, 0), rect2, 6, 1)
        pygame.display.flip()
    
    pygame.quit()
    exit()
    
此外,pygame.Rect.collidelistpygame.Rect.collidelistall可用于矩形与矩形列表之间的碰撞检测。pygame.Rect.collidedictpygame.Rect.collidedictall可用于矩形与矩形字典之间的碰撞检测。 pygame.sprite.Spritepygame.sprite.Group对象的碰撞可以通过pygame.sprite.spritecollide()pygame.sprite.groupcollide()pygame.sprite.spritecollideany()进行检测。使用这些方法时,可以通过collided参数指定碰撞检测算法:

collided参数是一个回调函数,用于计算两个精灵是否发生碰撞。

可能的collided可调用项包括collide_rectcollide_rect_ratiocollide_circlecollide_circle_ratiocollide_mask
一些示例:
  • pygame.sprite.spritecollide()

    repl.it/@Rabbid76/PyGame-spritecollide

    import pygame
    
    pygame.init()
    window = pygame.display.set_mode((250, 250))
    
    sprite1 = pygame.sprite.Sprite()
    sprite1.image = pygame.Surface((75, 75))
    sprite1.image.fill((255, 0, 0))
    sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
    sprite2 = pygame.sprite.Sprite()
    sprite2.image = pygame.Surface((75, 75))
    sprite2.image.fill((0, 255, 0))
    sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
    
    all_group = pygame.sprite.Group([sprite2, sprite1])
    test_group = pygame.sprite.Group(sprite2)
    
    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
    
        sprite1.rect.center = pygame.mouse.get_pos()
        collide = pygame.sprite.spritecollide(sprite1, test_group, False)
    
        window.fill(0)
        all_group.draw(window)
        for s in collide:
            pygame.draw.rect(window, (255, 255, 255), s.rect, 5, 1)
        pygame.display.flip()
    
    pygame.quit()
    exit()
    

关于掩码碰撞,请参见如何制作碰撞掩码?或者Pygame掩码碰撞

另请参见碰撞和交集

  • pygame.sprite.spritecollide() / collide_circle

    repl.it/@Rabbid76/PyGame-spritecollidecollidecircle

    import pygame
    
    pygame.init()
    window = pygame.display.set_mode((250, 250))
    
    sprite1 = pygame.sprite.Sprite()
    sprite1.image = pygame.Surface((80, 80), pygame.SRCALPHA)
    pygame.draw.circle(sprite1.image, (255, 0, 0), (40, 40), 40)
    sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
    sprite1.radius = 40
    sprite2 = pygame.sprite.Sprite()
    sprite2.image = pygame.Surface((80, 89), pygame.SRCALPHA)
    pygame.draw.circle(sprite2.image, (0, 255, 0), (40, 40), 40)
    sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
    sprite2.radius = 40 
    
    all_group = pygame.sprite.Group([sprite2, sprite1])
    test_group = pygame.sprite.Group(sprite2)
    
    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
    
        sprite1.rect.center = pygame.mouse.get_pos()
        collide = pygame.sprite.spritecollide(sprite1, test_group, False, pygame.sprite.collide_circle)
    
        window.fill(0)
        all_group.draw(window)
        for s in collide:
            pygame.draw.circle(window, (255, 255, 255), s.rect.center, s.rect.width // 2, 5)
        pygame.display.flip()
    
    pygame.quit()
    exit()
    

这对你的代码意味着什么? pygame.Surface.get_rect.get_rect()返回一个矩形,其大小与Surface对象相同,由于Surface对象没有位置,因此始终从(0,0)开始。矩形的位置可以通过关键字参数指定。例如,可以使用关键字参数center指定矩形的中心点。在返回之前,这些关键字参数应用于pygame.Rect的属性(请参见pygame.Rect以获取关键字参数列表)。
请参阅*为什么我的碰撞测试总是返回“true”,而图像的矩形位置始终错误(0,0)? 你根本不需要SpriteBulletxy属性。相反,使用rect属性的位置即可。
#Define the sprite class
class Sprite:
    def __init__(self, x, y, name):
        self.image = pygame.image.load(name)
        self.rect = self.image.get_rect(topleft = (x, y))

    def render(self):
        window.blit(self.image, self.rect)

# Define the bullet class to create bullets          
class Bullet:
    def __init__(self, x, y):
        self.bullet = pygame.image.load("user_bullet.BMP")
        self.rect = self.bullet.get_rect(topleft = (x + 23, y))

    def render(self):
        window.blit(self.bullet, self.rect)

使用pygame.Rect.colliderect()检测SpriteBullet实例之间的碰撞。请参见如何在pygame中检测两个矩形对象或图像之间的碰撞
my_sprite = Sprite(sx, sy, name)
my_bullet = Bullet(by, by)

while True:
    # [...]

    if my_sprite.rect.colliderect(my_bullet.rect):
        printe("hit")

14

根据我对pygame的理解,你只需要使用colliderect方法来检查两个矩形是否重叠。其中一种方法是在你的Bullet类中编写一个检查碰撞的方法:

def is_collided_with(self, sprite):
    return self.rect.colliderect(sprite.rect)

那么您可以这样调用:

sprite = Sprite(10, 10, 'my_sprite')
bullet = Bullet(20, 10)
if bullet.is_collided_with(sprite):
    print('collision!')
    bullet.kill()
    sprite.kill()

7
注意,如果子弹相对于目标的速度大于每个时间单位内目标的宽度,子弹可能会在未命中目标的情况下“瞬移到”目标的另一侧。如果可能出现这种情况,则你可能需要检查一个矩形,它代表了从上一帧到当前帧子弹轨迹的路径,而不仅仅是子弹本身。 - 9000

6

使用内置方法,您可以非常简单地完成所需操作。

以下是一个示例。

import pygame
import sys

class Sprite(pygame.sprite.Sprite):
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface([20, 20])
        self.image.fill((255, 0, 0))
        self.rect = self.image.get_rect()

        self.rect.center = pos

def main():
    pygame.init()
    clock = pygame.time.Clock()
    fps = 50
    bg = [255, 255, 255]
    size =[200, 200]


    screen = pygame.display.set_mode(size)

    player = Sprite([40, 50])
    player.move = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN]
    player.vx = 5
    player.vy = 5


    wall = Sprite([100, 60])

    wall_group = pygame.sprite.Group()
    wall_group.add(wall)

    player_group = pygame.sprite.Group()
    player_group.add(player)

    # I added loop for a better exit from the game
    loop = 1
    while loop:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                loop = 0

        key = pygame.key.get_pressed()

        for i in range(2):
            if key[player.move[i]]:
                player.rect.x += player.vx * [-1, 1][i]

        for i in range(2):
            if key[player.move[2:4][i]]:
                player.rect.y += player.vy * [-1, 1][i]

        screen.fill(bg)

        # first parameter takes a single sprite
        # second parameter takes sprite groups
        # third parameter is a do kill command if true
        # all group objects colliding with the first parameter object will be
        # destroyed. The first parameter could be bullets and the second one
        # targets although the bullet is not destroyed but can be done with
        # simple trick bellow
        hit = pygame.sprite.spritecollide(player, wall_group, True)

        if hit:
            # if collision is detected call a function in your case destroy
            # bullet
            player.image.fill((255, 255, 255))

        player_group.draw(screen)
        wall_group.draw(screen)

        pygame.display.update()
        clock.tick(fps)

    pygame.quit()
    # sys.exit


if __name__ == '__main__':
    main()

1
将子弹分组,并将其添加到组中。 我会这样做: 在玩家类中:
def collideWithBullet(self):
    if pygame.sprite.spritecollideany(self, 'groupName'):
        print("CollideWithBullet!!")
        return True

在主循环中的某个地方:


def run(self):
    if self.player.collideWithBullet():
         print("Game Over")

希望这对您有用!!!

0

在Sprite类内部,尝试添加一个名为self.mask的属性,代码如下:
self.mask = pygame.mask.from_surface(self.image)
并在Sprite类内部添加一个名为collide_mask的函数,函数代码如下:

    def collide_mask(self, mask):
        collided = False
        mask_outline = mask.outline()
        self.mask_outline = self.mask.outline()
        for point in range(len(mask_outline)):
            mask_outline[point] = list(mask_outline[point])
            mask_outline[point][0] += bullet.x
            mask_outline[point][1] += bullet.y
        for point in range(len(self.mask_outline)):
            self.mask_outline[point] = list(mask_outline[point])
            self.mask_outline[point][0] += self.x
            self.mask_outline[point][1] += self.y
        for point in mask_outline:
            for self_mask_point in self.mask_outline:
                if point = self_mask_point:
                    collided = True
        return collided

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