2.14 碰撞检测
除了定义了Sprite类、Group类以外,pygame.sprite模块还提供了一些功能函数用于碰撞检测。
1. 碰撞检测函数
pygame.sprite模块提供了三个函数用于Sprite之间的碰撞检测,它们是:
该函数检测参数sprite与参数group中的哪些Sprite发生碰撞,并返回group中发生碰撞的Sprite列表。参数dokill如果为True,则表示把发生碰撞的Sprite从group中删除。最后的参数collided为一个回调函数,用来设置检测模式,计算两个Sprite之间如何发生碰撞,关于它的具体用法,将在本书后面讲解。
该函数与spritecollide()的功能类似,只不过它仅返回group中第一个与参数sprite发生碰撞的Sprite,并且它没有dokill参数。与spritecollide()相比,它更加轻量级,所以如果不需要那么多功能,则可以选择使用该函数而非spritecollide()。这里的参数collided与spritecollide()中的参数collided相同,也用来设置检测模式。
前面两个函数都用来检测Sprite与Group之间的碰撞,而该函数则用来检测两个Group之间的碰撞情况,它返回的是一个字典,字典的key为参数groupa中的Sprite,value为参数groupb中与该Sprite发生碰撞的所有Sprite的列表。参数dokilla表示是否把发生碰撞的Sprite从groupa中删除;参数dokillb表示是否把发生碰撞的Sprite从groupb中删除;参数collided与前面函数中的意义相同。
2. 碰撞检测模式
上面的三个碰撞检测函数都用collided作为参数,现在详细介绍一下collided回调函数。
参数collided代表了碰撞检测的模式,表示以什么样的方式检测两个Sprite之间的碰撞。pygame.sprite模块为collided预置了五个可选的回调函数,它们分别是:
通过检测两个Sprite的rect属性是否有重合判断碰撞情况,这也是默认的检测模式,当碰撞检测函数的collided参数为None时,就是使用了这种检测模式。此时,两个Sprite必须都要包含rect属性。
与collide_rect类似,只不过可以按比例缩放待检测Sprite的矩形区域。参数ratio为一个浮点数,1.0为原始尺寸,2.0为放大一倍,0.5为缩小1/2。
通过检测两个Sprite所在的圆形区域是否有重合判断碰撞情况。如果Sprite有radius属性,那么将根据该属性的值决定圆形的大小;如果Sprite没有radius属性,只有rect属性,那么该圆形将取自rect矩形的外接圆;其中,该圆形的圆心为Sprite的中心点。所以,如果使用该模式,则Sprite必须有rect属性,radius属性为可选。
与collide_circle类似,只不过它可以按比例缩放待检测Sprite的圆形区域。参数ratio同样也为一个浮点数。
通过检测两个Sprite的位掩码(bitmask)是否重合判断碰撞情况。如果Sprite有mask属性,那么该mask即是Sprite的位掩码;如果Sprite没有mask属性,那么将利用Sprite的image属性自动创建mask。所以在使用这种模式时,image是Sprite必须有的属性,mask是可选属性。
不过,基于性能考虑,有必要提前计算Sprite的mask,而不是每次检测时都要重新从image中创建mask。一般利用下面的函数从Sprite的image属性中创建mask:
这里所谓的mask或者bitmask其实标记的是Sprite图片中的不透明部分。通过检测图片的不透明部分是否有重合可以实现Sprite的完美碰撞检测,从而有效避免看似没有碰撞却检测到碰撞的情况的发生。
总结一下,在考虑使用哪种检测模式时,可以根据Sprite图片的内容进行选择。不过collide_mask应该是最常用的,它可以完美地实现大多数场合中的碰撞检测。
3. 具体示例
最后是一个完整的示例。让我们一起完善前面Sprite小节中的程序,为它添加碰撞检测功能,让小狗在遇到骨头时就把它们吃掉。
如图2-18所示,这是程序运行中的一幅截图。
图2-18 碰撞检测示例程序
可见,与小狗发生碰撞的一块骨头消失不见了。
完整代码如下。
在上面的程序中,标记黑色的代码是与之前的Sprite示例程序相比新增的部分,其余的代码与Sprite示例程序中的一致。在这里,小狗与骨头之间的碰撞检测使用的是spritecollide()函数,它使用collide_mask作为检测模式,所以需要在Dog类和Bone类中计算相应的mask属性。让小狗把骨头吃掉其实就是让两者在发生碰撞时把骨头从骨头Group中删除,所以这里需要为spritecollide()函数的dokill参数赋予值True。