下面来让玩家能够左右移动飞船。为此,我们将编写代码,在用户按左或右箭头键时作出响应。我们将首先专注于向右移动,再使用同样的原理来控制向左移动。通过这样做,你将学会如何控制屏幕图像的移动。 12.6.1 响应按键 每当用户按键时,都将在Pygame中注册一个事件。事件都是通过方法pygame.event.get()获取的,因此在函数check_events()中,我们需要指定要检查哪些类型的事件。每次按键都被注册为一个KEYDOWN事件。 检测到KEYDOWN事件时,我们需要检查按下的是否是特定的键。例如,如果按下的是右箭头键,我们就增大飞船的rect.centerx值,将飞船向右移动: game_ functions.py def check_events(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.rect.centerx += 1 我们在函数check_events()中包含形参ship,因为玩家按右箭头键时,需要将飞船向右移动。在函数check_events()内部,我们在事件循环中添加了一个elif代码块,以便在Pygame 检测到KEYDOWN事件时作出响应(见)。我们读取属性event.key,以检查按下的是否是右箭头键(pygame.K_RIGHT)(见)。如果按下的是右箭头键,就将ship.rect.centerx的值加1,从而将飞船向右移动(见)。 在alien_invasion.py中,我们需要更新调用的check_events()代码,将ship作为实参传递给它: alien_invasion.py # 开始游戏主循环 while True: gf.check_events(ship) gf.update_screen(ai_settings, screen, ship) 如果现在运行alien_invasion.py,则每按右箭头键一次,飞船都将向右移动1像素。这是一个开端,但并非控制飞船的高效方式。下面来改进控制方式,允许持续移动。 12.6.2 允许不断移动 玩家按住右箭头键不放时,我们希望飞船不断地向右移动,直到玩家松开为止。我们将让游戏检测pygame.KEYUP事件,以便玩家松开右箭头键时我们能够知道这一点;然后,我们将结合使用KEYDOWN和KEYUP事件,以及一个名为moving_right的标志来实现持续移动。 飞船不动时,标志moving_right将为False。玩家按下右箭头键时,我们将这个标志设置为True;而玩家松开时,我们将这个标志重新设置为False。 飞船的属性都由Ship类控制,因此我们将给这个类添加一个名为moving_right的属性和一个名为update()的方法。方法update()检查标志moving_right的状态,如果这个标志为True,就调整飞船的位置。每当需要调整飞船的位置时,我们都调用这个方法。 下面是对Ship类所做的修改: ship.py class Ship(): def __init__(self, screen): --snip-- # 将每艘新飞船放在屏幕底部中央 self.rect.centerx = self.screen_rect.centerx self.rect.bottom = self.screen_rect.bottom # 移动标志 self.moving_right = False def update(self): """根据移动标志调整飞船的位置""" if self.moving_right: self.rect.centerx += 1 def blitme(self): --snip-- 在方法__init__()中,我们添加了属性self.moving_right,并将其初始值设置为False(见)。接下来,我们添加了方法update(),它在前述标志为True时向右移动飞船(见)。 下面来修改check_events(),使其在玩家按下右箭头键时将moving_right设置为True,并在玩家松开时将moving_right设置为False: game_functions.py def check_events(ship): """响应按键和鼠标事件""" for event in pygame.event.get(): --snip-- elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: ship.moving_right = False 在处,我们修改了游戏在玩家按下右箭头键时响应的方式:不直接调整飞船的位置,而只是将moving_right设置为True。在处,我们添加了一个新的elif代码块,用于响应KEYUP事件:玩家松开右箭头键(K_RIGHT)时,我们将moving_right设置为False。 最后,我们需要修改alien_invasion.py中的while循环,以便每次执行循环时都调用飞船的方法update(): alien_invasion.py # 开始游戏主循环 while True: gf.check_events(ship) ship.update() gf.update_screen(ai_settings, screen, ship) 飞船的位置将在检测到键盘事件后(但在更新屏幕前)更新。这样,玩家输入时,飞船的位置将更新,从而确保使用更新后的位置将飞船绘制到屏幕上。 如果你现在运行alien_invasion.py并按住右箭头键,飞船将不断地向右移动,直到你松开为止。 12.6.3 左右移动 飞船能够不断地向右移动后,添加向左移动的逻辑很容易。我们将再次修改Ship类和函数check_events()。下面显示了对Ship类的方法__init__()和update()所做的相关修改: ship.py def __init__(self, screen): --snip-- # 移动标志 self.moving_right = False self.moving_left = False def update(self): """根据移动标志调整飞船的位置""" if self.moving_right: self.rect.centerx += 1 if self.moving_left: self.rect.centerx -= 1 在方法__init__()中,我们添加了标志self.moving_left;在方法update()中,我们添加了一个if代码块而不是elif代码块,这样如果玩家同时按下了左右箭头键,将先增大飞船的rect.centerx值,再降低这个值,即飞船的位置保持不变。如果使用一个elif代码块来处理向左移动的情况,右箭头键将始终处于优先地位。从向左移动切换到向右移动时,玩家可能同时按住左右箭头键,在这种情况下,前面的做法让移动更准确。 我们还需对check_events()作两方面的调整: game_functions.py def check_events(ship): """响应按键和鼠标事件""" for event in pygame.event.get(): --snip-- elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False 如果因玩家按下K_LEFT键而触发了KEYDOWN事件,我们就将moving_left设置为True;如果因玩家松开K_LEFT而触发了KEYUP事件,我们就将moving_left设置为False。这里之所以可以使用elif代码块,是因为每个事件都只与一个键相关联;如果玩家同时按下了左右箭头键,将检测到两个不同的事件。 如果此时运行alien_invasion.py,将能够不断地左右移动飞船;如果你同时按左右箭头键,飞船将纹丝不动。 下面来进一步优化飞船的移动方式:调整飞船的速度;限制飞船的移动距离,以免它移到屏幕外面去。 12.6.4 调整飞船的速度 当前,每次执行while循环时,飞船最多移动1像素,但我们可以在Settings类中添加属性ship_speed_factor,用于控制飞船的速度。我们将根据这个属性决定飞船在每次循环时最多移动多少距离。下面演示了如何在settings.py中添加这个新属性: settings.py class Settings(): """一个存储游戏《外星人入侵》的所有设置的类""" def __init__(self): --snip-- # 飞船的设置 self.ship_speed_factor = 1.5 我们将ship_speed_factor的初始值设置成了1.5。需要移动飞船时,我们将移动1.5像素而不是1像素。 通过将速度设置指定为小数值,可在后面加快游戏的节奏时更细致地控制飞船的速度。然而,rect的centerx等属性只能存储整数值,因此我们需要对Ship类做些修改: ship.py class Ship(): def __init__(self, ai_settings, screen): """初始化飞船并设置其初始位置""" self.screen = screen self.ai_settings = ai_settings --snip-- # 将每艘新飞船放在屏幕底部中央 --snip-- # 在飞船的属性center中存储小数值 self.center = float(self.rect.centerx) # 移动标志 self.moving_right = False self.moving_left = False def update(self): """根据移动标志调整飞船的位置""" # 更新飞船的center值,而不是rect if self.moving_right: self.center += self.ai_settings.ship_speed_factor if self.moving_left: self.center -= self.ai_settings.ship_speed_factor # 根据self.center更新rect对象 self.rect.centerx = self.center def blitme(self): --snip-- 在处,我们在__init__()的形参列表中添加了ai_settings,让飞船能够获取其速度设置。接下来,我们将形参ai_settings的值存储在一个属性中,以便能够在update()中使用它(见)。鉴于现在调整飞船的位置时,将增加或减去一个单位为像素的小数值,因此需要将位置存储在一个能够存储小数值的变量中。可以使用小数来设置rect的属性,但rect将只存储这个值的整数部分。为准确地存储飞船的位置,我们定义了一个可存储小数值的新属性self.center(见)。我们使用函数float()将self.rect.centerx的值转换为小数,并将结果存储到self.center中。 现在在update()中调整飞船的位置时,将self.center的值增加或减去ai_settings.ship_ speed_factor的值(见)。更新self.center后,我们再根据它来更新控制飞船位置的self.rect.centerx(见)。self.rect.centerx将只存储self.center的整数部分,但对显示飞船而言,这问题不大。 在alien_invasion.py中创建Ship实例时,需要传入实参ai_settings: alien_invasion.py --snip-- def run_game(): --snip-- # 创建飞船 ship = Ship(ai_settings, screen) --snip-- 现在,只要ship_speed_factor的值大于1,飞船的移动速度就会比以前更快。这有助于让飞船的反应速度足够快,能够将外星人射下来,还让我们能够随着游戏的进行加快游戏的节奏。 12.6.5 限制飞船的活动范围 当前,如果玩家按住箭头键的时间足够长,飞船将移到屏幕外面,消失得无影无踪。下面来修复这种问题,让飞船到达屏幕边缘后停止移动。为此,我们将修改Ship类的方法update(): ship.py def update(self): """根据移动标志调整飞船的位置""" # 更新飞船的center值,而不是rect if self.moving_right and self.rect.right < self.screen_rect.right: self.center += self.ai_settings.ship_speed_factor if self.moving_left and self.rect.left > 0: self.center -= self.ai_settings.ship_speed_factor # 根据self.center更新rect对象 self.rect.centerx = self.center 上述代码在修改self.center的值之前检查飞船的位置。self.rect.right返回飞船外接矩形的右边缘的x坐标,如果这个值小于self.screen_rect.right的值,就说明飞船未触及屏幕右边缘(见)。左边缘的情况与此类似:如果rect的左边缘的x坐标大于零,就说明飞船未触及屏幕左边缘(见)。这确保仅当飞船在屏幕内时,才调整self.center的值。 如果此时运行alien_invasion.py,飞船将在触及屏幕左边缘或右边缘后停止移动。 12.6.6 重构check_events() 随着游戏开发的进行,函数check_events()将越来越长,我们将其部分代码放在两个函数中:一个处理KEYDOWN事件,另一个处理KEYUP事件: game_functions.py 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) 我们创建了两个新函数:check_keydown_events()和check_keyup_events(),它们都包含形参event和ship。这两个函数的代码是从check_events()中复制而来的,因此我们将函数check_events中相应的代码替换成了对这两个函数的调用。现在,函数check_events()更简单,代码结构更清晰。这样,在其中响应其他玩家输入时将更容易。
Python编程:从入门到实践——12.6 驾驶飞船
书名: Python编程:从入门到实践
作者: [美] 埃里克·马瑟斯
出版社: 人民邮电出版社
原作名: Python Crash Course
副标题: 从入门到实践
译者: 袁国忠
出版年: 2016-7-1
页数: 459
定价: CNY 89.00
装帧: 平装
ISBN: 9787115428028