重构 check_events()
随着开发工作的进行,函数 check_events() 将越来越长,所以可以考虑将其部分代码放在两个函数中:
- 一个处理 KEYDOWN 事件;
- 一个处理 KEYUP 事件;
如下:
def check_events(settings, ship): # 响应按键和鼠标事件 for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ship.moving_right = True #ship.rect.centerx += 50 #if ship.rect.centerx > settings.screen_width: # ship.rect.centerx -= 50 if event.key == pygame.K_LEFT: ship.moving_left = True #ship.rect.centerx -= 50 #if ship.rect.centerx < 0: #ship.rect.centerx += 50 elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: ship.moving_right = False if event.key == pygame.K_LEFT: ship.moving_left = False
修改后,将其中按键按下处理和按键松开处理封装成对应函数,如下:
def check_keydown_events(event, ship): # 响应按键按下 if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True def check_keyup_events(event, ship): # 响应按键松开 if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False def check_events(ship): # 响应按键和鼠标事件 for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: check_keydown_events(event, ship) elif event.type == pygame.KEYUP: check_keyup_events(event, ship)
射击
下面就开始添加射击功能。将编写玩家按下空格键时发射子弹(小矩形)的响应处理,子弹将在屏幕中向上穿行,抵达屏幕上边缘后消失。
添加子弹设置
首先,更新 settings.py ,在其方法 __init__() 末尾存储新类 Bullet 所需的值:
class Settings(): '''存储《外星人大战》所有设置的类''' def __init__(self): # 初始化游戏的设置 self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 0, 0) # 飞船的设置 self.ship_speed_factor = 1.5 # 子弹设置 self.bullet_speed_factor = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60
创建 Bullet 类
下面创建 bullet 模块,编写子弹运动的 Bullet 类,具体如下:
import pygame from pygame.sprite import Sprite class Bullet(Sprite): # 一个对飞船发射的子弹进行管理的类 def __init__(self, ai_settings, screen, ship): # 在飞船所处的位置创建一个子弹对象 super(Bullet, self).__init__() self.screen = screen # 在 (0,0)处创建一个表示子弹的矩形,在设置正确的位置 self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height) self.rect.centerx = ship.rect.centerx self.rect.top = ship.rect.top # 存储用小数表示的子弹位置 self.y = float(self.rect.y) self.color = ai_settings.bullet_color self.speed_factor = ai_settings.bullet_speed_factor def update(self): # 向上移动子弹 # 更新表示子弹的小数值 self.y -= self.speed_factor # 更新表示子弹的rect位置 self.rect.y = self.y def draw_bullet(self): # 在屏幕上绘制子弹 pygame.draw.rect(self.screen, self.color, self.rect)
- Bullet 类继承了从模块 pygame.sprite 模块中导入的 Sprite 类,通过使用精灵,可以将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,需要想 __init__() 传递 ai_settings 、 screen 、 ship 实例。
- 方法 update() 管理子弹的位置,放射子弹后,子弹在屏幕中向上移动,这意味着 y 的值不断减小,因此更新子弹的位置就可以看到子弹在移动。
- 方法 draw_bullet() 绘制子弹,使用存储在 self.color 中的颜色填充子弹的 rect 占据屏幕部分。
将子弹存储到编组
定义 Bullet 类后,就可以编写代码。首先,将 alien_invasion.py 中创建一个编组(group),用于存储所有高效的子弹,以便能够管理发射出去的所有子弹。
Group 类其实类似于一个列表,方便存储精灵。具体修改如下(alien_invasion.py):
import sys import pygame from settings import Settings from ship import Ship import game_func as gf from pygame.sprite import Group def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alien Invasion") # 创建一艘飞船 ship = Ship(ai_settings, screen) # 创建一个用于存储子弹的编组 bullets = Group() # 开始游戏的主循环 while True: # 监视键盘和鼠标事件 gf.check_events(ai_settings, screen, ship, bullets) ship.update() bullets.update() # 调用 update_screen() 更新屏幕 gf.update_screen(ai_settings, screen, ship, bullets) run_game()
开火
在 game_func.py 中,需要修改 check_keydown_events() 代码,添加对空格键的响应操作,并且还需要修改 update_screen() ,以确保在调用 flip() 前在屏幕上已经完成了子弹重绘。具体修改如下:
def check_keydown_events(event, ai_settings, screen, ship, bullets): # 响应按键按下 if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: # 创建一颗子弹,并将其加入编组bullets中 new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet) def update_screen(ai_settings, screen, ship, bullets): # 更新屏幕上的图像,并切换到新屏幕 # 每次循环时都重新绘制屏幕 screen.fill(ai_settings.bg_color) # 在飞船和外星人后面重新绘制子弹 for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() # 让最近绘制的屏幕可见 pygame.display.flip()
执行效果如下图所示:
删除已消失的子弹
上面的操作完成后,子弹到达顶端后看上去是消失了,实际上是因为超出边界没有显示,其实子弹依然存在,只不过它们的 y 坐标值为负数,这样积攒过多对程序消耗和处理都是负担。所以需要将这些已经消失的子弹进行删除,其实就是检测子弹坐标条件,在超出上边界后将其删除即可。具体代码如下( alien_invasion.py ):
import sys import pygame from settings import Settings from ship import Ship import game_func as gf from pygame.sprite import Group def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alien Invasion") # 创建一艘飞船 ship = Ship(ai_settings, screen) # 创建一个用于存储子弹的编组 bullets = Group() # 开始游戏的主循环 while True: # 监视键盘和鼠标事件 gf.check_events(ai_settings, screen, ship, bullets) ship.update() bullets.update() # 删除已消失的子弹 for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) print(len(bullets)) # 调用 update_screen() 更新屏幕 gf.update_screen(ai_settings, screen, ship, bullets) run_game()
执行结果如下,从图中可以看出在子弹到达上边界后,列表中的数量就减少了。
限制子弹的数量
很多射击游戏都对可同时出现在屏幕上的子弹数量进行了限制,以鼓励玩家有目标地设计,这里也添加上这部分的限制。
首先,在 settings.py 中存储子弹最大限制数量参数,如下:
class Settings(): '''存储《外星人大战》所有设置的类''' def __init__(self): # 初始化游戏的设置 self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 0, 0) # 飞船的设置 self.ship_speed_factor = 1.5 # 子弹设置 self.bullet_speed_factor = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 5
这里将同时存在的子弹数量设置为 5 ,如何在 game_func.py 中的 check_keydown_events() 对空格的响应增加子弹个数前进行条件判断,具体如下:
def check_keydown_events(event, ai_settings, screen, ship, bullets): # 响应按键按下 if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: # 创建一颗子弹,并将其加入编组bullets中 # 判断当前子弹数量 if len(bullets) < ai_settings.bullets_allowed: new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet)
创建函数 update_bullets()
编写并检查子弹管理代码后,可以将这部分代码封装到 game_func模块中,使得主程序尽可能简单,这里创建一个名为 update_bullets() 的函数,添加到 game_func模块中,具体如下:
def update_bullets(bullets) # 更新子弹的位置 bullets.update() # 删除已消失的子弹 for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet)
如何修改 alien_invasion.py 模块中的 while 循环,如下:
import sys import pygame from settings import Settings from ship import Ship import game_func as gf from pygame.sprite import Group def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alien Invasion") # 创建一艘飞船 ship = Ship(ai_settings, screen) # 创建一个用于存储子弹的编组 bullets = Group() # 开始游戏的主循环 while True: # 监视键盘和鼠标事件 gf.check_events(ai_settings, screen, ship, bullets) ship.update() # 调用 update_bullets() 更新子弹 gf.update_bullets(bullets) # 调用 update_screen() 更新屏幕 gf.update_screen(ai_settings, screen, ship, bullets) run_game()
创建函数 fire_bullet()
下面将发射子弹的代码转移到单独的函数中,这样在 check_keydown_events() 中只需要使用一行函数调用即可完成发射子弹操作。
具体如下( game_func.py ):
def fire_bullet(ai_settings, screen, ship, bullets): # 创建一个新子弹,并加入编组bullets # 判断当前子弹数量 if len(bullets) < ai_settings.bullets_allowed: new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet) def check_keydown_events(event, ai_settings, screen, ship, bullets): # 响应按键按下 if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: # 创建一颗子弹,并将其加入编组bullets中 fire_bullet(ai_settings, screen, ship, bullets)
添加程序退出快捷键响应
在 game_func.py 模块中添加对 Q 按键的退出响应,具体如下:
def check_keydown_events(event, ai_settings, screen, ship, bullets): # 响应按键按下 if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: # 创建一颗子弹,并将其加入编组bullets中 fire_bullet(ai_settings, screen, ship, bullets) elif event.key == pygame.K_q: # 退出程序 sys.exit()
创建第一个外星人
在屏幕上放置敌方外星人和放置飞船类似,每个外星人的行为都由 Alien 类控制,我们将像创建 Ship 类那样创建这个类,简化考虑,使用位图表示外星人,下载外星人的图片文件,下载链接:外星人图片下载
将下载好的图片放置到 images 目录下,命名为 alien.png 如下图所示:
创建 alien.py 模块,具体代码如下:
import pygame from pygame.sprite import Sprite class Alien(Sprite): # 表示外星人的类 def __init__(self, ai_settings, screen): # 初始化外星人并设置其初始位置 super(Alien, self).__init__() self.screen = screen self.ai_settings = ai_settings # 加载外星人图像,并设置rect属性 self.image = pygame.image.load('images/alien.png') self.rect = self.image.get_rect() # 每个外星人最初在屏幕左上角出现 self.rect.x = self.rect.width self.rect.y = self.rect.height def blitme(self): # 在指定位置绘制外星人 self.screen.blit(self.image, self.rect)
创建 Alien 实例
下面在 alien_invasion.py 中创建一个 Alien 实例:
import sys import pygame from settings import Settings from ship import Ship from alien import Alien import game_func as gf from pygame.sprite import Group def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alien Invasion") # 创建一艘飞船 ship = Ship(ai_settings, screen) # 创建一个用于存储子弹的编组 bullets = Group() # 创建一个外星人 alien = Alien(ai_settings, screen) # 开始游戏的主循环 while True: # 监视键盘和鼠标事件 gf.check_events(ai_settings, screen, ship, bullets) ship.update() # 调用 update_bullets() 更新子弹 gf.update_bullets(bullets) # 调用 update_screen() 更新屏幕 gf.update_screen(ai_settings, screen, ship, bullets) run_game()
并且修改其中调用函数 updaget_screen(),如下:
def update_screen(ai_settings, screen, ship, alien, bullets): # 更新屏幕上的图像,并切换到新屏幕 # 每次循环时都重新绘制屏幕 screen.fill(ai_settings.bg_color) # 在飞船和外星人后面重新绘制子弹 for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() alien.blitme() # 让最近绘制的屏幕可见 pygame.display.flip()
运行之后,如下图:
创建一群外星人
要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行外星人。所以需要先计算外星人之间的水平间距,并创建一行外星人,再确定可以的垂直空间,并创建整群外星人。
确定一行可容纳多少个外星人
为了确定一行可容纳多少个外星人,了解可用的水平空间大小。屏幕宽度存储在 ai_settings.screen_width 中,但是两边还是需要留下一定的空袭,因此可以用放置外星人的水平控件定为屏幕宽度减去外星人宽度的两倍。
# 可用空间宽度 available_space_x = ai_settings.screen_width - (2 * alien_width) # 单行可容纳外星人个数 number_aliens_x = available_space_x / (2 * alien_width)
创建多行外星人
为创建一行外星人,首先在 alien_ivasion.py 中创建一个名为 aliens 的空编组,用于存储全部外星人。再调用 game_func.py 模块中创建外星人群的函数。
game_func.py 模块中创建外星人群函数如下:
def create_fleet(ai_settings, screen, aliens): # 创建外星人群 # 创建一个外星人,并计算一行可容纳多少个外星人 # 外星人间距为外星人宽度 alien = Alien(ai_settings, screen) alien_width = alien.rect.width available_space_x = ai_settings.screen_width - 2 * alien_width number_aliens_x = available_space_x / (2 * alien_width) # 创建第一行外星人 for alien_number in range(number_aliens_x): # 创建第一个外星人并加入当前行 alien = Alien(ai_settings, screen) alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x aliens.add(alien) def update_screen(ai_settings, screen, ship, aliens, bullets): # 更新屏幕上的图像,并切换到新屏幕 # 每次循环时都重新绘制屏幕 screen.fill(ai_settings.bg_color) # 在飞船和外星人后面重新绘制子弹 for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() aliens.draw(screen) # 让最近绘制的屏幕可见 pygame.display.flip()
修改 alien_invasion.py 中的调用,具体如下:
import sys import pygame from settings import Settings from ship import Ship from alien import Alien import game_func as gf from pygame.sprite import Group def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alien Invasion") # 创建一艘飞船 ship = Ship(ai_settings, screen) # 创建一个用于存储子弹的编组 bullets = Group() # 创建外星人群 aliens = Group() gf.create_fleet(ai_settings, screen, aliens) # 开始游戏的主循环 while True: # 监视键盘和鼠标事件 gf.check_events(ai_settings, screen, ship, bullets) ship.update() # 调用 update_bullets() 更新子弹 gf.update_bullets(bullets) # 调用 update_screen() 更新屏幕 gf.update_screen(ai_settings, screen, ship, aliens, bullets) run_game()
执行如下图所示:
重构 create_fleet()
这时候能看出外星人都在屏幕上靠左边,这样就可以向右移动,触及屏幕边缘后下移再向左移动,依次类推。
下面是 create_fleet() 和新创建的两个函数 aliens_x() 、 create_alien() :
def get_number_aliens_x(ai_settings, alien_width): # 计算每行可容纳的外星人个数 available_space_x = ai_settings.screen_width - 2 * alien_width number_aliens_x = int(available_space_x / (2 * alien_width)) return number_aliens_x def create_alien(ai_settings, screen, aliens, alien_number): # 创建一个外星人并将其放在当前行 alien = Alien(ai_settings, screen) alien_width = alien.rect.width alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x alien.add(alien) def create_fleet(ai_settings, screen, aliens): # 创建外星人群 # 创建一个外星人,并计算一行可容纳多少个外星人 # 外星人间距为外星人宽度 alien = Alien(ai_settings, screen) number_aliens_x = get_number_aliens_x(ai_settings, alien_width) # 创建第一行外星人 for alien_number in range(number_aliens_x): # 创建第一个外星人并加入当前行 create_alien(ai_settings, screen, aliens, alien_number)
添加行
要创建外星人群,首先就需要计算屏幕可容纳多少行,并对创建一行外星人的循环重复相应的次数。为计算可容纳的行数,需要计算当前屏幕可用垂直空间:
将屏幕高度减去第一行外星人的高度,飞船的高度以及最初外星人群与飞船的距离(外星人高度的两倍)
available_space_y = ai_settings.screen_height - 3 * alien_height - ship_height
这样计算将会在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。所以行数计算如下:
number_rows = available_height_y / (2 * alien_height)
知道可容纳多少行后,便可以重复执行创建一行外星人的代码( game_func.py ):
def get_number_rows(ai_settings, ship_height, alien_height): # 计算屏幕可容纳多少行外星人 available_space_y = (ai_settings.screen_height - (3 * alien_height) - ship_height) number_rows = int(available_space_y / (2 * alien_height)) return number_rows def create_alien(ai_settings, screen, aliens, alien_number, row_number): # 创建一个外星人并将其放在当前行 alien = Alien(ai_settings, screen) alien_width = alien.rect.width alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number aliens.add(alien) def create_fleet(ai_settings, screen, ship, aliens): # 创建外星人群 # 创建一个外星人,并计算一行可容纳多少个外星人 # 外星人间距为外星人宽度 alien = Alien(ai_settings, screen) number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width) number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height) # 创建外星人群 for row_number in range(number_rows): # 创建第一行外星人 for alien_number in range(number_aliens_x): # 创建第一个外星人并加入当前行 create_alien(ai_settings, screen, aliens, alien_number, row_number)
执行后可以看到添加了一群外星人,如下图所示:
让外星人移动
接下来就是让外星人群在屏幕上向右移动,在接触到屏幕边缘后下移一定距离,再向相反方向移动,不断移动所有的外星人,知道都被消灭;有外星人撞到飞船,或者有外星人抵达屏幕最低端则游戏结束。
向右移动外星人
为移动外星人,可以调用 alien.py 中的 update() 方法,需要先在 settings.py 模块中添加外星人移动速度参数,如下:
class Settings(): '''存储《外星人大战》所有设置的类''' def __init__(self): # 初始化游戏的设置 self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 0, 0) # 飞船的设置 self.ship_speed_factor = 1.5 # 子弹设置 self.bullet_speed_factor = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 5 # 外星人设置 self.alien_speed_factor = 1
然后设置 alien.py 中的 update() 实现,如下:
def update(self): # 向右移动外星人 self.x += self.ai_settings.alien_speed_factor self.rect.x = self.x
在 game_func.py 模块中添加 update_aliens() 方法用于更新外星人组的位置,如下:
def update_aliens(aliens): # 更新外星人群中所有的外星人的位置 # 对编组调用方法update(),会自动对每个组内成员调用update() aliens.update()
在主循环中添加使用更新外星人位置的方法,具体如下:
import sys import pygame from settings import Settings from ship import Ship from alien import Alien import game_func as gf from pygame.sprite import Group def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alien Invasion") # 创建一艘飞船 ship = Ship(ai_settings, screen) # 创建一个用于存储子弹的编组 bullets = Group() # 创建外星人群 aliens = Group() gf.create_fleet(ai_settings, screen, ship, aliens) # 开始游戏的主循环 while True: # 监视键盘和鼠标事件 gf.check_events(ai_settings, screen, ship, bullets) ship.update() # 调用 update_bullets() 更新子弹 gf.update_bullets(bullets) # 调用 update_aliens() 更新外星人位置 gf.update_aliens(aliens) # 调用 update_screen() 更新屏幕 gf.update_screen(ai_settings, screen, ship, aliens, bullets) run_game()
此时还没有添加移动到右端限制,所以外星人会始终右移,即使离开屏幕后还是会不断增加 x 的大小。
创建表示外星人移动方向的设置
下面创建使外星人在接触右边缘后向下移动、再向左移动的设置。在 settings.py 模块中添加参数:
class Settings(): '''存储《外星人大战》所有设置的类''' def __init__(self): # 初始化游戏的设置 self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 0, 0) # 飞船的设置 self.ship_speed_factor = 1.5 # 子弹设置 self.bullet_speed_factor = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 5 # 外星人设置 self.alien_speed_factor = 1 # 遇到边沿后向下移动的速度 self.fleet_drop_speed = 10 # fleet_direction :1-表示向右移动,-1 表示向左移动 self.fleet_direction = 1
添加外星人是否撞到边缘的判断
现在向 alien.py 中添加检测边缘的代码,如下:
def update(self): # 向右移动外星人 self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction) self.rect.x = self.x def check_edges(self): # 如果接触边缘,则返回 True screen_rect = self.screen.get_rect() if self.rect.right >= screen_rect.right: return True elif self.rect.left <= 0: return True else return False
实现向下移动并改变方向
有外星人到达屏幕边沿时,需要将其下移并改变移动方向。为此,可以向 game_func() 模块中添加判断边缘函数 check_fleet_edges() 、 change_fleet_direction() 。具体如下:
def check_fleet_edges(ai_settings, aliens): # 有外星人到达边缘时采取相应措施 for alien in aliens: if alien.check_edges(): change_fleet_direction(ai_settings, aliens) break def change_fleet_direction(ai_settings, aliens): # 整群外星人下移,并改变移动方向 for alien in aliens: alien.rect.y += ai_settings.fleet_drop_speed ai_settings.fleet_direction *= -1 def update_aliens(ai_settings, aliens): # 更新外星人群中所有的外星人的位置 check_fleet_edges(ai_settings, aliens) aliens.update()
修改 ai_invasion.py 中的相关调用,之前调用 gf.update_aliens() 的传递参数部分,gf.update_aliens(ai_settings, aliens) 。修改完成后执行效果如下:
射杀外星人
在创建完成飞船及操作响应和外星人移动后,就需要添加当子弹击中外星人后的响应操作。在游戏编程中,子弹碰撞到外星人其实就是两个游戏元素产生了重叠,可以采用 sprite.groupcollide() 方法来检测两个编组成员之间的碰撞。
检测子弹与外星人的碰撞
首先添加坚持子弹与外星人直接的碰撞,如果发生碰撞立刻使被碰撞的外星人消失,为此,可以在更新子弹位置后立刻检测碰撞并做出响应。
方法 sprite.groupcollide() 将每颗子弹的 rect 同每个外星人的 rect 进行比较,并返回一个字典,其中包含了碰撞的子弹和外星人。在这个字典中,每个键都是一颗子弹,而对应的值就是发生碰撞的外星人。
具体在 game_func.py 模块中的 update_bullets() 中添加如下代码:
def update_bullets(aliens, bullets): # 更新子弹的位置 bullets.update() # 删除已消失的子弹 for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) # 检查是否有子弹与外星人发送碰撞 #如果发生碰撞,删除发送碰撞的子弹和外星人 collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
其中,collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) ,groupcollide 会返回字典,后面的两个实参 True 表示当检测到发生碰撞时会自动移除组内发送碰撞的元素。修改相应的参数调用并执行,如下:
为测试方便添加大招子弹
只需要通过运行这个游戏就可以测试其中的很多功能,但是有些时候需要花很长的时间才能击落外星人进行测试,这很浪费时间,为了方便起见,可以将子弹的宽度设置很大,这样就能一次操作后将所有的外星人都击落了。
在 settings.py 模块中将子弹宽度设置与屏幕同宽,如下:
# 子弹设置 self.bullet_speed_factor = 1 self.bullet_width = 1200 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 5
在 game_func.py 中的碰撞检测的调用修改为 collisions = pygame.sprite.groupcollide(bullets, aliens, False, True) ,这样子弹在击落外星人后会继续存在,效果如下:
生成新的外星人群
当一个外星人群被消灭之后,又会出现新的一群外星人。所以需要在外星人群被消灭后再次添加一群外星人,这就需要检查编组 aliens 是否为空,如果为空则再次调用 create_fleet() ,这一步操作添加到 update_bullets() 中进行,因为外星人是在这里被消灭的,如下:
def update_bullets(ai_settings, screen, ship, aliens, bullets): # 更新子弹的位置 bullets.update() # 删除已消失的子弹 for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) # 检查是否有子弹与外星人发送碰撞 #如果发生碰撞,删除发送碰撞的子弹和外星人 collisions = pygame.sprite.groupcollide(bullets, aliens, False, True) # 检查是否需要再次添加外星人 if len(aliens) == 0: # 删除现有子弹并新建新的外星人群 bullets.empty() create_fleet(ai_settings, screen, ship, aliens)
改动后,效果如下:
提高子弹速度
当外星人数量多了之后,可能会发现子弹速度比之前更慢了,这是因为在每次循环后,pygame 需要做更多工作了,可以修改 settings.py 模块中的子弹速度参数来提高子弹的速度。
# 子弹设置 self.bullet_speed_factor = 1 self.bullet_width = 1200 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 5