12.8 射击
下面来添加射击功能。我们将编写在玩家按空格键时发射子弹(用小矩形表示)的代码。子弹将在屏幕中向上飞行,抵达屏幕上边缘后消失。
12.8.1 添加子弹设置
首先,更新settings.py,在方法init() 末尾存储新类Bullet 所需的值:
settings.py
def init(self):
—snip—
# 子弹设置
self.bullet_speed = 1.0
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 pygameBullet 类继承了从模块pygame.sprite 导入的Sprite 类。通过使用精灵(sprite),可将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,init() 需要当前的AlienInvasion 实例,我们还调用了super() 来继承Sprite 。另外,我们还定义了用于存储屏幕以及设置对象和子弹颜色的属性。 在❶处,创建子弹的属性rect 。子弹并非基于图像,因此必须使用pygame.Rect() 类从头开始创建一个矩形。创建这个类的实例时,必须提供矩形左上角的
from pygame.sprite import Sprite
class Bullet(Sprite):
"""管理飞船所发射子弹的类"""
def init(self, aigame):
"""在飞船当前位置创建一个子弹对象。"""
super()._init()
self.screen = ai_game.screen
self.settings = ai_game.settings
self.color = self.settings.bullet_color
#在(0,0)处创建一个表示子弹的矩形,再设置正确的位置。
❶ self.rect = pygame.Rect(0, 0, self.settings.bullet_width,
self.settings.bullet_height)
❷ self.rect.midtop = ai_game.ship.rect.midtop
# 存储用小数表示的子弹位置。
❸ self.y = float(self.rect.y)



def update(self):方法update() 管理子弹的位置。发射出去后,子弹向上移动,意味着其
"""向上移动子弹。"""
#更新表示子弹位置的小数值。
❶ self.y -= self.settings.bulletspeed
# 更新表示子弹的rect的位置。
❷ self.rect.y = self.y
def drawbullet(self):
"""在屏幕上绘制子弹。"""
❸ pygame.draw.rect(self.screen, self.color, self.rect)


def __init(self):然后在while 循环中更新子弹的位置: alien_invasion.py
—snip—
self.ship = Ship(self)
self.bullets = pygame.sprite.Group()
def run_game(self):对编组调用update() 时(见❶),编组自动对其中的每个精灵调用update() 。因此代码行bullets.update() 将为编组bullets 中的每颗子弹调用bullet.update() 。 12.8.4 开火 在AlienInvasion 中,需要修改checkkeydown_events() ,以便在玩家按空格键时发射一颗子弹。无须修改checkkeyup_events() ,因为玩家松开空格键时什么都不会发生。还需要修改updatescreen() ,确保在调用flip() 前在屏幕上重绘每颗子弹。 为发射子弹,需要做的工作不少,因此编写一个新方法firebullet() 来完成这项任务: alien_invasion.py
"""开始游戏主循环。"""
while True:
self.checkevents()
self.ship.update()
❶ self.bullets.update()
self.updatescreen()
—snip—首先导入Bullet 类(见❶),再在玩家按空格键时调用firebullet() (见❷)。在firebullet() 中,创建一个Bullet 实例并将其赋给new_bullet (见❸),再使用方法add() 将其加入编组bullets 中(见❹)。方法add() 类似于append() ,不过是专门为Pygame编组编写的。 方法bullets.sprites() 返回一个列表,其中包含编组bullets 中的所有精灵。为在屏幕上绘制发射的所有子弹,遍历编组bullets 中的精灵,并对每个精灵调用draw_bullet() (见❺)。 如果此时运行alien_invasion.py,将能够左右移动飞船,并发射任意数量的子弹。子弹在屏幕上向上飞行,抵达屏幕顶部后消失得无影无踪,如图12-3所示。你可在settings.py中修改子弹的尺寸、颜色和速度。
from ship import Ship
❶ from bullet import Bullet
class AlienInvasion:
—snip—
def checkkeydown_events(self, event):
—snip—
elif event.key == pygame.K_q:
sys.exit()
❷ elif event.key == pygame.K_SPACE:
self.firebullet()
def checkkeyup_events(self, event):
—snip—
def firebullet(self):
"""创建一颗子弹,并将其加入编组bullets中。"""
❸ new_bullet = Bullet(self)
❹ self.bullets.add(new_bullet)
def updatescreen(self):
"""更新屏幕上的图像,并切换到新屏幕。"""
self.screen.fill(self.settings.bg_color)
self.ship.blitme()
❺ for bullet in self.bullets.sprites():
bullet.draw_bullet()
pygame.display.flip()
—snip—


