1.添加Play按钮:Play按钮在开始游戏前出现,结束游戏后再次出现。需要使游戏一开始处于非活动状态,在game_stats.py中修改game_active为False。
新建button模块,创建Button类,用于创建带标签的实心矩形。该模块导入了pygame.font模块将文本渲染到屏幕上,Button类中pygame.font.SysFont()可指定文本的字体和字号,font.render()方法可以使文本渲染成图像,该方法传递四个实参:字符串文本信息,布尔实参,文本颜色和背景色。布尔实参指定开启或关闭反锯齿功能(反锯齿让文本的边缘更光滑),若不指定背景颜色,Pygame将以透明背景的方式渲染文本。screen.fill()绘制按钮矩形,screen.blit()绘制文本图像。
game.functions模块中新建函数start_game()和check_play_button(),并修改了check_keydown_events()、check_events()、update_screen()和ship_hit()。上述修改是为了实现Play按钮显示在屏幕上,游戏未开始时鼠标点击Play按钮或者按下P键可以开始游戏,并重置游戏。游戏结束后Play按钮重新出现。游戏为活跃状态时隐藏光标。
alien_invasion模块创建Button实例,并增加check_events()和update_screen()的实参。
game_stats.py 代码如下:
# -*- coding: GBK -*-
class GameStats():
'''跟踪游戏的统计信息'''
def __init__(self, ai_settings):
'''初始化统计信息'''
self.ai_settings = ai_settings
self.reset_stats()
# 让游戏一开始处于非活动状态
self.game_active = False
def reset_stats(self):
'''初始化在游戏运行期间可能变化的统计信息'''
self.ship_left = self.ai_settings.ship_limit
button.py 代码如下:
# -*- coding: GBK -*-
import pygame.font #用于将文本渲染到屏幕上
class Button():
def __init__(self, ai_settings, screen, msg):
'''初始化按钮的属性'''
self.screen = screen
self.screen_rect = self.screen.get_rect()
# 设置按钮的尺寸和其他属性
self.width, self.height = 200, 50
self.button_color = (0, 255, 0) #按钮颜色
self.text_color = (255, 255, 255) #字体颜色
self.font = pygame.font.SysFont(None, 48) #选择字体渲染文本,实参None表示使用默认字体,后一个实参表示字号
# 创建按钮的rect对象,并使其居中
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 按钮的标签只需创建一次
self.prep_msg(msg) #调用prep_msg()方法使字符串信息渲染为图像
def prep_msg(self, msg):
'''将msg渲染为图像,并使其在按钮上居中'''
self.msg_image = self.font.render(msg, True, self.text_color,
self.button_color) #调用font.render()将msg中的文本转换为图像,布尔实参指定开启或关闭反锯齿功能(反锯齿让文本的边缘更平滑),后两个实参分别为文本颜色和背景色
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
# 绘制一个用颜色填充的按钮,再绘制文本
self.screen.fill(self.button_color, self.rect) #screen.fill()绘制按钮
self.screen.blit(self.msg_image, self.msg_image_rect) #绘制文本图像
game_functions.py 代码如下:
# -*- coding: GBK -*-
import sys # 使用模块sys来退出游戏。
import pygame
from bullet import Bullet
from alien import Alien # 导入alien模块Alien类,控制每个外星人的行为
from time import sleep # sleep()函数可用来让游戏暂停
def check_keydown_events(event, ai_settings, screen, stats, ship, aliens, 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_settings, screen, ship, bullets)
elif event.key == pygame.K_q: # 字母Q键
sys.exit()
elif event.key == pygame.K_p: # 游戏处于非活跃状态时按P键可以开始游戏
if not stats.game_active:
start_game(ai_settings, screen, stats, ship, aliens, 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)
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_settings, screen, stats, play_button, ship, aliens, bullets):
'''响应按键和鼠标事件'''
# 监视键盘和鼠标事件
for event in pygame.event.get(): #Pygame检测到的时间
if event.type == pygame.QUIT: #玩家单击窗口关闭按钮,则检测到pygame.QUIT事件。
sys.exit() #退出游戏
elif event.type == pygame.KEYDOWN: #根据按住键修改移动标志的值
check_keydown_events(event, ai_settings, screen, stats, ship, aliens, bullets)
elif event.type == pygame.KEYUP: #根据松开键修改移动标志的值
check_keyup_events(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos() #get_pos()返回一个元祖,包含点击时鼠标的x和y坐标
check_play_button(ai_settings, screen, stats, play_button, ship,
aliens, bullets, mouse_x, mouse_y)
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens,
bullets, mouse_x, mouse_y):
'''在玩家单击Play按钮时开始新游戏'''
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) #cllidepoint()检查鼠标单击位置是否在Play按钮的rect内
if button_clicked and not stats.game_active: #仅在游戏非活跃状态时点击Play按钮区域才可以重新开始游戏,防止游戏时误点Play按钮区域导致重新开始
start_game(ai_settings, screen, stats, ship, aliens, bullets)
def start_game(ai_settings, screen, stats, ship, aliens, bullets):
'''在按Q键或者鼠标点击Play按钮时开始游戏'''
# 隐藏光标
pygame.mouse.set_visible(False)
# 重置游戏统计信息
stats.reset_stats()
stats.game_active = True
#清空外星人列表和子弹列表
aliens.empty()
bullets.empty()
#创建一群新的外星人,并让飞船居中
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
def update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button):
'''更新屏幕上的图像,并切换到新屏幕'''
# 每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color) #screen.fill()只接受一个实参:一种颜色
# 在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites(): #方法bullets.sprites()返回一个列表,其中包含编组bullets中的所有精灵
bullet.draw_bullet()
ship.blitme() #在指定位置绘制飞船
aliens.draw(screen) #对编组调用draw(),Pygame自动绘制编组的每个元素,绘制的位置由元素的属性rect决定。此处在屏幕上绘制编组中每个外星人。
#如果游戏处于非活跃状态,就绘制Play按钮
if not stats.game_active:
play_button.draw_button()
# 让最近绘制的屏幕可见
pygame.display.flip() #不断更新屏幕,以显示元素的新位置
def update_bullets(ai_settings, screen, ship, aliens, bullets):
'''更新子弹的位置,并删除已消失子弹'''
# 更新每一粒子弹的位置
bullets.update() #对编组调用update(),编组自动对其中每个精灵调用update(),即为每一粒子弹调用bullet.update()
# 删除已消失子弹
for bullet in bullets.copy(): # 遍历编组的副本
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# print(len(bullets)) # 核实已消失子弹确实删除了
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, True, True) #每当子弹和外星人的rect重叠时,Pygame删除发生碰撞的子弹和外星人
if len(aliens) == 0:
# 若外星人群被消灭干净,删除现有的子弹并新建一群外星人
bullets.empty() #方法empty()可删除编组中剩余所有精灵
create_fleet(ai_settings, screen, ship, aliens)
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 get_number_rows(ai_settings, ship_height, alien_height):
available_space_y = (ai_settings.screen_height -
(3 * alien_height) - ship_height) #此处减去的2个alien_height是为了在飞船与外星人之间间隔一定空白,给玩家留出射杀外星人的时间
number_rows = int(available_space_y / (2 * alien_height))
return number_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 #确定生成外星人的x坐标
alien.rect.x = alien.x
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number #确定生成外星人的y坐标
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): #此处alien_number从0开始数起,range()也需要整数
create_alien(ai_settings, screen, aliens, alien_number, row_number)
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_drop_speed
ai_settings.fleet_direction *= -1
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
'''响应被外星人撞到的飞船'''
if stats.ship_left > 0:
# 讲ships_left减1
stats.ship_left -= 1
# 清空外星人列表和子弹列表
aliens.empty()
bullets.empty()
# 创建一群新的外星人,并将飞船放到屏幕底部*
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
# 暂停
sleep(0.5)
else:
stats.game_active = False
pygame.mouse.set_visible(True) # 游戏不活跃状态后会显示Play按钮,并显示光标
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() #对编组调用update(),自动对每个外星人调用方法update()
# 检测外星人和飞船之间的碰撞
if pygame.sprite.spritecollideany(ship, aliens): #接受两个实参,检查编组是否有成员与精灵发生了碰撞,若有停止遍历编组,返回发生碰撞的那个外星人,否则返回None
ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
# 检查是否有外星人到达屏幕底端
check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)
alien_invasion.py 执行代码如下:
# -*- coding: GBK -*-
import pygame # 模块pygame包含开发游戏所需的功能。
from settings import Settings # 导入设置模块的Setting类
from ship import Ship # 导入ship模块的Ship类,它负责管理飞船的大部分行为。
import game_functions as gf # 导入game_functions模块
from pygame.sprite import Group #用于创建子弹编组
from game_stats import GameStats #导入跟踪游戏统计信息的类GameStats
from button import Button #导入Button类,用于创建带标签的实心矩形
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init() #初始化背景设置,让Pygame能够正确地工作。
ai_settings = Settings() #创建Settings实例,并使用它来访问设置
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height)) #调用pygame.display.set_mode()来创建一个窗口,实参是一个元祖,指定游戏窗口尺寸。
pygame.display.set_caption("Alien Invasion") #设置窗口的标题
# 创建Play按钮,创建Button实例
play_button = Button(ai_settings, screen, "Play")
# 创建一个用于存储游戏统计信息的实例
stats = GameStats(ai_settings)
# 创建一艘飞船
ship = Ship(ai_settings, screen)
# 创建一个用于存储子弹的编组
bullets = Group()
# 创建一个外星人编组
aliens = Group()
# 创建外星人群
gf.create_fleet(ai_settings, screen, ship, aliens)
# 开始游戏的主循环
while True:
# 响应按键和鼠标事件
gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, 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, stats, ship, aliens, bullets, play_button)
run_game()