心情影响了我学习的兴致,昨天旷学了,希望改正!
13.1 回顾项目
本章涉及以下内容:1、研究既有代码,确定实现新功能前是否要进行重构。2、早屏幕左上角添加一个外星人,并指定合适的边距。3、根据第一个外星人的边距和屏幕的尺寸计算屏幕可以容纳多少个外星人。编写一系列循环来创建一系列外星人,使它们充满屏幕的上半部分。4、让外星人群向两边和下方移动,知道外星人被全部击落,有外星人撞到飞船,或有外星人抵达屏幕低端,我们将销毁飞船并再创建一群外星人。5、限制玩家可用的飞船数量,配给的飞船用完后,游戏结束。
13.2 创建第一个外星人
13.2.1 创建Alien
import pygame from pygame.sprite import Sprite class Alien(Sprite): """表示单个外星人的类""" def __init__(self,ai_settings,screen): super().__init__() self.screen = screen self.ai_settings = ai_settings # 加载外星人图像,并设置其rect属性 self.image = pygame.image.load('images/alien.bmp') self.rect = self.image.get_rect() # 每个外星人最初都在屏幕左上角附近 self.rect.x = self.rect.width self.rect.y = self.rect.height # 存储外星人的精确位置 self.x = float(self.rect.x) def blitme(self): """在指定位置绘制外星人""" self.screen.blit(self.image,self.rect)
除位置不同外,这个类的大部分代码都与Ship类似。我们将每个外星人的左边编剧都设置为外星人的宽度,并将上边边距设置为外星人的高度。
13.2.2 创建Alien实例
# 创建一个外星人 alien = Alien(ai_settings,screen)
# 开始游戏的主循环
while True:
gf.check_events(ai_settings,screen,ship,bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings,screen,ship,alien,bullets)
run_game()
13.2.3 让外星人出现在屏幕上
为让外星人出现在屏幕上,我们在update_screen()中调用其方法blitme():
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()
我们先绘制飞船和子弹,再绘制外星人,让外星人在屏幕上位于最前面。第一个外星人正确地现身后,下面来编写绘制一群外星人的代码:
13.3 创建一群外星人
要绘制一群外星人,需要确定一行能容纳多少个外星人,我们将首先计算外星人之间水平距离,并创建一行外星人,再确定可用的垂直空间,并创建整群外星人。
13.3.1 确定一行可容纳多少个外星人
屏幕宽度存储在ai_settings.screen_width中,但需要在屏幕两边都留下一定的边距,把它设置为外星人的宽度。由于有两个边距,因此可用于防止外星人的水平空间为屏幕宽度减去外星人宽度的两倍:
available_space_x =ai_settings.screen_width - 2 * alien_width
我们还需要在外星人之间留出一定的空间,及外星人宽度。因此显示一个外星人所需要的水平空间为外星人宽度的两倍:
number_aliens_x = int(available_space_x / (2 * alien_width))
13.3.2 创建多行外星人
首先在alien_invasion.py中创建一个名为aliens的空编组,用于存储全部外星人,再调用game_functions.py中创建外星人群的函数:
import pygame from pygame.sprite import Group from settings import Settings from ship import Ship from alien import Alien import game_functions as gf def run_game(): # 初始化pygame、设置和屏幕对象 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() gf.update_bullets(bullets) gf.update_screen(ai_settings,screen,ship,aliens,bullets) run_game()
ship.blitme() aliens.draw(screen) # 让最近绘制的屏幕可见 pygame.display.flip()
对编组调用draw()时,pygame会自动绘制编组的每个元素,绘制位置由元素的属性rect决定。aliens.draw(screen)在屏幕上绘制编组中的每一个外星人。
13.3.3 创建外星人群
下面是新函数create_fleet(),我们将它放在game_functions.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 = int(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)
运行后发现,外星人在屏幕上稍微偏向了左边,这实际上是有好处的,因为我们将让外星人向右运动,触及屏幕边缘后稍微往下,然后往左移,以此类推。
13.3.4 重构create_fleet()
下面是create_fleet()和两个新函数,get_number_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 aliens.add(alien) def create_fleet(ai_settings,screen,aliens): """创建外星人群""" # 创建一个外星人,并计算一行可容纳多少个外星人 alien = Alien(ai_settings, screen) number_aliens_x = get_number_aliens_x(ai_settings,alien.rect.width) # 创建第一行外星人 for alien_number in range(number_aliens_x): create_alien(ai_settings,screen,aliens,alien_number)
13.3.5 添加行
要创建外星人群,需要计算屏幕可容纳多少行,并创建一行外星人的循环重复相应的次数。为计算可容纳的行数,我们这样计算可用垂直空间:将屏幕高度减去第一行外星人的上边距(外星人高度)、飞船的高度以及最初外星人高度加上外星人间距(外星人高度的两倍)
available_space_y = (ai_settings.screen_height -(3 * alien_height - ship_height))
这将在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。
为计算可容纳的行数,我们将可用垂直空间除以外星人高度的两倍。
numbers_rows = int(available_space_y / (2 * alien_height))
知道可容纳多少行之后,便可重复创建一行外星人代码:
def get_number_rows(ai_settings,ship_height,alien_height): """计算屏幕可容纳多少行外星人""" available_space_y = (ai_settings.screen_height - (3 * alien_height - ship_height)) numbers_rows = int(available_space_y / (2 * alien_height)) return numbers_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)
在create_fleet()定义中,还新增了一个用于存储ship对象的形参,因此在alien_invasion.py调用create_fleet()时,需要传递实参ship。
13.4 让外星人移动
13.4.1 向右移动外星人
为移动外星人,使用update()。首先,在settings中添加一个控制外星人速度的设置。
def update(self): """向右移动外星人""" self.x += self.ai_settings.alien_speed_factor self.rect.x = self.x
在主while循环中调用更新外星人的位置:
while True: gf.check_events(ai_settings,screen,ship,bullets) ship.update() gf.update_bullets(bullets) gf.update_aliens(aliens) gf.update_screen(ai_settings,screen,ship,aliens,bullets)
在更新子弹后再更新外星人的位置,因为稍后要检查是否有子弹撞到了外星人。
最后,在文件game_functions.py末尾添加新函数update_aliens():
def update_aliens(aliens): """更新外星人群中所有外星人的位置""" aliens.update()
13.4.2 创建表示外星人移动方向的设置
下面来床架让外星人撞到屏幕右边缘后向下移动、再向左移动的设置。实现这种行为的代码如下:
# 外星人设置 self.alien_speed_factor = 1 self.fleet_drop_speed = 10 # fleet_direction 为1表示向右,为-1表示向左 self.fleet_direction = 1
设置fleet_drop_speed制定了有外星人撞到屏幕边缘时,外星人向下移动的速度。将这个速度与水平速度分开,就可以调整两种速度了。
要实现fleet_direction设置,可以将其设置为文本值,如'lef‘或‘right’,但这样就必须编写if-elif语句来检查外星人群移动的方向。鉴于只有两种方向,我们使用1和-1来表示它们,并在外星人群改变方向时在这两个值之间切换。另外,鉴于向右移动需要增大每个外星人的x坐标,而向左移动时,需要减小每个外星人的x坐标,使用数字来表示方向更合理。
13.4.3 检查外星人是否撞到了屏幕边缘
现在需要编写一个方法来检查是否有外星人撞到了屏幕边缘,还需要修改update(),以让每个外星人都沿正确的方向移动:
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 def update(self): """向左或右移动外星人""" self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction) self.rect.x = self.x
13.4.4 向下移动外星人群并改变移动方向
我们需要对game_function.py做重大修改,因为我们要在这里检查是否有外星人达到了左边缘或右边缘。为此我们编写函数check_fleet_edges()和change_fleet_direction(),并对update_aliens()进行修改:
def check_fleet_edges(ai_settings,aliens): """有外星人到达边缘时采取相应的措施""" for alien in aliens.sprites(): if alien.check_edges(): change_fleet_direction(ai_settings,aliens) break def change_fleet_direction(ai_settings, aliens): """将整群外星人下移,并改变它们的方向""" for alien in aliens.sprites(): alien.rect.y += ai_settings.fleet_dorp_speed ai_settings.fleet_direction *= -1 def update_aliens(ai_settings,aliens): """ 检查是否有外星人位于屏幕边缘,并更新整群外星人的位置 """ check_fleet_edges(ai_settings,aliens) aliens.update()
我们修改了函数update_aliens(),在其中通过调用check_fleet_edges()来确定是否有外星人位于屏幕边缘。现在,函数update_alien()包含形参ai_settings,因此我们调用它时指定了与ai_settings对应的实参:
while True: gf.check_events(ai_settings,screen,ship,bullets) ship.update() gf.update_bullets(bullets) gf.update_aliens(ai_settings,aliens) gf.update_screen(ai_settings,screen,ship,aliens,bullets)
13.5 射杀外星人
在游戏编程中,碰撞指的是游戏元素重叠在一起。要让子弹能够击落外星人,我们将使用sprites.groupcollide()检测两个编组的成员之间的碰撞。
13.5.1 检测子弹与外星人的碰撞
方法sprites.groupcollide()将每颗子弹的rect同每个外星人的rect进行比较,并返回一个字典,其中包含发生了碰撞的子弹和外星人。在这个字典中,每个键都是子弹,而相应的值都是被击中的外星人。
# 检查是否有子弹击中了外星人 # 如果是这样,就删除相应的子弹和外星人 collisions = pygame.sprite.groupcollide(bullets,aliens,True,True)
新增的这行代码遍历编组bullets中的每颗子弹,再遍历编组aliens中的每个外星人。每当有子弹和外星人的rect重叠时,groupcollide()就在它返回的字典中添加一个键-值对。两个实参True告诉Pygame删除发生碰撞的子弹和外星人。(要模拟能够穿行屏幕顶端的高能子弹——消灭它击中的每个外星人,可将第一个布尔实参设置为False,并让第二个布尔实参为True。这样被击中的外星人 消失,但所有的子弹始终有效,知道抵达屏幕顶端后消失。)
调用update_bullets()时,传递实参aliens。
13.5.3 生成新的外星人群
首先要检查aliens是否为空,如果为空就调用create_fleet()。我们将在check_bullets()中执行这种检查,因为外星人都是在这里被消灭的:
if len(aliens) ==0: # 删除现有的子弹并新建一群外星人 bullets.empty() create_fleet(ai_settings,screen,ship,aliens
检查aliens是否为空,是,就删除编组中余下的所有精灵,从而删除现有的所有子弹。再更新alien_invason.py中对update_bullets()的调用。
13.5.4 提高子弹的速度
13.5.5 重构 update_bullets()
# 删除已消失的子弹 for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) check_bullet_alien_collisions(ai_settings,screen,ship,aliens,bullets) def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets): # 检查是否有子弹击中了外星人 # 如果是这样,就删除相应的子弹和外星人 collisions = pygame.sprite.groupcollide(bullets, aliens, False, True) if len(aliens) == 0: # 删除现有的子弹并新建一群外星人 bullets.empty() create_fleet(ai_settings, screen, ship, aliens)
13.6 结束游戏
13.6.1 检测外星人和飞船的碰撞
def update_aliens(ai_settings,ship,aliens): """ 检查是否有外星人位于屏幕边缘,并更新整群外星人的位置 """ check_fleet_edges(ai_settings,aliens) aliens.update() #检测外星人和飞船之间的碰撞 if pygame.sprite.spritecollideany(ship,aliens): print("Ship hit!!!")
13.6.2 响应外星人和飞船碰撞
通过游戏的统计信息来记录飞船被撞了多少次。下面来编写一个用于跟踪游戏统计信息的新类GameStats,并将其保存为文件game_stats.py:
class GameStats(): """跟踪游戏的统计信息""" def __init__(self,ai_settings): """初始化统计信息""" self.ai_settings = ai_settings self.reset_stats() def reset_stats(self): """初始化在游戏运行期间可能变化的统计信息""" self.ships_left = self.ai_settings.ship_limit
一开始玩家拥有的飞船数量存储在settings.py的ship_limit中:self.ship_limit=3
我们哈需要对alien_invasion().py做些修改,以创建一个GamesStats实例:
# 创建一个用于存储游戏统计信息的实例 stats = GameStats(ai_settings) while True: gf.check_events(ai_settings,screen,ship,bullets) ship.update() gf.update_bullets(ai_settings,screen,ship,aliens,bullets) gf.update_aliens(ai_settings,stats,screen,ship,aliens,bullets) gf.update_screen(ai_settings,screen,ship,aliens,bullets)
有外星人撞到飞船时,我们将余下的飞船数减1,创建新的外星人,并将飞船重置到屏幕低端中央(我们还将游戏暂停一段时间,让玩家在新外星人群出现前注意到碰撞)
下面将实现这些功能的大部分代码放到函数ship_hit()中:
def ship_hit(ai_settings,stats,screen,ship,aliens,bullets): """响应被外星人撞到的飞船""" # 将ship_left减1 stats.ship_left -= 1 #清空外星人列表和子弹列表 aliens.empty() bullets.empty() # 创建一群新的外星人,并将飞船放到屏幕低端中央 create_fleet(ai_settings,screen,ship,aliens) ship.center_ship() # 暂停 sleep(0.5) def update_aliens(ai_settings,stats,screen,ship,aliens,bullets): """ 检查是否有外星人位于屏幕边缘,并更新整群外星人的位置 """ check_fleet_edges(ai_settings,aliens) aliens.update() #检测外星人和飞船之间的碰撞 if pygame.sprite.spritecollideany(ship,aliens): ship_hit(ai_settings,stats,screen,ship,aliens,bullets)
下面是新方法center_ship(),请将其添加到ship.py的末尾:
def center_ship(self): """让飞船在屏幕上居中""" self.center = self.screen_rect.centerx
13.6.3 有外星人到达屏幕低端
添加一个新函数,check_aliens_bottom():
def check_aliens_bottom(ai_settings,stats,screen,ship,aliens,bullets): """检查是否有外星人到达了屏幕底端""" screen_rect = screen.get_rect() for alien in aliens.sprites(): if alien.rect.bottom >= screen_rect.bottom: # 像飞船被撞到一样处理 ship_hit(ai_settings,stats,screen,ship,aliens,bullets) break def update_aliens(ai_settings,stats,screen,ship,aliens,bullets): """ 检查是否有外星人位于屏幕边缘,并更新整群外星人的位置 """ check_fleet_edges(ai_settings,aliens) aliens.update() #检测外星人和飞船之间的碰撞 if pygame.sprite.spritecollideany(ship,aliens): ship_hit(ai_settings,stats,screen,ship,aliens,bullets) # 检查是否有外星人到达屏幕底端 check_aliens_bottom(ai_settings,stats,screen,ship,aliens,bullets)
13.6.4 游戏结束
在GameStats中添加一个标志属性game_active,在ship_hit()中添加代码。
def ship_hit(ai_settings,stats,screen,ship,aliens,bullets): """响应被外星人撞到的飞船""" if stats.ships_left > 0: # 将ship_left减1 stats.ships_left -= 1 #清空外星人列表和子弹列表 aliens.empty() bullets.empty() # 创建一群新的外星人,并将飞船放到屏幕底端中央 create_fleet(ai_settings,screen,ship,aliens) ship.center_ship() # 暂停 sleep(0.5) else: stats.game_active = False
13.7 确定应运行游戏的哪些部分
while True: gf.check_events(ai_settings,screen,ship,bullets) if stats.game_active: ship.update() gf.update_bullets(ai_settings,screen,ship,aliens,bullets) gf.update_aliens(ai_settings,stats,screen,ship,aliens,bullets) gf.update_screen(ai_settings,screen,ship,aliens,bullets)