def run_game(self):使用for 循环遍历列表(或Pygame编组)时,Python要求该列表的长度在整个循环中保持不变。因为不能从for 循环遍历的列表或编组中删除元素,所以必须遍历编组的副本。我们使用方法copy() 来设置for 循环(见❶),从而能够在循环中修改bullets 。我们检查每颗子弹,看看它是否从屏幕顶端消失(见❷)。如果是,就将其从bullets 中删除(见❸)。在❹处,使用函数调用print() 显示当前还有多少颗子弹,以核实确实删除了消失的子弹。 如果这些代码没有问题,我们发射子弹后查看终端窗口时,将发现随着子弹一颗颗地在屏幕顶端消失,子弹数将逐渐降为零。运行该游戏并确认子弹被正确删除后,请将这个函数调用print() 删除。如果不删除,游戏的速度将大大降低,因为将输出写入终端花费的时间比将图形绘制到游戏窗口花费的时间还要多。 12.8.6 限制子弹数量 很多射击游戏对可同时出现在屏幕上的子弹数量进行了限制,以鼓励玩家有目标地射击。下面在游戏《外星人入侵》中做这样的限制。 首先,在settings.py中存储最大子弹数: settings.py
"""开始游戏主循环。"""
while True:
self.checkevents()
self.ship.update()
self.bullets.update()
# 删除消失的子弹。
❶ for bullet in self.bullets.copy():
❷ if bullet.rect.bottom <= 0:
❸ self.bullets.remove(bullet)
❹ print(len(self.bullets))
self.updatescreen()
# 子弹设置这将未消失的子弹数限制为三颗。在AlienInvasion 的firebullet() 中,在创建新子弹前检查未消失的子弹数是否小于该设置: alien_invasion.py
—snip—
self.bullet_color = (60, 60, 60)
self.bullets_allowed = 3
def firebullet(self):玩家按空格键时,我们检查bullets 的长度。如果len(bullets) 小于3,就创建一颗新子弹;但如果有三颗未消失的子弹,则玩家按空格键时什么都不会发生。如果现在运行这个游戏,屏幕上最多只能有三颗子弹。 12.8.7 创建方法updatebullets()
"""创建新子弹并将其加入编组bullets中。"""
if len(self.bullets) < self.settings.bullets_allowed:
new_bullet = Bullet(self)
self.bullets.add(new_bullet)
编写并检查子弹管理代码后,可将其移到一个独立的方法中,确保AlienInvasion 类组织有序。为此,创建一个名为updatebullets() 的新方法,并将其放在updatescreen() 前面:
alien_invasion.py
def updatebullets(self):
"""更新子弹的位置并删除消失的子弹。"""
# 更新子弹的位置。
self.bullets.update()
# 删除消失的子弹。
for bullet in self.bullets.copy():
if bullet.rect.bottom <= 0:
self.bullets.remove(bullet)
updatebullets() 的代码是从run_game() 剪切并粘贴而来的,这里只是让注释更清晰了。
run_game() 中的while 循环又变得简单了:
alien_invasion.py
while True:
self.checkevents()
self.ship.update()
self.updatebullets()
self.updatescreen()
我们让主循环包含尽可能少的代码,这样只要看方法名就能迅速知道游戏中发生的情况。主循环检查玩家的输入,并更新飞船的位置和所有未消失子弹的位置。然后,使用更新后的位置来绘制新屏幕。
请再次运行alien_invasion.py,确认发射子弹时没有错误。
动手试一试
练习12-6:侧面射击 编写一个游戏,将一艘飞船放在屏幕左侧,并允许玩家上下移动飞船。在玩家按空格键时,让飞船发射一颗在屏幕中向右飞行的子弹,并在子弹从屏幕中消失后将其删除。