解决精灵碰撞检测不精确的问题,以及自定义碰撞检测区域和函数
关注 1421
如何在 Pygame 中提高精灵碰撞检测的精度视频演示 YouTube如何在 Pygame 中提高精灵碰撞检测的精度视频演示 Bilibili
引言
在 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
对象设置了透明颜色键,那么将由透明颜色键决定像素是否参与碰撞检测。
像素级碰撞检测的性能问题
像素级碰撞检测能有效消除“幽灵碰撞”,但其性能开销较大,并且碰撞效果可能仍不够“真实”(如子弹消失过快)。
在下面的示例中,精灵对象laser
和candy
进行了像素级碰撞检测。
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
模块的spritecollide
和groupcollide
函数,支持通过collided
参数指定碰撞检测函数,因此,与碰撞检测相关的代码可以变得更为灵活。
# 缩放碰撞检测区域
pygame.sprite.spritecollide(door, lasers, True, collided=crr)
# 自定义碰撞检测区域
pygame.sprite.groupcollide(candies, lasers, True, True, collided=collide_rects)
讲解视频
如何在 Pygame 中提高精灵碰撞检测的精度·YouTube如何在 Pygame 中提高精灵碰撞检测的精度·Bilibili