目录
引言
到这篇博文位置,我们对《外星人入侵》这款游戏中的游戏元素进行了绘制,游戏元素的移动进行了完善。
我们已经可以在屏幕上移动飞船,发射子弹;同时屏幕上方的外星人群也会以一定的规律逼近飞船。
现在,我们想要继续完善游戏,实现飞船发射的子弹能够击落外星人,外星人能够击落飞船,以及外星人到达屏幕底部后,视为击落飞船的效果。
一、子弹击落外星人
我们在绘制子弹和外星人时,都是通过 精灵类 继承来的,处理起来也是用精灵对象的方法来处理,因此,我们这里可以通过精灵之间的碰撞检测来实现子弹和外星人之间的碰撞检测。
1、子弹和外星人碰撞检测
要想实现子弹击落外星人,首先需要能检测到两者之间的碰撞。
我们可以通过 pygame 包中的 sprite 模块中的 groupcollide() 方法来实现。
groupcollide(group1, group2, bool1,bool2)
group1:第一个精灵编组
group2:第二个精灵编组
bool1:碰撞后是否删除第一个精灵
bool2:碰撞后是否删除第二个精灵
这个方法实现的逻辑是:
遍历第一个编组中的精灵,判断它的 rect 是否和第二个编组中的精灵碰撞,如果碰撞了,将不会继续遍历。
第3、4个参数表示,碰撞后,两个碰撞的精灵是否从各自编组中删除。
如果设置为 True, True,那么碰撞后,碰撞的两个精灵都会从各自的编组中删除,可以用来实现普通子弹击落外星人的效果。
如果设置为 False,True,那么碰撞后,子弹不会消失,被击中的外星人会消失,这颗子弹有可能会继续击落别的外星人,可以用来实现超级子弹的效果。
如果设置为 True,False,那么碰撞后,子弹消失,外星人不会消失,可以用来实现外星人BOSS的效果。
现在,我们编写方法来检测子弹和外星人之间的碰撞
def check_bullet_alien_collide(bullets, aliens,settings, screen, ship):
# 检测子弹队列中的每颗子弹是否击中外星人队列中的外星人
pygame.sprite.groupcollide(bullets, aliens, True, True)
2、调用碰撞检测方法
我们现在定义了子弹和外星人之间的碰撞检测方法,那么问题是该在什么地方调用它呢?
我们可以在移动子弹后,判断子弹是否和外星人发生碰撞。
因此,需要在 update_bulltes() 方法中调用
def update_bullet(bullets, settings, aliens, screen, ship):
# 更新子弹队列中的所有子弹位置
bullets.update(settings)
# 如果子弹超出了屏幕范围,那么就删除这枚子弹
for bullet in bullets.copy(): # 注意这里我们判断的是副本中的子弹,但是删除的是本体的子弹
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# 检测子弹和外星人之间的碰撞
check_bullet_alien_collide(bullets, aliens,settings, screen, ship)
现在,我们运行游戏时,飞船发射的子弹在碰撞到外星人时,两者会一同消失。
二、外星人击落飞船
现在,我们想要实现外星人碰撞到飞船后,飞船被击落的效果。
与之前的碰撞检测不同,此处的碰撞对象是 单个的精灵 和 精灵编组,因此需要用新的方法来实现。
1、飞船和外星人碰撞检测
我们可以通过 pygame 包下的 sprite 模块中的spritecollideany() 方法来实现。
spritecollideany(sprite, group)
遍历编组中的所有精灵,判断是否和参数精灵有碰撞,只要有任意一个碰撞了,就不再遍历下去,同时返回编组中发生碰撞的精灵,如果未发生碰撞,则返回NONE。
2、飞船和外星人碰撞后的业务
我们设计,当外星人碰撞到飞船后,要做这样几件事:
1、清空当前全部外星人
2、清空当前全部子弹
3、将飞船重置到屏幕底部中间
4、重新生成一群外星人
5、游戏暂停 0.5秒,让玩家意识到飞船被撞了
将这些业务统一封装到 ship_hit() 方法里
# 飞船被撞到后的业务
def ship_hit(game_stats, aliens, bullets, settings, screen, ship):
# 删除全部外星人 和 全部子弹
aliens.empty()
bullets.empty()
# 重新生成一群外星人
create_fleet(settings, screen, aliens, ship)
# 将飞船重置到屏幕中间底部位置
ship.center_ship()
# 停止游戏 0.5秒,让玩家能意识到飞船被撞到了
sleep(0.5)
注意,我们并不会删除飞船并重新生成一架飞船,这样太慢了
我们直接控制飞船的横坐标,让它重新回到中间即可。
游戏始终都只会有一架飞船供我们使用。
3、重构 update_aliens()
现在,我们需要在 update_aliens() 方法中增加外星人和飞船的碰撞检测,并在碰撞后发生业务。
# 更新外星人位置的方法
def update_aliens(aliens, settings, ship, game_stats, bullets, screen):
check_fleet_edges(aliens, settings)
aliens.update()
# 检测飞船和外星人之间的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
# 飞船和外星人碰撞后的业务
ship_hit(game_stats, aliens, bullets, settings, screen, ship)
此时,运行游戏,当外星人碰撞到飞船后,就会产生我们设置的业务效果。
三、外星人触底
除了外星人撞到飞船外,我们还可以设计,当外星人触碰到游戏屏幕底部时,也视为撞到了飞船。
1、外星人和屏幕的碰撞检测
这里的碰撞检测,可以参考之前我们实现外星人碰撞屏幕边界的思想:
遍历外星人编组,判断外星人的底边值是否大于屏幕高度即可
for alien in aliens.sprites():
if alien.rect.bottom >= screen_height:
# 击落飞船的业务
2、代码整合封装
现在将外星人触底的代码和触底后的业务代码,封装到一个方法中
# 检测外星人和屏幕底端的碰撞
def check_alien_bottom(game_stats, aliens, bullets, settings, screen, ship):
screen_height = settings.screen_height
for alien in aliens.sprites():
if alien.rect.bottom >= screen_height:
ship_hit(game_stats, aliens, bullets, settings, screen, ship)
同时,需要再次修改 update_aliens() 方法,因为我们不仅要检测外星人和飞船的碰撞,还需要检测外星人和屏幕底部的碰撞了
# 更新外星人位置的方法
def update_aliens(aliens, settings, ship, game_stats, bullets, screen):
# 外星人对屏幕边界的碰撞检测
check_fleet_edges(aliens, settings)
aliens.update()
# 检测飞船和外星人之间的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
# 飞船和外星人碰撞后的业务
ship_hit(game_stats, aliens, bullets, settings, screen, ship)
# 检测外星人和屏幕底端的碰撞
check_alien_bottom(game_stats, aliens, bullets, settings, screen, ship)
四、设置飞船数量和游戏状态
此时,我们已经可以实现子弹、飞船、外星人之间的碰撞,以及碰撞之后的业务了。
但是,还有一个问题,当我们开始游戏后,玩家其实是“不会输”的。
为什么这么说呢?因为即使玩家的水平很差,不断地被外星人碰撞,顶多也只是一次次地重置飞船并生成新的外星人大军罢了。
基于此,我们想到,可以限制玩家使用的飞船数量
1、设置玩家飞船数
在设置类中新增玩家的飞船数
# 4.2 玩家所能使用的最大飞船数
self.ship_limit = 3
2、设置游戏的活跃状态
我们希望,在飞船被外星人碰撞后,或者是外星人到达屏幕底部后,玩家可使用的飞船数减一。
当玩家可用的飞船数不足时,游戏就不再进行下去。
通过游戏的“活跃状态”来表示游戏是否进行下去。
我们将游戏状态相关的设置放置在一个新的模块 game_stats.py 中,设置GameStats 类。
class GameStats():
# 初始化游戏状态,设置玩家开局将有多少架飞船可以使用
def __init__(self, settings):
self.settings = settings
self.reset_stats()
def reset_stats(self):
self.ship_left = self.settings.ship_limit
self.game_active = True
3、修改碰撞后的业务
现在,可以根据设计来实现玩家飞船数减一,并在可用飞船数不够的情况下修改游戏状态。这些动作都是发生在外星人碰撞到飞船后以及外星人到达屏幕底部后。
好在所有碰撞后的业务都是封装在 ship_hit() 方法中的, 因此,我们直接修改它即可。
# 飞船被撞到后的业务
def ship_hit(game_stats, aliens, bullets, settings, screen, ship):
if game_stats.ship_left > 0:
# 玩家可用的飞船数 -1
game_stats.ship_left -= 1
# 删除全部外星人 和 全部子弹
aliens.empty()
bullets.empty()
# 重新生成一群外星人
create_fleet(settings, screen, aliens, ship)
# 将飞船重置到屏幕中间底部位置
ship.center_ship()
# 停止游戏 0.5秒,让玩家能意识到飞船被撞到了
sleep(0.5)
else:
game_stats.game_active = False
4、修改游戏主循环代码
游戏主循环中有点业务需要一直进行,而有一些则只在游戏处于活跃状态时才执行
# 游戏主循环,使得循环中的代码可以不断循环执行
while True:
# 事件检测,现在我们已经将事件循环的代码封装到函数中了,直接运行即可
check_events(my_ship, my_screen, my_settings, bullets)
# 当游戏处于活跃状态时,才渲染屏幕元素
if game_stats.game_active:
# 每次执行完事件检测循环后,都更新飞船的位置
my_ship.update()
# 每次执行完事件检测循环后,都更新子弹的位置、并删除出界的子弹,检测子弹和外星人之间的碰撞
update_bullet(bullets, my_settings, aliens, my_screen, my_ship)
# 每次执行完事件检测后,都更新外星人的位置,检测外星人和飞船之间的碰撞
update_aliens(aliens, my_settings, my_ship, game_stats, bullets, my_screen)
# 绘制游戏画面,包括屏幕背景、飞船,并将内容显示到游戏屏幕上
update_screen(my_screen, my_settings, my_ship, bullets, aliens)
这里,我们对事件检测要一直执行,因为需要等待玩家退出游戏的操作
而绘制游戏元素的行为,在游戏的非活跃状态下就不必进行了。
五、小结
本篇博文中,实现了较多内容,包括:
1、子弹和外星人之间的碰撞检测
2、外星人和飞船之间的碰撞检测
3、外星人和屏幕底部的碰撞检测
4、控制玩家可用的飞船数并控制游戏活跃度