Python编程:从入门到实践12.8 射击_Python编程:从入门到实践12.8 射击试读-查字典图书网
查字典图书网
当前位置: 查字典 > 图书网 > 编程 > Python编程:从入门到实践 > 12.8 射击

Python编程:从入门到实践——12.8 射击

下面来添加射击功能。我们将编写玩家按空格键时发射子弹(小矩形)的代码。子弹将在屏幕中向上穿行,抵达屏幕上边缘后消失。 12.8.1 添加子弹设置 首先,更新settings.py,在其方法__init__()末尾存储新类Bullet所需的值: settings.py def __init__(self): --snip-- # 子弹设置 self.bullet_speed_factor = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 这些设置创建宽3像素、高15像素的深灰色子弹。子弹的速度比飞船稍低。 12.8.2 创建Bullet类 下面来创建存储Bullet类的文件bullet.py,其前半部分如下: bullet.py 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 Bullet类继承了我们从模块pygame.sprite中导入的Sprite类。通过使用精灵,可将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,需要向__init__()传递ai_settings、screen和ship实例,还调用了super()来继承Sprite。 注意 代码super(Bullet, self).__init__()使用了Python 2.7语法。这种语法也适用于Python 3,但你也可以将这行代码简写为super().__init__()。 在处,我们创建了子弹的属性rect。子弹并非基于图像的,因此我们必须使用pygame.Rect()类从空白开始创建一个矩形。创建这个类的实例时,必须提供矩形左上角的x坐标和y坐标,还有矩形的宽度和高度。我们在(0, 0)处创建这个矩形,但接下来的两行代码将其移到了正确的位置,因为子弹的初始位置取决于飞船当前的位置。子弹的宽度和高度是从ai_settings中获取的。 在处,我们将子弹的centerx设置为飞船的rect.centerx。子弹应从飞船顶部射出,因此我们将表示子弹的rect的top属性设置为飞船的rect的top属性,让子弹看起来像是从飞船中射出的(见)。 我们将子弹的y坐标存储为小数值,以便能够微调子弹的速度(见)。在处,我们将子弹的颜色和速度设置分别存储到self.color和self.speed_factor中。 下面是bullet.py的第二部分——方法update()和draw_bullet(): bullet.py 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) 方法update()管理子弹的位置。发射出去后,子弹在屏幕中向上移动,这意味着y坐标将不断减小,因此为更新子弹的位置,我们从self.y中减去self.speed_factor的值(见)。接下来,我们将self.rect.y设置为self.y的值(见)。属性speed_factor让我们能够随着游戏的进行或根据需要提高子弹的速度,以调整游戏的行为。子弹发射后,其x坐标始终不变,因此子弹将沿直线垂直地往上穿行。 需要绘制子弹时,我们调用draw_bullet()。函数draw.rect()使用存储在self.color中的颜色填充表示子弹的rect占据的屏幕部分(见)。 12.8.3 将子弹存储到编组中 定义Bullet类和必要的设置后,就可以编写代码了,在玩家每次按空格键时都射出一发子弹。首先,我们将在alien_invasion.py中创建一个编组(group),用于存储所有有效的子弹,以便能够管理发射出去的所有子弹。这个编组将是pygame.sprite.Group类的一个实例;pygame.sprite. Group类类似于列表,但提供了有助于开发游戏的额外功能。在主循环中,我们将使用这个编组在屏幕上绘制子弹,以及更新每颗子弹的位置: alien_invasion.py import pygame from pygame.sprite import Group --snip-- def run_game(): --snip-- # 创建一艘飞船 ship = Ship(ai_settings, screen) # 创建一个用于存储子弹的编组  bullets = Group() # 开始游戏主循环 while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update()  bullets.update() gf.update_screen(ai_settings, screen, ship, bullets) run_game() 我们导入了pygame.sprite中的Group类。在处,我们创建了一个Group实例,并将其命名为bullets。这个编组是在while循环外面创建的,这样就无需每次运行该循环时都创建一个新的子弹编组。 注意 如果在循环内部创建这样的编组,游戏运行时将创建数千个子弹编组,导致游戏慢得像蜗牛。如果游戏停滞不前,请仔细查看主while循环中发生的情况。 我们将bullets传递给了check_events()和update_screen()。在check_events()中,需要在玩家按空格键时处理bullets;而在update_screen()中,需要更新要绘制到屏幕上的bullets。 当你对编组调用update()时,编组将自动对其中的每个精灵调用update(),因此代码行bullets.update()将为编组bullets中的每颗子弹调用bullet.update()。 12.8.4 开火 在game_functions.py中,我们需要修改check_keydown_events(),以便在玩家按空格键时发射一颗子弹。我们无需修改check_keyup_events(),因为玩家松开空格键时什么都不会发生。我们还需修改update_screen(),确保在调用flip()前在屏幕上重绘每颗子弹。下面是对game_ functions.py所做的相关修改: game_functions.py --snip-- from bullet import Bullet  def check_keydown_events(event, ai_settings, screen, ship, bullets): --snip--  elif event.key == pygame.K_SPACE: # 创建一颗子弹,并将其加入到编组bullets中 new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet) --snip--  def check_events(ai_settings, screen, ship, bullets): """响应按键和鼠标事件""" for event in pygame.event.get(): --snip-- elif event.type == pygame.KEYDOWN: check_keydown_events(event, ai_settings, screen, ship, bullets) --snip--  def update_screen(ai_settings, screen, ship, bullets): --snip-- # 在飞船和外星人后面重绘所有子弹  for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() --snip-- 编组bulltes传递给了check_keydown_events()(见)。玩家按空格键时,创建一颗新子弹(一个名为new_bullet的Bullet实例),并使用方法add()将其加入到编组bullets中(见);代码bullets.add(new_bullet)将新子弹存储到编组bullets中。 在check_events()的定义中,我们需要添加形参bullets(见);调用check_keydown_events()时,我们也需要将bullets作为实参传递给它。 在处,我们给在屏幕上绘制子弹的update_screen()添加了形参bullets。方法bullets.sprites()返回一个列表,其中包含编组bullets中的所有精灵。为在屏幕上绘制发射的所有子弹,我们遍历编组bullets中的精灵,并对每个精灵都调用draw_bullet()(见)。 如果此时运行alien_invasion.py,将能够左右移动飞船,并发射任意数量的子弹。子弹在屏幕上向上穿行,抵达屏幕顶部后消失,如图12-3所示。可在settings.py中修改子弹的尺寸、颜色和速度。 图12-3 飞船发射一系列子弹后的《外星人入侵》游戏 12.8.5 删除已消失的子弹 当前,子弹抵达屏幕顶端后消失,这仅仅是因为Pygame无法在屏幕外面绘制它们。这些子弹实际上依然存在,它们的y坐标为负数,且越来越小。这是个问题,因为它们将继续消耗内存和处理能力。 我们需要将这些已消失的子弹删除,否则游戏所做的无谓工作将越来越多,进而变得越来越慢。为此,我们需要检测这样的条件,即表示子弹的rect的bottom属性为零,它表明子弹已穿过屏幕顶端: alien_invasion.py # 开始游戏主循环 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)) gf.update_screen(ai_settings, screen, ship, bullets) 在for循环中,不应从列表或编组中删除条目,因此必须遍历编组的副本。我们使用了方法copy()来设置for循环(见),这让我们能够在循环中修改bullets。我们检查每颗子弹,看看它是否已从屏幕顶端消失(见)。如果是这样,就将其从bullets中删除(见)。在处,我们使用了一条print语句,以显示当前还有多少颗子弹,从而核实已消失的子弹确实删除了。 如果这些代码没有问题,我们发射子弹后查看终端窗口时,将发现随着子弹一颗颗地在屏幕顶端消失,子弹数将逐渐降为零。运行这个游戏并确认子弹已被删除后,将这条print语句删除。如果你留下这条语句,游戏的速度将大大降低,因为将输出写入到终端而花费的时间比将图形绘制到游戏窗口花费的时间还多。 12.8.6 限制子弹数量 很多射击游戏都对可同时出现在屏幕上的子弹数量进行限制,以鼓励玩家有目标地射击。下面在游戏《外星人入侵》中作这样的限制。 首先,在settings.py中存储所允许的最大子弹数: settings.py # 子弹设置 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 3 这将未消失的子弹数限制为3颗。在game_functions.py的check_keydown_events()中,我们在创建新子弹前检查未消失的子弹数是否小于该设置: game_functions.py def check_keydown_events(event, ai_settings, screen, ship, bullets): --snip-- 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) 玩家按空格键时,我们检查bullets的长度。如果len(bullets)小于3,我们就创建一个新子弹;但如果已有3颗未消失的子弹,则玩家按空格键时什么都不会发生。如果你现在运行这个游戏,屏幕上最多只能有3颗子弹。 12.8.7 创建函数update_bullets() 编写并检查子弹管理代码后,可将其移到模块game_functions中,以让主程序文件alien_invasion.py尽可能简单。我们创建一个名为update_bullets()的新函数,并将其添加到game_functions.py的末尾: game_functions.py def update_bullets(bullets): """更新子弹的位置,并删除已消失的子弹""" # 更新子弹的位置 bullets.update() # 删除已消失的子弹 for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) update_bullets()的代码是从alien_invasion.py剪切并粘贴而来的,它只需要一个参数,即编组bullets。 alien_invasion.py中的while循环又变得很简单了: alien_invasion.py # 开始游戏主循环 while True:  gf.check_events(ai_settings, screen, ship, bullets)  ship.update()  gf.update_bullets(bullets)  gf.update_screen(ai_settings, screen, ship, bullets) 我们让主循环包含尽可能少的代码,这样只要看函数名就能迅速知道游戏中发生的情况。主循环检查玩家的输入(见),然后更新飞船的位置(见)和所有未消失的子弹的位置(见)。接下来,我们使用更新后的位置来绘制新屏幕(见)。 12.8.8 创建函数fire_bullet() 下面将发射子弹的代码移到一个独立的函数中,这样,在check_keydown_events()中只需使用一行代码来发射子弹,让elif代码块变得非常简单: game_functions.py def check_keydown_events(event, ai_settings, screen, ship, bullets): """响应按键""" --snip-- elif event.key == pygame.K_SPACE: fire_bullet(ai_settings, screen, ship, bullets) 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) 函数fire_bullet()只包含玩家按空格键时用于发射子弹的代码;在check_keydown_events()中,我们在玩家按空格键时调用fire_bullet()。 请再次运行alien_invasion.py,确认发射子弹时依然没有错误。 动手试一试 12-5 侧面射击:编写一个游戏,将一艘飞船放在屏幕左边,并允许玩家上下移动飞船。在玩家按空格键时,让飞船发射一颗在屏幕中向右穿行的子弹,并在子弹离开屏幕而消失后将其删除。

展开全文


推荐文章

猜你喜欢

附近的人在看

推荐阅读

拓展阅读

《Python编程:从入门到实践》其他试读目录

• 1.1 搭建编程环境
• 1.2 在不同操作系统中搭建Python编程环境
• 1.3 解决安装问题
• 1.4 从终端运行Python程序
• 1.5 小结
• 12.1 规划项目
• 12.2 安装Pygame
• 12.3 开始游戏项目
• 12.4 添加飞船图像
• 12.5 重构:模块game_functions
• 12.6 驾驶飞船
• 12.7 简单回顾
• 12.8 射击 [当前]
• 12.9 小结
  • 大家都在看
  • 小编推荐
  • 猜你喜欢
  •