URLhttps://learnscript.net/zh/pygame/sprite/collision/custom/
    复制链接转到说明  示例

    解决精灵碰撞检测不精确的问题,以及自定义碰撞检测区域和函数

    我被代码海扁署名-非商业-禁演绎
    阅读 5:13·字数 1568·发布 

    引言

    在 Pygame 中,sprite模块提供了方便的碰撞检测函数,但直接使用这些函数,可能会导致不够精确的碰撞效果(比如“幽灵碰撞”)。本文将探讨这个问题的原因,并介绍三种改进碰撞检测精度的方法。

    “幽灵碰撞”问题

    “幽灵碰撞”,即视觉上看起来没有接触的精灵,却被判定为发生了碰撞。造成“幽灵碰撞”的原因在于,精灵的碰撞检测区域超出了精灵所展示的形象。

    改进方法一:像素级碰撞检测

    像素级碰撞检测(又称像素完美碰撞检测,或蒙板碰撞检测)通过蒙板(Mask对象)来决定图像中哪些像素参与碰撞检测。

    sprite模块提供了collide_mask函数,用于对精灵进行像素级碰撞检测,如果参与检测的精灵对象未包含蒙板信息,collide_mask会根据精灵图像自动生成。

    collide_mask(left, right)

    left,right 参数

    参与检测的两个精灵对象。

    返回值

    在未发生碰撞时,返回空值None,否则返回表示第一个被碰撞像素的坐标的整数元组,其 X,Y 坐标相对于left参数所对应的精灵图像的左上角。

    当然,如果需要的话,你也可以通过mask模块的from_surface函数手动创建蒙板。

    from_surface(surface, threshold=127)

    surface 参数

    绘制了图像的Surface对象。

    threshold 参数

    透明度阈值,只有透明度大于该值的像素才会参与检测。如果Surface对象设置了透明颜色键,那么将由透明颜色键决定像素是否参与碰撞检测。

    像素级碰撞检测的性能问题

    像素级碰撞检测能有效消除“幽灵碰撞”,但其性能开销较大,并且碰撞效果可能仍不够“真实”(如子弹消失过快)。

    在下面的示例中,精灵对象lasercandy进行了像素级碰撞检测。

    for laser in lasers:
    	for candy in candies:
    		p = pygame.sprite.collide_mask(laser, candy)
    		if p:
    			print(f"碰撞像素坐标: {p}")
    			laser.kill()
    			candy.kill()

    改进方法二:按比例缩小碰撞检测区域

    sprite模块提供了collide_rect_ratio类,可用于缩放碰撞检测区域,但这不会改变精灵的rect变量所对应的矩形的大小,缩放效果仅在碰撞检测时有效。

    collide_rect_ratio(ratio)

    ratio 参数

    小于1表示缩小,大于1表示放大。

    你可以像调用函数一样,调用collide_rect_ratio类的实例,以检测两个精灵是否发生了碰撞。

    collide_rect_ratio_instance(left, right)

    left,right 参数

    参与检测的两个精灵对象。

    返回值

    返回表示是否发生碰撞的布尔值。

    等比例缩放带来的问题

    collide_rect_ratio类实现的等比例缩放,可能导致某些期望参与碰撞的区域,被排除在碰撞检测之外。

    在下面的示例中,我们将碰撞检测区域缩小至70%

    from pygame.sprite import collide_rect_ratio
    
    crr = collide_rect_ratio(0.7) # 缩小到 70% if crr(laser, candy): # 处理碰撞

    改进方法三:自定义碰撞检测区域

    对于结构复杂的精灵,可以定义多个碰撞检测区域来实现更精确的控制,比如,精确控制哪些区域参与碰撞,或实现对角色不同部位造成不同伤害等。

    对于自定义的检测区域,不应该保存在精灵类的rect变量中,因为该变量还决定了精灵的位置。

    在下面的示例中,我们为精灵类Candy定义了多个小矩形,作为其碰撞检测区域,并定义了实现碰撞检测的函数collide_rects,这里假设了其参数left对应的精灵拥有rects变量。

    class Candy(pygame.sprite.Sprite):
    	def __init__(self, *groups):
    		super().__init__(*groups)
    		# …
    		self.rects = [ # 定义多个小矩形作为碰撞区域
    			pygame.Rect(24, 4, 2, 2),
    			pygame.Rect(20, 8, 4, 4),
    			pygame.Rect(16, 12, 4, 4),
    			pygame.Rect(12, 16, 4, 4),
    			pygame.Rect(8, 20, 4, 4),
    			pygame.Rect(4, 24, 2, 2)
    		]
    
    def collide_rects(left, right): for rect in left.rects: adjusted_rect = rect.move(left.rect.topleft) if adjusted_rect.colliderect(right.rect): return True return False # … if collide_rects(candy, laser): # 处理碰撞

    指定碰撞检测函数

    sprite模块的spritecollidegroupcollide函数,支持通过collided参数指定碰撞检测函数,因此,与碰撞检测相关的代码可以变得更为灵活。

    # 缩放碰撞检测区域
    pygame.sprite.spritecollide(door, lasers, True, collided=crr)
    
    # 自定义碰撞检测区域 pygame.sprite.groupcollide(candies, lasers, True, True, collided=collide_rects)

    讲解视频

    如何在 Pygame 中提高精灵碰撞检测的精度·YouTube如何在 Pygame 中提高精灵碰撞检测的精度·Bilibili