大多数计算机游戏中,你需要知道一个动画精灵在什么时候碰到另一个精灵。例如,可能需要知道保龄球何时碰到球瓶,或者导弹什么时候击中飞船。
你可能认为,如果我们知道每个动画精灵的位置和大小,可以写一些代码对每一个其他动画精灵的位置和大小进行检查,看哪里出现了重叠。不过,编写 Pygame 的人已经为我们完成了这项工作。Pygame 中已经内置有这种碰撞检测。
术语箱
简单地说,碰撞检测(collision detection)指的是了解两个动画精灵何时接触或重叠。两个移动的东西相互碰到一起,这就是一个碰撞(collision)。
Pygame 还提供了一种方法对动画精灵分组。例如,在保龄球游戏中,所有球瓶可能在一组,球则在另一组。
组和碰撞检测密切相关。在保龄球例子中,你可能想检测球何时击倒某个瓶子,因此要寻找球精灵与球瓶组中所有精灵之间的碰撞。还可以检测组内部的碰撞(如球瓶相互碰倒)。
下面来完成一个例子。以我们的反弹沙滩球为基础,不过为了更容易地看出发生了什么,这里首先建立 4 个球而不是 9 个球。另外与上一个例子中建立球的列表不同,我们将会使用 Pygame 的 group
类。
这里还要对代码稍稍做些整理,把完成球动画的部分(代码清单 17-2 中的最后几行)放在一个函数中,我们把这个函数命名为 animate
。animate
函数还包括完成碰撞检测的代码。两个球碰撞时,我们会让它们反向。
代码清单 17-3 显示了相应的代码。
代码清单 17-3 使用一个动画精灵组而不是列表
这里最有意思的新内容是碰撞检测如何工作。Pygame sprite
模块有一个 spritecollide
函数,它会查找一个精灵与一个组中所有精灵之间的碰撞。要检查同一个组中精灵之间的碰撞,必须通过 3 步来完成:
从这个组中删除这个精灵;
检查这个精灵与组中其他精灵之间的碰撞;
再把这个精灵添加回原来的组中。
这些工作在第 21 行到第 29 行的 for
循环中(animate
函数的中间部分)完成。如果开始时没有从组中删除这个精灵,spritecollide
会检测到这个精灵与它自身发生了碰撞,因为它也在这个组中。乍一看好像有些奇怪,不过如果再想想看就会发现这是有道理的。
如果加大动画步,就能更容易地看到这一点。可以把速度从 2 增加到 5,另外把各步之间的延迟从 20 增加到50。
运行程序,看看有什么结果。有没有注意到一些奇怪的行为?我注意到两点:
球碰撞时,它们会“颤抖”或者发生两次碰撞;
有时球会“卡”在窗口边界上,颤抖一段时间。
为什么会出现这种情况?嗯,这与我们如何编写 animate
函数有关。注意我们的做法是先移动一个球,检查它的碰撞,然后移动另一个球,再检查这个球的碰撞,依此类推。也许应该先完成所有移动,然后再完成全部碰撞检测。
所以需要把第 28 行(ball.move
)放在它自己的循环中,就像这样:
试试看,效果是不是比原来好一些。
可以对这个代码做些试验,改变某些值,比如速度(time.delay
数)、球数、球原先的位置、随机性等,来看球会有什么变化。
矩形碰撞与像素完美碰撞
你会注意到,球“碰撞”时并不总是完全接触。这是因为 spritecollide
没有使用球的圆形轮廓来检测碰撞。它使用了球的 rect
,也就是球的外围矩形。
如果想看看具体是怎样的,可以画一个矩形包围球图像,并且使用这个新图像而不是原先常规的沙滩球图像。我已经为你做好了这个新图像,你可以试一试:
img_file = “b_ball_rect.png”
看上去就像右图显示的这样:
如果希望球的圆形部分(而不是矩形边界)真正接触时球才会相互反弹,就必须使用一种称为“像素完美碰撞检测”的方法。spritecollide
函数没有这样做,而是使用了更简单的“矩形碰撞检测”。
它们的区别如下。使用矩形碰撞检测,两个球矩形区的任何部分相互接触时就会“碰撞”。而使用像素完美碰撞检测,两个球本身接触时才会碰撞。如下:
像素完美碰撞检测更逼真。(在真正的沙滩球周围你不会觉得有任何隐形的矩形,对吧?)但是在程序中实现时就没这么简单了。
对于要在 Pygame 中完成的大多数工作来说,矩形碰撞检测已经足够了。像素完美碰撞检测需要写更多代码,而且会让游戏运行得更慢,所以应该只有在确实有必要的情况下才使用这种方法。完成像素完美碰撞检测有几个模块(我上次查看时,Pygame 网站上至少有两个)。如果想尝试进行像素完美碰撞检测,只需在网上搜索一下就会找到这些模块。