第十三章——外星人
13.1 回顾项目
开发大型项目时,进入每一个开发阶段都要回顾开发计划。
- 研究现有代码,决定是否需要重构
- 屏幕左上角创建一个外星人,并指定合适的边距。
- 根据第一个外星人的边距和屏幕尺寸计算屏幕上可容纳多少个外星人。编写一个循环,创建一系列外星人填满了屏幕的上半部分。
- 让外星人群向两边和下方移动,直到外星人被全部击落,有外星人撞到飞船,或有外星人抵达屏幕底端。如果整群外星人都被击落,我们将再创建一群外星人。如果有外星人撞到了飞船或抵达屏幕底端,我们将销毁飞船并再创建一群外星人。
- 限制玩家可用的飞船数量,配给的飞船用完后,游戏结束。
添加一个结束游戏的快捷键Q:
在check_keydown_events()中添加下列代码,按下Q就退出游戏窗口,因为Q在最左上角,游戏误触概率较低,设计友好。注意这里需要在英文输入法时该功能才生效哦!
elif event.key == pygame.K_q:
sys.exit()
13.2 创建第一个外星人
在屏幕上放置外星人与放置飞船类似。每个外星人的行为都由Alien类控制,务必将选择的图
像文件保存到文件夹images中。
13.2.1 创建 Alien 类
Alien类主要包括的部分为加载图片、获取外接矩形确定初始位置、更新移动位置、在指定位置上绘制飞船。
blit()函数
其主要格式:blit(source,dest=None,special_flags=0)
将source参数指定的Surface对象绘制到该对象上。dest参数指定绘制的位置。dest的值可以是source的左上角坐标,如果传入一个rect对象给dest,那么blit()会使用它的左上角坐标。
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""表示单个外星人的类"""
def __init__(self, screen):
super().__init__()
self.screen = screen
# 加载图像并获取它的外接矩形
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)
注意:继承类写法
super().__init__()
13.2.2 创建 Alien 实例
在alien_invasion.py中创建一个Alien实例:
13.2.3 让外星人出现在屏幕上
在game function中的update_screen函数下绘制外星人
运行代码,效果图如下,成功显示出一个外星人
13.3 创建一群外星人
要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行外星人。我们将首
先计算外星人之间的水平间距,并创建一行外星人,再确定可用的垂直空间,并创建整群外星人。
13.3.1 确定一行可容纳多少个外星人
屏幕宽度存储在ai_settings.screen_width中,需要在屏幕两边留边距,将外星人的宽度设置为边距。有两个边距,因此可用于放置外星人的水平空间为屏幕宽度减去外星人宽度的两倍:
avaliable_space_x = ai_settings.width - (alien.width * 2)
外星人之间也需要间距,所有将间距设置为外星人图像的宽度,也就是一个外星人占据两个宽。
numbers_aliens_x = avaliable_space_x / (alien.width * 2)
注意: 在程序中执行计算时,一开始你无需确定公式是正确的,而可以尝试直接运行程序,看看结果是否符合预期。
13.3.2 创建多行外星人
首先在alien_invasion.py中创建一个名为aliens的空编组,用于存储全部外星人,再调用game_functions.py中创建外星人群的函数。
# 创建外星人编组
aliens = Group()
# 创建外星人群
gf.creat_fleet(ai_settings, screen, aliens)
# 开始游戏主循环
while True:
--snip—
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
我们还需要修改update_screen():
对编组调用draw()时, Pygame自动绘制编组的每个元素,绘制位置由元素的属性rect决定。
在这里, aliens.draw(screen)在屏幕上绘制编组中的每个外星人。
game_function.py
def update_screen(ai_setting, screen, ship, aliens, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
---snip---
aliens.draw(screen)
13.3.3 创建外星人群
下面是新函数create_fleet(),我们将它放在game_functions.py的末尾。我们还需要导入Alien类。
补充:range()函数
注意:函数定义的位置形参与调用函数的实参顺序不对应产生的错误。
creat_fleet()函数
def creat_fleet(screen, ai_setting, aliens):
"""创建外星人舰队"""
# 创建一个外星人,并计算一行可容纳多少个外星人
# 外星人间距为外星人宽度
alien = Alien(screen)
alien_width = alien.rect.width
available_space_x = ai_setting.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(screen)
alien.x = alien_width + 2 * alien_number * alien_width
alien.rect.x = alien.x
aliens.add(alien)
执行效果如下
13.3.4 重构 create_fleet()
game_functions.py
def creat_fleet(screen, ai_setting, aliens):
"""创建外星人舰队"""
# 创建一个外星人,并计算一行可容纳多少个外星人
# 外星人间距为外星人宽度
alien = Alien(screen)
number_aliens_x = get_number_aliens_x(ai_setting, alien.rect.width)
# 创建第一行外星人
for alien_number in range(number_aliens_x):
creat_alien(screen, aliens, alien_number)
def get_number_aliens_x(ai_setting, alien_width):
"""计算每行可以容纳多少个外星人"""
available_space_x = ai_setting.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def creat_alien(screen, aliens, alien_number):
"""创建一个外星人并将其放在当前行"""
alien = Alien(screen)
alien_width = alien.rect.width
alien.x = alien_width + 2 * alien_number * alien_width
alien.rect.x = alien.x
aliens.add(alien)
我们将计算可用水平空间的代码替换为对get_number_aliens_x()的调用,并删除了引用alien_width的代码行,因为现在这是在create_alien()中处理的。我们调用create_alien()。通过这样的重构,添加新行进而创建整群外星人将更容易。
13.3.5 添加行
计算可用垂直空间:将屏幕高度减去第一行外星人的上边距(外星人高度)、飞船的高度以及最初外星人群与飞船的距离(外星人高度的两倍):
available_space_y = ai_setting.screen_width - ship_height - 3 * alien.height
这将在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。
每行下方都要留出一定的空白区域,并将其设置为外星人的高度。
number_rows = available_space_y / (2 * alien_height)
game_function.py重构函数后的代码如下:
import sys
import pygame
from bullet import Bullet
from alien import Alien
def check_keydown_events(event, ai_setting, 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:
fire_bullet(ai_setting, screen, ship, bullets)
elif event.key == pygame.K_q:
sys.exit()
def fire_bullet(ai_setting, screen, ship, bullets):
"""如果没有达到限制,就发射子弹"""
if len(bullets) < ai_setting.bullets_allowed:
new_bullet = Bullet(ai_setting, screen, ship)
bullets.add(new_bullet)
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(ai_setting, screen, ship, bullets):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_setting, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
def update_screen(ai_setting, screen, ship, aliens, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
# 每次循环都重绘屏幕
screen.fill(ai_setting.bg_color)
# 在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
aliens.draw(screen)
# 让最近绘制的屏幕可见
pygame.display.flip()
def update_bullets(bullets):
"""更新并删除子弹"""
bullets.update()
# 删除已经消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
def creat_fleet(screen, ai_setting, aliens, ship):
"""创建外星人舰队"""
# 创建一个外星人,并计算一行可容纳多少个外星人
# 外星人间距为外星人宽度
alien = Alien(screen)
number_aliens_x = get_number_aliens_x(ai_setting, alien.rect.width)
number_rows = get_number_rows(ai_setting, ship.rect.height, alien.rect.height)
# 创建外星人群
for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
creat_alien(screen, aliens, alien_number, row_number)
def get_number_aliens_x(ai_setting, alien_width):
"""计算每行可以容纳多少个外星人"""
available_space_x = ai_setting.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def get_number_rows(ai_setting, ship_height, alien_height):
"""计算屏幕可以容纳多少行"""
available_space_y = (ai_setting.screen_height - ship_height - (3*alien_height))
number_rows = int(available_space_y/(2* alien_height))
return number_rows
def creat_alien(screen, aliens, alien_number, row_number):
"""创建一个外星人并将其放在当前行"""
alien = Alien(screen)
alien_width = alien.rect.width
alien_height = alien.rect.height
alien.x = alien_width + 2 * alien_number * alien_width
alien.y = alien_height + 2 * row_number * alien_height
alien.rect.y = alien.y
alien.rect.x = alien.x
aliens.add(alien)
主要修改的部分是,增加了get_number_rows函数,计算屏幕可以容纳多少行。在创建外星人的时候增加y坐标,y坐标的高度和可以容纳多少行相关。修改create_fleet函数,首先调用get_number_rows函数确定有多少行,然后遍历行数和列数创建对应的外星人。
效果图
13.3 动手试一试
13-1 星星
找一幅星星图像,并在屏幕上显示一系列整齐排列的星星。
首先创建一个星星类star.py
import pygame
from pygame.sprite import Sprite
class Star(Sprite):
"""单个星星的类"""
def __init__(self, screen):
super().__init__()
self.screen = screen
# 加载图像并设置初始位置
self.image = pygame.image.load('images/star.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)
在主程序中添加下面的代码
重构了主程序,主程序使用的是第十二章动手试一试12-1,蓝色背景窗口
import pygame
from pygame.sprite import Group
import star_gamefunction as sg
def blue_background():
"""创建一个背景为蓝色的Pygame窗口"""
# 初始化pygame、屏幕对象
pygame.init()
screen = pygame.display.set_mode((1200, 800))
pygame.display.set_caption('blue_background')
# 创建星星组
stars = Group()
while True:
sg.check_events()
sg.update_screen(screen)
blue_background()
编写星星显示的功能脚本:脚本包含可以创建多少行星星,多少列星星,创建每一颗星星并添加进group组,遍历创建星星群组,刷新屏幕(设置背景颜色,背景颜色填充,最新屏幕可见,绘制星星)(PS:这里我用的是alien的图形,我自己找的大小不合适,显示不出来)
import pygame
import sys
from star import Star
def get_rows(screen, star):
"""计算可以创建多少行星星"""
screen_rect = screen.get_rect()
available_space_x = screen_rect.width - (2*star.rect.width)
number_x = int(available_space_x / (2*star.rect.width))
return number_x
def get_lines(screen, star):
"""计算可以创建多少列星星"""
screen_rect = screen.get_rect()
available_space_y = screen_rect.height - (3*star.rect.height)
number_y = int(available_space_y / (2*star.rect.height))
return number_y
def creat_star(screen, number_x, number_y, stars):
"""创建星星"""
# 创建一个星星并将它放在当前行
star = Star(screen)
star.x = star.rect.width + 2 * number_x * star.rect.width
star.y = star.rect.height + 2 * number_y * star.rect.height
star.rect.x = star.x
star.rect.y = star.y
stars.add(star)
def creat_fleet(screen, stars):
"""创建星星群"""
star = Star(screen)
numberx = get_rows(screen, star)
numbery = get_lines(screen, star)
# 创建星星群
for line in range(numbery):
for row in range(numberx):
creat_star(screen, row, line, stars)
def update_screen(screen, stars):
"""更新屏幕"""
# 设置屏幕背景颜色
bg_color = (255, 255, 255)
# 每次循环都重绘屏幕
screen.fill(bg_color)
# 绘制星星
stars.draw(screen)
# 让最新的屏幕可视
pygame.display.flip()
def check_events():
"""按键响应事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
在主程序更新两个地方,在主程序循环之前就创建好星星群组,在主循环循环时,刷新屏幕。
因此最终的主程序代码如下
import pygame
from pygame.sprite import Group
import star_gamefunction as sg
def blue_background():
"""创建一个背景为蓝色的Pygame窗口"""
# 初始化pygame、屏幕对象
pygame.init()
screen = pygame.display.set_mode((1200, 800))
pygame.display.set_caption('blue_background')
# 创建星星组
stars = Group()
sg.creat_fleet(screen, stars)
while True:
sg.check_events()
sg.update_screen(screen, stars)
blue_background()
效果图如下
13-2 更逼真的星星
为让星星的分布更逼真,可随机地放置星星。本书前面说过,可像下面这样来生成随机数:
from random import randint
random_number = randint(-10,10)
上述代码返回一个-10 和 10 之间的随机整数。 在为完成练习 13-1 而编写的程序中,随机地调整每颗星星的位置。
随机的调整星星的位置方法修改绘制星星位置的函数,主要修改的代码是
在star_gamefunction.py函数导入模块
from random import randint
修改功能函数的绘制星星位置的代码
当然你也可以将星星群组生成的行列数进行随机选择,不这样会改变生成的总个数。下面的图是只修改了星星位置的效果图。
13.4 让外星人群移动
外星人群在屏幕上向右移动,撞到屏幕边缘后下移一定的距离,再沿相反的方向移动。外星人自上而下走S型。
13.4.1 向右移动外星人
首先,添加一个控制外星人速度的设置:
Setting.py
# 外星人设置
self.alien_speed_factor = 1
使用这个设置来实现update():
def update(self, ai_setting):
"""向右移动外星人"""
self.x += ai_setting.alien_speed_factor
self.rect.x = self.x
现在还需更新每个外星人的位置:上面的操作是告诉你怎么移动,每次移动1,下面的代码时调用这个移动的函数。
game_functions.py
def update_alien(aliens, ai_setting):
"""更新外星人群中所由外星人的位置"""
aliens.update(ai_setting)
在主循环过程中,不断调用外星人移动的函数。我们在更新子弹后再更新外星人的位置,因为稍后要检查是否有子弹撞到了外星人。现在运行这个游戏,会看到外星人群向右移,并逐渐在屏幕右边缘消失。
13.4.2 创建表示外星人移动方向的设置
创建让外星人撞到屏幕右边缘后向下移动、再向左移动的设置。实现这种行为的代码如下:
setting.py
# 外星人设置
self.alien_speed_factor = 1
self.fleet_drop_speed = 10
# fleet_direction -1表示左移,1表示右移
self.fleet_direction = 1
设置fleet_drop_speed指定了有外星人撞到屏幕边缘时,外星人群向下移动的速度。将这个速度与水平速度分开是有好处的,这样你就可以分别调整这两种速度了。
实现fleet_direction设置,鉴于只有两个可能的方向,我们使用值1和-1来表示,另外,鉴于向右移动时需要增大每个外星人的x坐标,而向左移动时需要减小每个外星人的x坐标。
13.4.3 检查外星人是否撞到了屏幕边缘
编写一个方法来检查是否有外星人撞到了屏幕边缘,还需修改update(),以让每个外星人都沿正确的方向移动:
alien.py
def check_edge(self):
"""检测外星人是否触及到了边缘,在边缘就返回True"""
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= 0:
return True
可对任何外星人调用新方法check_edges(),看是否位于屏幕左边缘或右边缘。
修改方法update(),将移动量设置为外星人速度和fleet_direction的乘积,让外星人向左或向右移。如果fleet_direction为1,x坐标增大,从而将外星人向右移;如果fleet_direction为-1,当前的x坐标减去alien_speed_factor,从而将外星人向左移。
alien.py
13.4.4 向下移动外星人群并改变移动方向
有外星人到达屏幕边缘时,需要将整群外星人下移,并改变它们的移动方向。
game_function.py
def change_fleet_direction(aliens, ai_setting):
"""将整群外星人下移,并改变它们的方向"""
for alien in aliens.sprites():
alien.rect.y += ai_setting.fleet_drop_speed
ai_setting.fleet_direction *= -1
def check_fleet_edges(aliens, ai_setting):
"""有外星人到达边缘时采取相应的措施"""
for alien in aliens.sprites():
if alien.check_edges():
change_fleet_direction(aliens, ai_setting)
break
def update_aliens(aliens, ai_setting):
"""更新外星人群中所有外星人的位置"""
check_fleet_edges(aliens, ai_setting)
aliens.update(ai_setting)
check_fleet_edges()函数,遍历外星人,如果它到达边缘,check_edges()返回的为True,那么调用change_fleet_direction()函数,遍历外星人,将所有外星人下移10,并将移动方向改变为当前的-1倍。(哇哇哇,真的很聪明!!!),然后在更新外星人位置的时候,调用触碰到边缘的函数,更新外星人群的位置。
效果如下
13.4 动手试一试
13-3 雨滴
寻找一幅雨滴图像,并创建一系列整齐排列的雨滴。让这些雨滴往下落,直到到达屏幕底端后消失。
13-4 连绵细雨
修改为完成练习 13-3 而编写的代码,使得一行雨滴消失在屏幕底端后,屏幕顶端又出现一行新雨滴,并开始往下落。
————未完待续————
13.5 射杀外星人
让子弹能够击落外星人,我们将使用sprite.groupcollide()检测两个编组的成员之间的碰撞。
13.5.1 检测子弹与外星人的碰撞
在更新子弹的位置后立即检测碰撞,方法sprite.groupcollide()将每颗子弹的rect同每个外星人的rect进行比较,并返回一个字典,其中包含发生了碰撞的子弹和外星人。
在这个字典中,每个键都是一颗子弹,而相应的值都是被击中的外星人。
补充碰撞函数groupcollide()的使用
Python中pygame模块pygame.sprite.groupcollision碰撞检测的详解与测试_pygame.sprite.groupcollide_万师兄的博客-CSDN博客
game_function.py在函数update_bullets()中,使用下面的代码来检查碰撞:
def update_bullets(bullets,aliens):
"""更新并删除子弹"""
----snip----
# 检查是否有子弹射中了外星人
# 如果是这样就删除该子弹和外星人
collision = pygame.sprite.groupcollide(bullets, aliens, True, True)
要模拟能够穿行到屏幕顶端的高能子弹——消灭它击中的每个外星人,可将第一个布尔实参设置为False,并让第二个布尔实参为True。这样被击中的外星人将消失,但所有的子弹都始终有效,直到抵达屏幕顶端后消失。
因为修改了update_bullets()所以更新主程序引用这个函数的代码:
gf.update_bullets(bullets, aliens)
此时运行这个游戏,被击中的外星人将消失。
13.5.2 为测试创建大子弹
尝试将bullet_width设置为300,并且让子弹在射杀之后保留,看看将所有外星人都射杀有多快!类似这样的修改可提高测试效率,还可能激发出如何赋予玩家更大威力的思想火花。(完成测试后,别忘了将设置恢复正常。)
setting.py
self.bullet_width = 300
game_function.py
collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)
运行效果如下:
13.5.3 生成新的外星人群
这个游戏的一个重要特点是外星人无穷无尽,一个外星人群被消灭后,又会出现一群外星人。要在外星人群被消灭后又显示一群外星人,首先需要检查编组aliens是否为空。如果为空,就调用create_fleet()。我们将在update_bullets()中执行这种检查,因为外星人都是在这里被消灭的:
game_function.py
def update_bullets(screen, ai_setting, ship, bullets, aliens):
"""更新并删除子弹"""
bullets.update()
# 删除已经消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# 检查是否有子弹射中了外星人
# 如果是这样就删除该子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
# 删除现有的子弹,并创建新外星人
bullets.empty()
creat_fleet(screen, ai_setting, aliens, ship)
更新主程序alien_invasion.py调用update_bullets()的代码:
gf.update_bullets(screen, ai_setting, ship, bullets, aliens)
13.5.4 提高子弹的速度
现在尝试在这个游戏中射杀外星人,可能发现子弹的速度比以前慢,这是因为在每次循环中,pygame做的事情多了,这项设置的最佳值取决于你的系统速度。提高子弹的速度,可调整bullet_speed_factor。
setting.py
self.bullet_speed_factor = 3
13.5.5 重构 update_bullets()
game_function.py
def update_bullets(screen, ai_setting, ship, bullets, aliens):
"""更新并删除子弹"""
bullets.update()
# 删除已经消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
check_bullet_alien_collisions(screen, ai_setting, ship, aliens, bullets)
def check_bullet_alien_collisions(screen, ai_setting, ship, aliens, bullets):
"""响应子弹和外星人的碰撞"""
# 检查是否有子弹射中了外星人
# 如果是这样就删除该子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
# 删除现有的子弹,并创建新外星人
bullets.empty()
creat_fleet(screen, ai_setting, aliens, ship)
13.5 动手试一试
13-5 抓球
创建一个游戏,在屏幕底端放置一个玩家可左右移动的角色。让一个球出现在屏幕顶端,且水平位置是随机的,并让这个球以固定的速度往下落。如果角色与球发生碰撞(表示将球抓住了),就让球消失。每当角色抓住球或球因抵达屏幕底端而消失后,都创建一个新球。
13.6 结束游戏
13.6.1 检测外星人和飞船碰撞
在更新每个外星人的位置后立即检测外星人和飞船之间的碰撞。
spritecollideany()函数
方法spritecollideany()接受两个实参:一个精灵和一个编组。它检查编组是否有成员与精灵发生了碰撞,并在找到与精灵发生了碰撞的成员后就停止遍历编组。如果没有发生碰撞spritecollideany()将返回None,if代码块不会执行。如果找到了与飞船发生碰撞的外星人,它就返回这个外星人,因此if代码块将执行:打印“Ship hit!!!”
game_function.py
def update_aliens(ship, aliens, ai_setting):
"""更新外星人群中所有外星人的位置"""
check_fleet_edges(aliens, ai_setting)
aliens.update(ai_setting)
# 检测外星人和飞船之间的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
print("Ship hit!!!")
更新alien_invasion.py
gf.update_aliens(ship, aliens, ai_setting)
运行程序验证这个碰撞函数是否起作用,是否在控制台输出"ship hit"
其实当外星人碰到飞船的时候需要进行的操作有:
①清除屏幕上的飞船和外星人
②将飞船置于屏幕底端中央
③创建一组新的外星人
在进行这些操作之前需要先验证飞船和外星人碰撞之后是否会发生响应,验证方法就是在控制台输出“Ship hit!!!”
13.6.2 响应外星人和飞船碰撞
通过跟踪游戏的统计信息来记录飞船被撞了多少次(跟踪统计信息还有助于记分)。编写一个用于跟踪游戏统计信 息的新类 ——GameStats , 并将其保存为文件game_stats.py:
game_stats.py
class GameState():
"""跟踪游戏的统计信息"""
def __init__(self, ai_setting):
"""初始化统计信息"""
self.ai_setting = ai_setting
self.reset_stats()
def reset_stats(self):
"""初始化在游戏运行期间可能变化的统计信息"""
self.ships_left = self.ai_setting.ship_limit
但每当玩家开始新游戏时,需要重置一些统计信息。为此,我们在方法reset_stats()中初始化大部分统计信息,而不是在__init__()中直接初始化它们,只是在__init__时调用它。
将玩家拥有的飞船数存储在setting.py中
# 设置飞船数量
self.ship_limit = 3
对alien_invasion.py做些修改,以创建一个GameStats实例:
当外星人碰到飞船的时候需要进行的操作有:
①清除屏幕上的飞船和外星人
②将飞船置于屏幕底端中央
③创建一组新的外星人
将实现这些功能的大部分代码放到函数ship_hit()中:
补充:暂停的方法
导入类:from time import sleep
调用: # 暂停0.5s sleep(0.5)
game_function.py
from time import sleep
def ship_hit(stats, aliens, bullets, screen, ai_setting, ship):
"""响应被外星人撞到的飞船"""
# 将ship_left数量减一
stats.ships_left -= 1
# 清空外星人列表和子弹列表
aliens.empty()
bullets.empty()
# 创建一群新的外星人并将飞船放在屏幕底端中央
creat_fleet(screen, ai_setting, aliens, ship)
ship.center_ship()
# 暂停0.5s
sleep(0.5)
更新所有元素后(但在将修改显示到屏幕前)暂停,让玩家知道其飞船被撞到了。屏幕将暂时停止变化,让玩家能够看到外星人撞到了飞船。函数sleep()执行完毕后,执行update_screen(),将新的外星人群绘制到屏幕上。
更新update_aliens()处碰撞后的响应事件。
def update_aliens(ship, aliens, ai_setting, stats, bullets, screen):
"""更新外星人群中所有外星人的位置"""
check_fleet_edges(aliens, ai_setting)
aliens.update(ai_setting)
# 检测外星人和飞船之间的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(stats, aliens, bullets, screen, ai_setting, ship)
在Ship类中添加方法center_ship()
def center_ship(self):
"""让飞船在屏幕上居中"""
self.center = self.screen_rect.centerx
注意
根本没有创建多艘飞船,在整个游戏运行期间,我们都只创建了一个飞船实例,并在该飞船被撞到时将其居中。统计信息ships_left让我们知道飞船是否用完。
更新alien_invasion的update_aliens()的形参。
update_aliens(ship, aliens, ai_setting, stats, bullets, screen)
13.6.4 游戏结束
现在这个游戏看起来更完整了,但它永远都不会结束,只是ships_left不断变成更小的负数。在GameStats中添加一个作为标志的属性game_active,以便在玩家的飞船用完后结束游戏:
game_stats.py
def __init__(self, ai_setting):
"""初始化统计信息"""
self.ai_setting = ai_setting
self.reset_stats()
# 游戏刚启动处于活动状态
self.game_active = True
在ship_hit()中添加代码,在玩家的飞船都用完后将game_active设置为False:
game_functions.py
13.7 确定应运行游戏的哪些部分
需要确定游戏的哪些部分在任何情况下都应运行,哪些部分仅在游戏处于活动状态时才运行:
alien_invasion.py
# 开始游戏的主循环
while True:
gf.check_events(ai_setting, screen, ship, bullets)
if stats.game_active:
ship.update()
gf.update_bullets(screen, ai_setting, ship, bullets, aliens)
gf.update_aliens(ship, aliens, ai_setting, stats, bullets, screen)
gf.update_screen(ai_setting, screen, ship, aliens, bullets)
在主循环中,在任何情况下都需要调用check_events(),其他的函数仅在游戏处于活动状态时才需要调用,因为游戏处于非活动状态时,我们不用更新游戏元素的位置。运行这个游戏时,它将在飞船用完后停止不动。
13.7 动手试一试
13-6 游戏结束
在为完成练习 13-5 而编写的代码中,跟踪玩家有多少次未将球接着。在未接着球的次数到达三次后,结束游戏。
13.8 小结
创建了外星人,根据屏幕尺寸大小显示一定数量的外星人,实现外星人的移动和边缘检测,通过碰撞检测实现了外星人的射杀,实现碰撞之后的响应事件,最后限制飞船的数量以结束游戏。
如何在游戏中添加大量相同的元素,如创建一群外星人;
如何使用嵌套循环来创建元素网格
通过调用每个元素的方法update()移动了大量的元素;
如何控制对象在屏幕上移动的方向,以及如何响应事件,如有外星人到达屏幕边缘;如何检测和响应子弹和外星人碰撞以及外星人和飞船碰撞;
如何在游戏中跟踪统计信息,以及如何使用标志game_active来判断游戏是否结束了。