这是我们“太空大碰撞”项目的第 3 部分! 在本课中,我们将添加玩家和敌人之间的碰撞,以及添加子弹供玩家射击。
碰撞
碰撞是游戏开发的基本组成部分。 碰撞检测意味着你想要检测游戏世界中的一个物体是否接触到了另一个物体。 碰撞反应是决定碰撞发生时你想要发生的事情——马里奥是否捡起硬币,林克的剑是否对敌人造成伤害等等。
在我们的游戏中,我们目前有许多敌方精灵(Mob)沿着屏幕飞向我们的玩家,我们需要知道其中的Mob是否命中玩家。 在这个阶段的程序中,当敌方精灵击中玩家,就意味着游戏结束。
边框
请记住,Pygame 中的每个精灵都有一个 rect 属性,用于定义其坐标和大小。 Pygame 中的 Rect 对象的格式为 [x, y, width, height],其中 x 和 y 表示矩形的左上角。 这个矩形的另一个词是边框(bounding box),因为它代表对象的边界。
这种碰撞检测称为 AABB,代表“Axis Aligned Bounding Box”,因为矩形与屏幕轴对齐 - 它们没有倾斜角度。 AABB 碰撞非常受欢迎,因为它运算速度很快 - 计算机可以非常快速地比较矩形的坐标,如果您有大量对象要比较,这是一个很好的方法。
为了检测角色是否碰撞,我们需要查看玩家的矩形并将其与每个敌方Mob的矩形进行比较。 现在我们可以通过循环遍历Mob并为每个Mob进行比较来做到这一点:
在这张图片中,您可以看到只有矩形 #3 与黑色的大矩形发生碰撞。 #1 在 x 轴上重叠,但不在 y 轴上; #2 在 y 中重叠,但在 x 中不重叠。 为了使两个矩形重叠,它们的边界必须在每个轴上重叠。 用代码实现:
if mob.rect.right > player.rect.left and \
mob.rect.left < player.rect.right and \
mob.rect.bottom > player.rect.top and \
mob.rect.top < player.rect.bottom:
collide = True
幸运的是,Pygame 为我们提供了 spritecollide() 函数,实现了上述检测方法。
玩家碰撞敌方Mob
我们要将此命令添加到游戏循环的“更新”部分:
#Update
all_sprites.update()
#check to see if a mob hit the player
hits = pygame.sprite.spritecollide(player, mobs, False)
if hits:
running = False
spritecollide() 函数接受 3 个参数:要检查的精灵的名称、要检测的精灵组的名称以及名为 dokill 的 True/False 参数。 dokill 参数允许您设置对象在被击中时是否应该被删除。 例如,如果我们试图查看玩家是否捡起硬币,我们希望将其设置为 True,这样硬币就会消失。
spritecollide() 命令的返回值是被击中的精灵列表(请记住,玩家一次可能与多个生物发生碰撞)。 我们将该列表赋值给变量 hits。
如果命中列表不为空,则 if 语句将为 True,我们将 running 设置为 False,因此游戏将结束。
回击
子弹精灵
现在我们准备添加一个新的精灵:子弹。 这将是一个在我们射击时生成的精灵,出现在玩家精灵的顶部,并以相当高的速度向上移动。 定义一个 sprite 对我们来说应该已经开始熟悉了,所以这里是完整的 Bullet 类:
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedy = -10
def update(self):
self.rect.y += self.speedy
# kill if it moves off the top of the screen
if self.rect.bottom < 0:
self.kill()
在子弹精灵的 __init__()
方法中,我们给它一个 x 和 y 值,以便我们可以告诉精灵出现在哪里。 由于玩家精灵可以移动,所以我们可以通过它们将子弹精灵设置为玩家射击时玩家所在的位置。 我们将 speedy 设置为负值,这样它就会向上。
最后,我们检查子弹是否已经离开屏幕顶部,如果是,我们将其删除。
按键事件
为了简单起见,目前我们让游戏在每次玩家按下空格键时都会发射一颗子弹。 我们需要将其添加到事件检查中:
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.shoot()
上面的代码检查 KEYDOWN 事件,如果有,检查它是否是 K_SPACE 键。 如果是,我们将运行玩家精灵的 shoot() 方法。
###生成子弹
首先,我们需要添加一个新组来保存所有子弹精灵:
bullets = pygame.sprite.Group()
现在,我们可以在 Player 类中添加以下方法:
def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
shoot() 方法所做的只是生成一颗子弹,使用玩家的顶部中心作为生成点。 然后我们确保将子弹添加到 all_sprites(以便绘制和更新)和子弹组bullets——这将用于碰撞检测。
子弹碰撞
现在我们需要检查子弹是否击中了敌方Mob。 这里的区别是我们有多个子弹(在子弹组中)和多个Mob(在Mob组中),所以我们不能像以前那样使用 spritecollide() ,因为它只会将一个精灵与一组精灵进行比较。 相反,我们将使用 groupcollide():
# Update
all_sprites.update()
# check to see if a bullet hit a mob
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
m = Mob()
all_sprites.add(m)
mobs.add(m)
groupcollide() 函数类似于 spritecollide(),除了这里指定的进行比较的是2个精灵组,我们会得到一个被击中的生物列表。 有两个 dokill 选项来设置是否删除精灵组中碰撞到的精灵,每个精灵组一个。
如果我们只是删除敌方Mob,我们会遇到一个问题:Mob用完了! 所以我们要做的是循环点击,对于我们摧毁的每一个Mob,都会产生另一个新的。
现在它开始感觉像是一个可玩的游戏了:
在下一课中,我们将学习如何将图形添加到我们的游戏中,而不是使用那些纯色矩形。
本次课程的完整代码请访问好学好教Python编程平台,直接点击运行查看效果。