Menu
CoddyTech

圆形碰撞箱

本课属于 Coddy Pygame 学习旅程的“精灵与图像”单元。第 25 课,共 39 课。

像素测试很精确,但精确并不总是让人觉得公平。尖刺擦到翅膀尖端也算命中。许多游戏会改为测试一个更小、更简单的形状,也就是碰撞箱,并让擦边未命中不算命中。

对于圆形物体,可以测试圆形。为每个精灵设置一个 radius,当两个圆心之间的距离小于两个半径之和时,pygame.sprite.collide_circle 就会判定为命中:

self.radius = 24  # 在 Player.__init__ 中
self.radius = 22  # 在 Ball.__init__ 中

hits = pygame.sprite.spritecollide(player, balls, False, pygame.sprite.collide_circle)

这里两个圆心之间的距离阈值是 24 + 22 = 46 像素。圆形比图片小,因此最边缘即使擦到也不会算作命中。圆形测试也很省资源:只需计算一次距离,不必检查像素。

如果没有 radius,collide_circle 会使用一个包住整个 Rect 的圆,这个圆比 Rect 本身还大。对于方形精灵,使用较小的 Rect 也能达到同样的效果:self.rect.inflate(-16, -16) 得到的 Rect 宽度和高度各缩小 16 像素,中心位置不变。

challenge icon

挑战

中等

使用圆形而不是遮罩测试碰撞。

  1. 为 Player 设置 self.radius = 24,为 Ball 设置 self.radius = 22。
  2. 使用 pygame.sprite.collide_circle 代替 collide_mask 进行测试。

按“开始”:飞船的翼尖可能会擦过尖刺,但不会显示 Hit!。然后点击“检查”。

亲手试试

import pygame

pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Spike Field")
clock = pygame.time.Clock()

ship_image = pygame.image.load("assets/images/ship.png").convert_alpha()
ball_image = pygame.image.load("assets/images/spike_ball.png").convert_alpha()
font = pygame.font.Font(None, 40)


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = ship_image
        self.rect = self.image.get_rect(topleft=(420, 320))
        self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.rect.x -= 4
        if keys[pygame.K_RIGHT]:
            self.rect.x += 4
        if keys[pygame.K_UP]:
            self.rect.y -= 4
        if keys[pygame.K_DOWN]:
            self.rect.y += 4


class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = ball_image
        self.rect = self.image.get_rect(center=(x, y))
        self.mask = pygame.mask.from_surface(self.image)


player = Player()
balls = pygame.sprite.Group(Ball(160, 240), Ball(320, 240), Ball(480, 240))

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

    player.update()
    hits = pygame.sprite.spritecollide(player, balls, False, pygame.sprite.collide_mask)

    screen.fill((20, 22, 40))
    balls.draw(screen)
    screen.blit(player.image, player.rect)
    if hits:
        text = font.render("Hit!", True, (240, 240, 240))
        screen.blit(text, (10, 10))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

精灵与图像中的所有课

自己练习: Pygame 在线编辑器