文章目录

  • 项目描述
  • Pygame 安装
    • Windows 平台下安装
    • Linux 平台安装
  • 开始编写项目
    • 创建 pygame 窗口
    • 设置背景色
    • 创建设置类
  • 添加飞船图像
    • 创建 Ship 类
    • 在屏幕上绘制飞船
  • 重构:模块 game_func
    • 函数 check_events()
    • 函数 update_screen()
  • 操作飞船
    • 响应按键
    • 允许不断移动
    • 调整飞船的速度
    • 限制飞船活动范围
    • 重构 check_events()
  • 射击
    • 添加子弹设置
    • 创建 Bullet 类
    • 将子弹存储到编组
    • 开火
    • 删除已消失的子弹
    • 限制子弹的数量
    • 创建函数 update_bullets()
    • 创建函数 fire_bullet()
    • 添加程序退出快捷键响应
  • 创建第一个外星人
    • 创建 Alien 实例
  • 创建一群外星人
    • 确定一行可容纳多少个外星人
    • 创建多行外星人
    • 重构 create_fleet()
    • 添加行
  • 让外星人移动
    • 向右移动外星人
    • 创建表示外星人移动方向的设置
    • 添加外星人是否撞到边缘的判断
    • 实现向下移动并改变方向
  • 射杀外星人
    • 检测子弹与外星人的碰撞
    • 为测试方便添加大招子弹
    • 生成新的外星人群
    • 提高子弹速度
    • 重构 update_bullets()
  • 结束游戏
    • 检测外星人和飞船碰撞
    • 响应外星人和飞船碰撞
  • 确定运行游戏的哪些部分
  • 添加 Play 按钮
    • 创建 Button 类
    • 在屏幕上绘制按钮
    • 开始游戏
    • 重置游戏
    • 将 Play 按钮切换到非活动状态
    • 隐藏光标
  • 提高等级
    • 修改速度设置
    • 重置速度
  • 记分
    • 创建得分显示类
    • 创建记分牌
    • 消灭外星人后更新得分
    • 将消灭的每个外星人的点数都计入得分
    • 提高点数
    • 得分圆整
    • 添加最高得分
    • 显示等级
    • 显示余下的飞船数量
  • 总结

项目描述

首先描述一下要实验的这个《飞机大战》游戏。

在游戏中,玩家控制这一艘最初出现在屏幕最底部中央的飞船。玩家可以使用 箭头键 左右移动非常,还可以使用 空格键 进行射击。游戏开始时,一群外星人出现在天空中,他们在屏幕中向下移动。玩家的任务就是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一批新的外星人,他们移动速度更快。这个过程中,只要外星人撞到玩家的飞船或者到达了屏幕底部,玩家就会损失一艘飞船。玩家在损失三艘飞船后,游戏结束。

Pygame 安装

Windows 平台下安装

首先下载安装包,下载地址,下载 whl 文件,使用 pip 安装。

PS F:\Pythone\实例代码\项目(飞机大战)> pip install pygame-1.9.6-cp39-cp39-win_amd64.whl
ERROR: pygame-1.9.6-cp39-cp39-win_amd64.whl is not a supported wheel on this platform.
WARNING: You are using pip version 19.2.3, however version 20.1.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
PS F:\Pythone\实例代码\项目(飞机大战)> pip install pygame-1.9.6-cp39-cp39-win_amd64.whl
ERROR: pygame-1.9.6-cp39-cp39-win_amd64.whl is not a supported wheel on this platform.
WARNING: You are using pip version 19.2.3, however version 20.1.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
PS F:\Pythone\实例代码\项目(飞机大战)> python -m pip install --upgrade pip
Collecting pipDownloading https://files.pythonhosted.org/packages/43/84/23ed6a1796480a6f1a2d38f2802901d078266bda38388954d01d3f2e821d/pip-20.1.1-py2.py3-none-any.whl (1.5MB)|████████████████████████████████| 1.5MB 21kB/s
Installing collected packages: pipFound existing installation: pip 19.2.3Uninstalling pip-19.2.3:Successfully uninstalled pip-19.2.3
Successfully installed pip-20.1.1
PS F:\Pythone\实例代码\项目(飞机大战)> pip install pygame-1.9.6-cp39-cp39-win_amd64.whl
ERROR: pygame-1.9.6-cp39-cp39-win_amd64.whl is not a supported wheel on this platform.
PS F:\Pythone\实例代码\项目(飞机大战)> pip3 install pygame-1.9.6-cp39-cp39-win_amd64.whl
ERROR: pygame-1.9.6-cp39-cp39-win_amd64.whl is not a supported wheel on this platform.
PS F:\Pythone\实例代码\项目(飞机大战)>

首先遇到 pip 工具更新问题,就行了安装更新。然后又报出来安装的这个库版本不匹配,我安装的 Python 版本为 3.8 ,这里发现库上写的 cp39 表示支持 Python 版本为 3.9 , 所以又下载了新的 3.8 的库,继续尝试安装。

终端运行 Python 查看是否可以正常导入,显示如下结果表示正常。

PS F:\Pythone\实例代码\项目(飞机大战)> python
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import pygame
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
>>>

Linux 平台安装

如果使用的是 Python2.7 ,则可以使用包管理器来安装 pygame。打开终端,执行下面命令:

$ sudo apt-get install python-pygame

如果使用的是 Python3 ,就需要执行两个步骤:

  • 安装 pygame 依赖的库
  • 下载并安装 pygame
$ sudo apt-get install python3.8-dev mercurial
$ sudo apt-get install libsdl-image1.2-dev libsdl2-dev libsd1-ttf2.0-dev
$ sudo apt-get install libsdl-mixer1.2-dev libportmidi-dev
$ sudo apt-get install libswscale-dev libsmpeg-dev libavformat-dev libavcode-dev
$ sudo apt-get install python-numpy$ pip install --user hg+http://bitbucket.org/pygame/pygame

开始编写项目

开始编写项目,首先创建一个空的 pygame 窗口,供后面用来绘制游戏元素,如飞船和外星人。此外还需要使得这个游戏可以响应用户输入、设置背景以及加载飞船图像。

创建 pygame 窗口

首先,创建一个空的 pygame 窗口,使用 pygame 编写的游戏的基本结构如下:

import sys
import pygamedef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()screen = pygame.display.set_mode((1200, 800))pygame.display.set_caption("Alien Invasion")# 开始游戏的主循环while True:# 监视键盘和鼠标事件for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()# 让最近绘制的屏幕可见pygame.display.flip()run_game()

执行结果如下:


上述代码中:

  • pygame.init() 初始化背景设置,调用 pygame.display.set_mode() 来创建一个名为 screen 的显示窗口。
  • 对象 screen 是一个 surface 。在 pygame 中,surface 是屏幕的一部分,用于显示游戏元素。在这个项目中,每个元素(外星人或者飞船)都是一个 surface 。每次循环后都会重新绘制 surface 。
  • 在 while 循环中,编写一个侦听事件,并根据发送的事件执行对应的任务。为访问 pygame 检测到的事件,使用方法 pygame.event.get() 。所有的键盘和鼠标事件都将促使这个 for 循环运行。
  • pygame.display.flip() 调用是命令 pygame 使得最近绘制的屏幕可见。每次循环到最后都会绘制一个空屏幕,并擦去旧屏幕,使得新画面可见。
  • 最后一行的 run_game() 函数的调用,将初始化游戏并开始主循环。

设置背景色

pygame 默认创建一个黑色屏幕。下面将背景设置为另一种颜色:

import sys
import pygamedef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()screen = pygame.display.set_mode((1200, 800))pygame.display.set_caption("Alien Invasion")# 设置背景色bg_color = (230, 0, 0)# 开始游戏的主循环while True:# 监视键盘和鼠标事件for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()# 每次循环都重新绘制屏幕screen.fill(bg_color)# 让最近绘制的屏幕可见pygame.display.flip()run_game()

执行结果如下:

在 pygame 中,颜色是以 RGB值指定的。上面代码中 bg_color = (230, 0, 0) 指定了一个红色。在主循环调用 screen.fill() 将使用背景色填充正个屏幕(这个方法只接受一个实参,一种颜色)。

创建设置类

编写一个名为 settings 的模块作为设置功能模块,其中包含一个名为 Settings 类,用于将所有设置存储在一个地方,以方便后续添加设置或者修改参数方便。

具体模块代码如下:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)

修改之前游戏框架代码,使用 Settings 类,如下:

import sys
import pygame
from settings import Settingsdef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))pygame.display.set_caption("Alien Invasion")# 设置背景色#bg_color = (230, 0, 0)# 开始游戏的主循环while True:# 监视键盘和鼠标事件for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()# 每次循环都重新绘制屏幕screen.fill(ai_settings.bg_color)# 让最近绘制的屏幕可见pygame.display.flip()run_game()

执行结果如下:

添加飞船图像

下面将飞船加入到游戏中。为了在屏幕上绘制玩家操作的飞船,我们将加载一幅,再使用 pygame 方法 blit() 绘制它。

再添加图片素材时需要注意版权问题,这里可以使用安全免费的网站,例如:飞船图片
在主目录下新建一个名为 images 文件夹,将飞船图片素材存放到文件夹内。如下图:

创建 Ship 类

选择用于表示飞船的图像后,需要将其显示到屏幕上,创建一个名为 ship 的模块,模块中包含一个 Ship 类,负责管理飞船的大部分行为。具体内容如下:

import pygameclass Ship():# 管理飞船部分行为def __init__(self, screen):# 初始化飞船并设置其初始位置self.screen = screen# 加载飞船图像并获取其外接矩形self.image = pygame.image.load("images/ship.png")self.rect = self.image.get_rect()self.screen_rect = screen.get_rect()# 将每艘新产生的飞船放在屏幕底部正中央self.rect.centerx = self.screen_rect.centerxself.rect.bottom = self.screen_rect.bottomdef blitme(self):# 在指定位置绘制飞船self.screen.blit(self.image, self.rect)
  • 使用 pygame.image.load() 加载图像,这个函数返回一个表示飞船的 surface, 这里将这个 surface 存储到 self.image
  • 加载图像后,使用 get_rect() 获取相应的 surface 的属性 rect 。pygame 的效率之所以如此高,一个原因是它让你能够像处理矩形(rect 对象)一样处理游戏元素,即便它们的形状并非矩形。像处理矩形一样处理游戏元素之所以高效,是因为矩形是简单的几何形状。这样做法的效果通常很好,游戏玩家几乎注意不到我们处理的不是游戏元素的实际形状。
  • 处理 rect 对象时,可使用矩形四角和中心的 x 和 y 坐标。可以通过设置这些值来指定矩形的位置。
  • 要将游戏元素居中,可设置相应的 rect 对象的属性 center 、 centerx 或 centery 。要使得游戏元素与屏幕边缘对齐,可使用属性 topbottomleftright
  • 将飞船放置到屏幕底部中央。首先将表示屏幕的矩形存储在 self.screen_rect 中,再将 self.rect.centerx 设置为表示屏幕的矩形的属性 centerx,并将 self.rect.bottom 设置为表示屏幕的矩形的属性 bottom 。pygame将使用这些 rect 属性来放置飞船图像,使其与屏幕下边缘对齐并水平居中。
  • 定义 blitme() ,根据 self.rect 指定的位置将图像绘制到屏幕上。

在屏幕上绘制飞船

更新前面编写的 alien_invasion.py ,使其创建一艘飞船,并调用其方法 blitme() 。具体代码修改如下:

import sys
import pygame
from settings import Settings
from ship import Shipdef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))pygame.display.set_caption("Alien Invasion")# 设置背景色#bg_color = (230, 0, 0)# 创建一艘飞船ship = Ship(screen)# 开始游戏的主循环while True:# 监视键盘和鼠标事件for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()# 每次循环都重新绘制屏幕screen.fill(ai_settings.bg_color)ship.blitme()# 让最近绘制的屏幕可见pygame.display.flip()run_game()

执行结果如下:

重构:模块 game_func

在大型的项目中,经常需要在添加新代码前重构已有的代码。重构的目的主要是为了简化已有的代码结构,使得其更容易扩展。这里,我们将创建一个名为 game_func 的新模块,存储大量让游戏《飞机大战》运行的函数,通过创建模块 game_func,可避免 alien_invasion.py 内容过多,并使得代码逻辑更加容易理解。

函数 check_events()

首先将管理事件的代码转移到名为 check_events() 的函数中,以简化 run_game() 并隔离事件管理循环。通过隔离事件循环,可以将事件管理与游戏的其他方面(如更新屏幕)分离。

创建一个 game_func 的新模块,具体内容如下:

import sys
import pygamedef check_events():# 响应按键和鼠标事件for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()

下面修改之前的 alien_invasion.py ,使得其导入模块 game_func ,并将事件循环修改为对 check_events() 的调用。

import sys
import pygame
from settings import Settings
from ship import Ship
import game_func as gfdef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))pygame.display.set_caption("Alien Invasion")# 设置背景色#bg_color = (230, 0, 0)# 创建一艘飞船ship = Ship(screen)# 开始游戏的主循环while True:# 监视键盘和鼠标事件#for event in pygame.event.get():#    if event.type == pygame.QUIT:#        sys.exit()gf.check_events()# 每次循环都重新绘制屏幕screen.fill(ai_settings.bg_color)ship.blitme()# 让最近绘制的屏幕可见pygame.display.flip()run_game()

函数 update_screen()

进一步简化 run_game() ,下面将更新屏幕的代码转移到 update_screen() 的函数中,并将这个函数放入 game_func 模块中。添加代码如下:

import sys
import pygamedef check_events():# 响应按键和鼠标事件for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()def update_screen(ai_settings, screen, ship):# 更新屏幕上的图像,并切换到新屏幕# 每次循环时都重新绘制屏幕screen.fill(ai_settings.bg_color)ship.blitme()# 让最近绘制的屏幕可见pygame.display.flip()

修改 alien_invasion.py代码内容,具体内容如下:

import sys
import pygame
from settings import Settings
from ship import Ship
import game_func as gfdef run_game():# 初始化游戏并创建一个屏幕对象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(screen)# 开始游戏的主循环while True:# 监视键盘和鼠标事件gf.check_events()# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship)run_game()

操作飞船

下面来让玩家能够左右移动飞船。为此,将编写代码在用户操作按键时能够作出响应。这里,首先专注于处理向右移动,并用同样的方法来处理向左移动。

响应按键

每当用户按键时,都将在 pygame 中注册一个事件。事件都是通过方法 pygame.event.get() 获取的,因此在函数 check_evets() 中,这里我们需要制定要检查哪些类型的事件。每次按键都被注册为一个 KEYDOWN 事件。
检测到 KEYDOWN 事件时,我们需要检查按下的是否是特定的键。例如,如果按下的是右箭头,那么就增大飞船的 rect.centerx 值,将飞船向右移动。具体如下:(game_func.py中修改)

import sys
import pygamedef check_events(settings, 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 += 50if ship.rect.centerx > settings.screen_width:ship.rect.centerx -= 50if event.key == pygame.K_LEFT:ship.rect.centerx -= 50if ship.rect.centerx < 0:ship.rect.centerx += 50def update_screen(ai_settings, screen, ship):# 更新屏幕上的图像,并切换到新屏幕# 每次循环时都重新绘制屏幕screen.fill(ai_settings.bg_color)ship.blitme()# 让最近绘制的屏幕可见pygame.display.flip()

并且修改在 alien_invasion.py 中的 check_events() 函数调用,如下:

import sys
import pygame
from settings import Settings
from ship import Ship
import game_func as gfdef run_game():# 初始化游戏并创建一个屏幕对象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(screen)# 开始游戏的主循环while True:# 监视键盘和鼠标事件gf.check_events(ai_settings, ship)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship)run_game()

执行如下:

移动到最右侧:
移动到最左侧:

允许不断移动

前面修改过后,按键一次飞船响应一次,但是长按不动却不会响应,这样很不方便。可以添加检测 pygame.KEYUP 事件,以方便玩家在玩家在松开按键时能够检测到事件,结合使用 KEYDOWNKEYUP 事件以及一个名为 moving_right 的标志来实现持续移动。

飞船不动时,标志 moving_right 设为 False 。当按下按键时设置为 True ,当松开时再次设置为 False

ship.py 模块修改如下:

import pygameclass Ship():# 管理飞船部分行为def __init__(self, screen):# 初始化飞船并设置其初始位置self.screen = screen# 加载飞船图像并获取其外接矩形self.image = pygame.image.load("images/ship.png")self.rect = self.image.get_rect()self.screen_rect = screen.get_rect()# 将每艘新产生的飞船放在屏幕底部正中央self.rect.centerx = self.screen_rect.centerxself.rect.bottom = self.screen_rect.bottom# 移动标志self.moving_right = Falseself.moving_left = Falsedef update(self):# 根据移动标志调整位置if self.moving_right:self.rect.centerx += 1if self.rect.centerx > self.screen_rect.width:self.rect.centerx -= 1if self.moving_left:self.rect.centerx -= 1if self.rect.centerx < 0:self.rect.centerx += 1def blitme(self):# 在指定位置绘制飞船self.screen.blit(self.image, self.rect)

修改 game_func 模块,具体代码如下:

import sys
import pygamedef check_events(settings, 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.moving_right = True#ship.rect.centerx += 50#if ship.rect.centerx > settings.screen_width:#    ship.rect.centerx -= 50if event.key == pygame.K_LEFT:ship.moving_left = True#ship.rect.centerx -= 50#if ship.rect.centerx < 0:#ship.rect.centerx += 50elif event.type == pygame.KEYUP:if event.key == pygame.K_RIGHT:ship.moving_right = Falseif event.key == pygame.K_LEFT:ship.moving_left = Falsedef update_screen(ai_settings, screen, ship):# 更新屏幕上的图像,并切换到新屏幕# 每次循环时都重新绘制屏幕screen.fill(ai_settings.bg_color)ship.blitme()# 让最近绘制的屏幕可见pygame.display.flip()

执行如下:

调整飞船的速度

当前,每次执行 while 循环时,飞船最多移动 1 个像素的位置,这个移动步长其实可以在 Settings 类中添加属性 ship_speed_factor 用于控制飞船的速度。这样就可以根据这个属性决定飞船每次循环时最多移动多少距离。具体代码如下:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)# 飞船的设置self.ship_speed_factor = 1.5

ship_speed_factor 的初始值设置成了 1.5 。需要移动飞船的时候,就会根据这个参数移动 1.5 而不是 1 。由于之前在飞船类中的 rect 的 centerx 等属性只能存储整数值,因此需要对 Ship 类做如下修改:

import pygameclass Ship():# 管理飞船部分行为def __init__(self, ai_settings, screen):# 初始化飞船并设置其初始位置self.screen = screenself.ai_settings = ai_settings# 加载飞船图像并获取其外接矩形self.image = pygame.image.load("images/ship.png")self.rect = self.image.get_rect()self.screen_rect = screen.get_rect()# 将每艘新产生的飞船放在屏幕底部正中央self.rect.centerx = self.screen_rect.centerxself.rect.bottom = self.screen_rect.bottom# 在飞船的属性center中存储小数值self.center = float(self.rect.centerx)# 移动标志self.moving_right = Falseself.moving_left = Falsedef update(self):# 根据移动标志调整位置# 更新飞船的 center值而不是rect值if self.moving_right:#self.rect.centerx += 1self.center += self.ai_settings.ship_speed_factorif self.center > self.screen_rect.width:self.center -= self.ai_settings.ship_speed_factorif self.moving_left:#self.rect.centerx -= 1self.center -= self.ai_settings.ship_speed_factorif self.center < 0:self.center += self.ai_settings.ship_speed_factorself.rect.centerx = self.centerdef blitme(self):# 在指定位置绘制飞船self.screen.blit(self.image, self.rect)

修改前面 alien_invasion.py 文件中对于 Ship() 构造函数的调用为:ship = Ship(ai_settings, screen)

限制飞船活动范围

前面虽然也限制了飞船活动范围,不过前面是简单判断了一下,如果当前增加或者减少后飞船会离开屏幕,那么就放弃这次操作,代码如下:

class Ship():def update(self):if self.moving_right:self.center += self.ai_settings.ship_speed_factorif self.center > self.screen_rect.width:self.center -= self.ai_settings.ship_speed_factorif self.moving_left:self.center -= self.ai_settings.ship_speed_factorif self.center < 0:self.center += self.ai_settings.ship_speed_factorself.rect.centerx = self.center

优化后的代码如下:

class Ship():def update(self):if self.moving_right and self.rect.right < self.screen_rect.right:self.center += self.ai_settings.ship_speed_factorif self.moving_left and self.rect.left > 0:self.center -= self.ai_settings.ship_speed_factorself.rect.centerx = self.center

这样修改后,在这里的无用操作更少,并且飞船距离边界更清楚。此时运行程序后,飞船在触及屏幕边界时会停止继续向外移动。

修改后,执行程序如下图所示:

重构 check_events()

随着开发工作的进行,函数 check_events() 将越来越长,所以可以考虑将其部分代码放在两个函数中:

  • 一个处理 KEYDOWN 事件;
  • 一个处理 KEYUP 事件;

如下:

def check_events(settings, 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.moving_right = True#ship.rect.centerx += 50#if ship.rect.centerx > settings.screen_width:#    ship.rect.centerx -= 50if event.key == pygame.K_LEFT:ship.moving_left = True#ship.rect.centerx -= 50#if ship.rect.centerx < 0:#ship.rect.centerx += 50elif event.type == pygame.KEYUP:if event.key == pygame.K_RIGHT:ship.moving_right = Falseif event.key == pygame.K_LEFT:ship.moving_left = False

修改后,将其中按键按下处理和按键松开处理封装成对应函数,如下:

def check_keydown_events(event, ship):# 响应按键按下if event.key == pygame.K_RIGHT:ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Truedef check_keyup_events(event, ship):# 响应按键松开if event.key == pygame.K_RIGHT:ship.moving_right = Falseelif event.key == pygame.K_LEFT:ship.moving_left = Falsedef 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)

射击

下面就开始添加射击功能。将编写玩家按下空格键时发射子弹(小矩形)的响应处理,子弹将在屏幕中向上穿行,抵达屏幕上边缘后消失。

添加子弹设置

首先,更新 settings.py ,在其方法 __init__() 末尾存储新类 Bullet 所需的值:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)# 飞船的设置self.ship_speed_factor = 1.5# 子弹设置self.bullet_speed_factor = 1self.bullet_width = 3self.bullet_height = 15self.bullet_color = 60, 60, 60

创建 Bullet 类

下面创建 bullet 模块,编写子弹运动的 Bullet 类,具体如下:

import pygame
from pygame.sprite import Spriteclass Bullet(Sprite):# 一个对飞船发射的子弹进行管理的类def __init__(self, ai_settings, screen, ship):# 在飞船所处的位置创建一个子弹对象super(Bullet, self).__init__()self.screen = screen# 在 (0,0)处创建一个表示子弹的矩形,在设置正确的位置self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height)self.rect.centerx = ship.rect.centerxself.rect.top = ship.rect.top# 存储用小数表示的子弹位置self.y = float(self.rect.y)self.color = ai_settings.bullet_colorself.speed_factor = ai_settings.bullet_speed_factordef update(self):# 向上移动子弹# 更新表示子弹的小数值self.y -= self.speed_factor# 更新表示子弹的rect位置self.rect.y = self.ydef draw_bullet(self):# 在屏幕上绘制子弹pygame.draw.rect(self.screen, self.color, self.rect)
  • Bullet 类继承了从模块 pygame.sprite 模块中导入的 Sprite 类,通过使用精灵,可以将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,需要想 __init__() 传递 ai_settingsscreenship 实例。
  • 方法 update() 管理子弹的位置,放射子弹后,子弹在屏幕中向上移动,这意味着 y 的值不断减小,因此更新子弹的位置就可以看到子弹在移动。
  • 方法 draw_bullet() 绘制子弹,使用存储在 self.color 中的颜色填充子弹的 rect 占据屏幕部分。

将子弹存储到编组

定义 Bullet 类后,就可以编写代码。首先,将 alien_invasion.py 中创建一个编组(group),用于存储所有高效的子弹,以便能够管理发射出去的所有子弹。

Group 类其实类似于一个列表,方便存储精灵。具体修改如下(alien_invasion.py):

import sys
import pygame
from settings import Settings
from ship import Ship
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象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()# 开始游戏的主循环while True:# 监视键盘和鼠标事件gf.check_events(ai_settings, screen, ship, bullets)ship.update()bullets.update()# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, bullets)run_game()

开火

game_func.py 中,需要修改 check_keydown_events() 代码,添加对空格键的响应操作,并且还需要修改 update_screen() ,以确保在调用 flip() 前在屏幕上已经完成了子弹重绘。具体修改如下:

def check_keydown_events(event, ai_settings, screen, ship, bullets):# 响应按键按下if event.key == pygame.K_RIGHT:ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Trueelif event.key == pygame.K_SPACE:# 创建一颗子弹,并将其加入编组bullets中new_bullet = Bullet(ai_settings, screen, ship)bullets.add(new_bullet)def update_screen(ai_settings, screen, ship, bullets):# 更新屏幕上的图像,并切换到新屏幕# 每次循环时都重新绘制屏幕screen.fill(ai_settings.bg_color)# 在飞船和外星人后面重新绘制子弹for bullet in bullets.sprites():bullet.draw_bullet()ship.blitme()# 让最近绘制的屏幕可见pygame.display.flip()

执行效果如下图所示:

删除已消失的子弹

上面的操作完成后,子弹到达顶端后看上去是消失了,实际上是因为超出边界没有显示,其实子弹依然存在,只不过它们的 y 坐标值为负数,这样积攒过多对程序消耗和处理都是负担。所以需要将这些已经消失的子弹进行删除,其实就是检测子弹坐标条件,在超出上边界后将其删除即可。具体代码如下( alien_invasion.py ):

import sys
import pygame
from settings import Settings
from ship import Ship
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象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()# 开始游戏的主循环while True:# 监视键盘和鼠标事件gf.check_events(ai_settings, screen, ship, bullets)ship.update()bullets.update()# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)print(len(bullets))# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, bullets)run_game()

执行结果如下,从图中可以看出在子弹到达上边界后,列表中的数量就减少了。

限制子弹的数量

很多射击游戏都对可同时出现在屏幕上的子弹数量进行了限制,以鼓励玩家有目标地设计,这里也添加上这部分的限制。

首先,在 settings.py 中存储子弹最大限制数量参数,如下:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)# 飞船的设置self.ship_speed_factor = 1.5# 子弹设置self.bullet_speed_factor = 1self.bullet_width = 3self.bullet_height = 15self.bullet_color = 60, 60, 60self.bullets_allowed = 5

这里将同时存在的子弹数量设置为 5 ,如何在 game_func.py 中的 check_keydown_events() 对空格的响应增加子弹个数前进行条件判断,具体如下:

def check_keydown_events(event, ai_settings, screen, ship, bullets):# 响应按键按下if event.key == pygame.K_RIGHT:ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Trueelif event.key == pygame.K_SPACE:# 创建一颗子弹,并将其加入编组bullets中# 判断当前子弹数量if len(bullets) < ai_settings.bullets_allowed:new_bullet = Bullet(ai_settings, screen, ship)bullets.add(new_bullet)

创建函数 update_bullets()

编写并检查子弹管理代码后,可以将这部分代码封装到 game_func模块中,使得主程序尽可能简单,这里创建一个名为 update_bullets() 的函数,添加到 game_func模块中,具体如下:

def update_bullets(bullets)# 更新子弹的位置bullets.update()# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)

如何修改 alien_invasion.py 模块中的 while 循环,如下:

import sys
import pygame
from settings import Settings
from ship import Ship
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象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()# 开始游戏的主循环while True:# 监视键盘和鼠标事件gf.check_events(ai_settings, screen, ship, bullets)ship.update()# 调用 update_bullets() 更新子弹gf.update_bullets(bullets)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, bullets)run_game()

创建函数 fire_bullet()

下面将发射子弹的代码转移到单独的函数中,这样在 check_keydown_events() 中只需要使用一行函数调用即可完成发射子弹操作。

具体如下( game_func.py ):

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_keydown_events(event, ai_settings, screen, ship, bullets):# 响应按键按下if event.key == pygame.K_RIGHT:ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Trueelif event.key == pygame.K_SPACE:# 创建一颗子弹,并将其加入编组bullets中fire_bullet(ai_settings, screen, ship, bullets)

添加程序退出快捷键响应

game_func.py 模块中添加对 Q 按键的退出响应,具体如下:

def check_keydown_events(event, ai_settings, screen, ship, bullets):# 响应按键按下if event.key == pygame.K_RIGHT:ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Trueelif event.key == pygame.K_SPACE:# 创建一颗子弹,并将其加入编组bullets中fire_bullet(ai_settings, screen, ship, bullets)elif event.key == pygame.K_q:# 退出程序sys.exit()

创建第一个外星人

在屏幕上放置敌方外星人和放置飞船类似,每个外星人的行为都由 Alien 类控制,我们将像创建 Ship 类那样创建这个类,简化考虑,使用位图表示外星人,下载外星人的图片文件,下载链接:外星人图片下载

将下载好的图片放置到 images 目录下,命名为 alien.png 如下图所示:

创建 alien.py 模块,具体代码如下:

import pygame
from pygame.sprite import Spriteclass Alien(Sprite):# 表示外星人的类def __init__(self, ai_settings, screen):# 初始化外星人并设置其初始位置super(Alien, self).__init__()self.screen = screenself.ai_settings = ai_settings# 加载外星人图像,并设置rect属性self.image = pygame.image.load('images/alien.png')self.rect = self.image.get_rect()# 每个外星人最初在屏幕左上角出现self.rect.x = self.rect.widthself.rect.y = self.rect.heightdef blitme(self):# 在指定位置绘制外星人self.screen.blit(self.image, self.rect)

创建 Alien 实例

下面在 alien_invasion.py 中创建一个 Alien 实例:

import sys
import pygame
from settings import Settings
from ship import Ship
from alien import Alien
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象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()# 创建一个外星人alien = Alien(ai_settings, screen)# 开始游戏的主循环while True:# 监视键盘和鼠标事件gf.check_events(ai_settings, screen, ship, bullets)ship.update()# 调用 update_bullets() 更新子弹gf.update_bullets(bullets)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, bullets)run_game()

并且修改其中调用函数 updaget_screen(),如下:

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()

运行之后,如下图:

创建一群外星人

要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行外星人。所以需要先计算外星人之间的水平间距,并创建一行外星人,再确定可以的垂直空间,并创建整群外星人。

确定一行可容纳多少个外星人

为了确定一行可容纳多少个外星人,了解可用的水平空间大小。屏幕宽度存储在 ai_settings.screen_width 中,但是两边还是需要留下一定的空袭,因此可以用放置外星人的水平控件定为屏幕宽度减去外星人宽度的两倍。

# 可用空间宽度
available_space_x = ai_settings.screen_width - (2 * alien_width)
# 单行可容纳外星人个数
number_aliens_x = available_space_x / (2 * alien_width)

创建多行外星人

为创建一行外星人,首先在 alien_ivasion.py 中创建一个名为 aliens 的空编组,用于存储全部外星人。再调用 game_func.py 模块中创建外星人群的函数。

game_func.py 模块中创建外星人群函数如下:


def create_fleet(ai_settings, screen, aliens):# 创建外星人群# 创建一个外星人,并计算一行可容纳多少个外星人# 外星人间距为外星人宽度alien = Alien(ai_settings, screen)alien_width = alien.rect.widthavailable_space_x = ai_settings.screen_width - 2 * alien_widthnumber_aliens_x = 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_numberalien.rect.x = alien.xaliens.add(alien)def update_screen(ai_settings, screen, ship, aliens, bullets):# 更新屏幕上的图像,并切换到新屏幕# 每次循环时都重新绘制屏幕screen.fill(ai_settings.bg_color)# 在飞船和外星人后面重新绘制子弹for bullet in bullets.sprites():bullet.draw_bullet()ship.blitme()aliens.draw(screen)# 让最近绘制的屏幕可见pygame.display.flip()

修改 alien_invasion.py 中的调用,具体如下:

import sys
import pygame
from settings import Settings
from ship import Ship
from alien import Alien
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象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()# 调用 update_bullets() 更新子弹gf.update_bullets(bullets)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, aliens, bullets)run_game()

执行如下图所示:

重构 create_fleet()

这时候能看出外星人都在屏幕上靠左边,这样就可以向右移动,触及屏幕边缘后下移再向左移动,依次类推。

下面是 create_fleet() 和新创建的两个函数 aliens_x()create_alien()

def get_number_aliens_x(ai_settings, alien_width):# 计算每行可容纳的外星人个数available_space_x = ai_settings.screen_width - 2 * alien_widthnumber_aliens_x = int(available_space_x / (2 * alien_width))return number_aliens_xdef create_alien(ai_settings, screen, aliens, alien_number):# 创建一个外星人并将其放在当前行alien = Alien(ai_settings, screen)alien_width = alien.rect.widthalien.x = alien_width + 2 * alien_width * alien_numberalien.rect.x = alien.xalien.add(alien)def create_fleet(ai_settings, screen, aliens):# 创建外星人群# 创建一个外星人,并计算一行可容纳多少个外星人# 外星人间距为外星人宽度alien = Alien(ai_settings, screen)number_aliens_x = get_number_aliens_x(ai_settings, alien_width)# 创建第一行外星人for alien_number in range(number_aliens_x):# 创建第一个外星人并加入当前行create_alien(ai_settings, screen, aliens, alien_number)

添加行

要创建外星人群,首先就需要计算屏幕可容纳多少行,并对创建一行外星人的循环重复相应的次数。为计算可容纳的行数,需要计算当前屏幕可用垂直空间:

  • 将屏幕高度减去第一行外星人的高度,飞船的高度以及最初外星人群与飞船的距离(外星人高度的两倍)
available_space_y = ai_settings.screen_height - 3 * alien_height - ship_height

这样计算将会在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。所以行数计算如下:

number_rows = available_height_y / (2 * alien_height)

知道可容纳多少行后,便可以重复执行创建一行外星人的代码( game_func.py ):


def get_number_rows(ai_settings, ship_height, alien_height):# 计算屏幕可容纳多少行外星人available_space_y = (ai_settings.screen_height - (3 * alien_height) - ship_height)number_rows = int(available_space_y / (2 * alien_height))return number_rowsdef create_alien(ai_settings, screen, aliens, alien_number, row_number):# 创建一个外星人并将其放在当前行alien = Alien(ai_settings, screen)alien_width = alien.rect.widthalien.x = alien_width + 2 * alien_width * alien_numberalien.rect.x = alien.xalien.rect.y = alien.rect.height + 2 * alien.rect.height * row_numberaliens.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)

执行后可以看到添加了一群外星人,如下图所示:

让外星人移动

接下来就是让外星人群在屏幕上向右移动,在接触到屏幕边缘后下移一定距离,再向相反方向移动,不断移动所有的外星人,知道都被消灭;有外星人撞到飞船,或者有外星人抵达屏幕最低端则游戏结束。

向右移动外星人

为移动外星人,可以调用 alien.py 中的 update() 方法,需要先在 settings.py 模块中添加外星人移动速度参数,如下:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)# 飞船的设置self.ship_speed_factor = 1.5# 子弹设置self.bullet_speed_factor = 1self.bullet_width = 3self.bullet_height = 15self.bullet_color = 60, 60, 60self.bullets_allowed = 5# 外星人设置self.alien_speed_factor = 1

然后设置 alien.py 中的 update() 实现,如下:

    def update(self):# 向右移动外星人self.x += self.ai_settings.alien_speed_factorself.rect.x = self.x

game_func.py 模块中添加 update_aliens() 方法用于更新外星人组的位置,如下:


def update_aliens(aliens):# 更新外星人群中所有的外星人的位置# 对编组调用方法update(),会自动对每个组内成员调用update()aliens.update()

在主循环中添加使用更新外星人位置的方法,具体如下:

import sys
import pygame
from settings import Settings
from ship import Ship
from alien import Alien
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象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, ship, aliens)# 开始游戏的主循环while True:# 监视键盘和鼠标事件gf.check_events(ai_settings, screen, ship, bullets)ship.update()# 调用 update_bullets() 更新子弹gf.update_bullets(bullets)# 调用 update_aliens() 更新外星人位置gf.update_aliens(aliens)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, aliens, bullets)run_game()

此时还没有添加移动到右端限制,所以外星人会始终右移,即使离开屏幕后还是会不断增加 x 的大小。

创建表示外星人移动方向的设置

下面创建使外星人在接触右边缘后向下移动、再向左移动的设置。在 settings.py 模块中添加参数:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)# 飞船的设置self.ship_speed_factor = 1.5# 子弹设置self.bullet_speed_factor = 1self.bullet_width = 3self.bullet_height = 15self.bullet_color = 60, 60, 60self.bullets_allowed = 5# 外星人设置self.alien_speed_factor = 1# 遇到边沿后向下移动的速度self.fleet_drop_speed = 10# fleet_direction :1-表示向右移动,-1 表示向左移动self.fleet_direction = 1

添加外星人是否撞到边缘的判断

现在向 alien.py 中添加检测边缘的代码,如下:

    def update(self):# 向右移动外星人self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction)self.rect.x = self.xdef check_edges(self):# 如果接触边缘,则返回 Truescreen_rect = self.screen.get_rect()if self.rect.right >= screen_rect.right:return Trueelif self.rect.left <= 0:return Trueelsereturn False

实现向下移动并改变方向

有外星人到达屏幕边沿时,需要将其下移并改变移动方向。为此,可以向 game_func() 模块中添加判断边缘函数 check_fleet_edges()change_fleet_direction() 。具体如下:

def check_fleet_edges(ai_settings, aliens):# 有外星人到达边缘时采取相应措施for alien in aliens:if alien.check_edges():change_fleet_direction(ai_settings, aliens)breakdef change_fleet_direction(ai_settings, aliens):# 整群外星人下移,并改变移动方向for alien in aliens:alien.rect.y += ai_settings.fleet_drop_speedai_settings.fleet_direction *= -1def update_aliens(ai_settings, aliens):# 更新外星人群中所有的外星人的位置check_fleet_edges(ai_settings, aliens)aliens.update()

修改 ai_invasion.py 中的相关调用,之前调用 gf.update_aliens() 的传递参数部分,gf.update_aliens(ai_settings, aliens) 。修改完成后执行效果如下:

射杀外星人

在创建完成飞船及操作响应和外星人移动后,就需要添加当子弹击中外星人后的响应操作。在游戏编程中,子弹碰撞到外星人其实就是两个游戏元素产生了重叠,可以采用 sprite.groupcollide() 方法来检测两个编组成员之间的碰撞。

检测子弹与外星人的碰撞

首先添加坚持子弹与外星人直接的碰撞,如果发生碰撞立刻使被碰撞的外星人消失,为此,可以在更新子弹位置后立刻检测碰撞并做出响应。

方法 sprite.groupcollide() 将每颗子弹的 rect 同每个外星人的 rect 进行比较,并返回一个字典,其中包含了碰撞的子弹和外星人。在这个字典中,每个键都是一颗子弹,而对应的值就是发生碰撞的外星人。

具体在 game_func.py 模块中的 update_bullets() 中添加如下代码:

def update_bullets(aliens, bullets):# 更新子弹的位置bullets.update()# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)# 检查是否有子弹与外星人发送碰撞#如果发生碰撞,删除发送碰撞的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

其中,collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)groupcollide 会返回字典,后面的两个实参 True 表示当检测到发生碰撞时会自动移除组内发送碰撞的元素。修改相应的参数调用并执行,如下:

为测试方便添加大招子弹

只需要通过运行这个游戏就可以测试其中的很多功能,但是有些时候需要花很长的时间才能击落外星人进行测试,这很浪费时间,为了方便起见,可以将子弹的宽度设置很大,这样就能一次操作后将所有的外星人都击落了。

settings.py 模块中将子弹宽度设置与屏幕同宽,如下:

        # 子弹设置self.bullet_speed_factor = 1self.bullet_width = 1200self.bullet_height = 15self.bullet_color = 60, 60, 60self.bullets_allowed = 5

game_func.py 中的碰撞检测的调用修改为 collisions = pygame.sprite.groupcollide(bullets, aliens, False, True) ,这样子弹在击落外星人后会继续存在,效果如下:

生成新的外星人群

当一个外星人群被消灭之后,又会出现新的一群外星人。所以需要在外星人群被消灭后再次添加一群外星人,这就需要检查编组 aliens 是否为空,如果为空则再次调用 create_fleet() ,这一步操作添加到 update_bullets() 中进行,因为外星人是在这里被消灭的,如下:

def update_bullets(ai_settings, screen, ship, aliens, bullets):# 更新子弹的位置bullets.update()# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)# 检查是否有子弹与外星人发送碰撞#如果发生碰撞,删除发送碰撞的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)# 检查是否需要再次添加外星人if len(aliens) == 0:# 删除现有子弹并新建新的外星人群bullets.empty()create_fleet(ai_settings, screen, ship, aliens)

改动后,效果如下:

提高子弹速度

当外星人数量多了之后,可能会发现子弹速度比之前更慢了,这是因为在每次循环后,pygame 需要做更多工作了,可以修改 settings.py 模块中的子弹速度参数来提高子弹的速度。

        # 子弹设置self.bullet_speed_factor = 1self.bullet_width = 1200self.bullet_height = 15self.bullet_color = 60, 60, 60self.bullets_allowed = 5

重构 update_bullets()

接下来重新构造 update_bullets() 函数,使其不再需要完成那么多任务,将处理子弹和外星人碰撞的代码单独封装,如下:

def update_bullets(ai_settings, screen, ship, aliens, bullets):# 更新子弹的位置bullets.update()# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)# 检查是否有子弹与外星人发送碰撞check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets)check_bullet_alien_collision(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)

结束游戏

如果玩家没能够在足够短的时间内将全部外星人消灭,并且有外星人撞到飞船或者到达底端时,飞船将被摧毁。当玩家用光飞船后,游戏也就结束了。

检测外星人和飞船碰撞

我们首先检查外星人与飞船之间的碰撞,以便对此作出合适的响应。在每次更新外星人位置后立即检测外星人与飞船之间的碰撞。

def update_aliens(ai_settings, ship, aliens):# 更新外星人群中所有的外星人的位置check_fleet_edges(ai_settings, aliens)aliens.update()# 检测外星人和飞船之间的碰撞if pygame.sprite.spritecollideany(ship, aliens):print("Ship hit!!!")

修改后,当外星人和飞船发生碰撞后就会产生打印消息的处理,如下:

响应外星人和飞船碰撞

前面只是对外星人和飞船的碰撞进行检测,当检测到时也只是单纯作出打印处理,并没有做其他处理。在真实游戏中,发生碰撞后,并不销毁当前 ship 实例再创建一个新的实例,而是对撞击次数进行统计。

接下来就需要编写一个用于游戏统计信息的新类—— GameStats ,存放在模块 game_stats.py 中,具体如下:

class GameStats():# 跟踪游戏的统计信息def __init__(self, ai_settings):# 初始化统计信息self.ai_settings = ai_settingsself.reset_stats()def reset_stats(self):# 初始化在游戏运行中可能变化的统计信息# 飞船限制次数self.ships_left = self.ai_settings.ship_limit

并且在 settings.py 模块中添加飞船次数限制参数 ship_limit,如下:

        # 飞船的设置self.ship_speed_factor = 1.5self.ship_limit = 3

此外,还需要对 alien_invasion.py 做一些修改,以创建 GameStats 实例:

import sys
import pygame
from settings import Settings
from ship import Ship
from alien import Alien
from game_stats import GameStats
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))pygame.display.set_caption("Alien Invasion")# 创建用于存储游戏统计信息的实例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, ship, bullets)ship.update()# 调用 update_bullets() 更新子弹gf.update_bullets(ai_settings, screen, ship, aliens, bullets)# 调用 update_aliens() 更新外星人位置gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, aliens, bullets)run_game()

可以看到已经对 gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets) 调用进行了修改,所以还需要修改其具体实现:

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):# 响应外星人撞到飞船# 将 ships_left 减 1stats.ships_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)

并且在 ship.py 模块中添加 center_ship() 方法,如下:

class Ship():def center_ship(self):# 居中飞船位置self.center = self.screen_rect.centerx

运行后如下效果:

确定运行游戏的哪些部分

alien_invasion.py 中,需要确定游戏的哪些部分在任何情况下都运行,哪些部分仅在游戏处于活动状态才运行:

修改 alien_invasion.py ,如下:

import sys
import pygame
from settings import Settings
from ship import Ship
from alien import Alien
from game_stats import GameStats
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))pygame.display.set_caption("Alien Invasion")# 创建用于存储游戏统计信息的实例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, ship, bullets)if stats.game_active:ship.update()# 调用 update_bullets() 更新子弹gf.update_bullets(ai_settings, screen, ship, aliens, bullets)# 调用 update_aliens() 更新外星人位置gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, ship, aliens, bullets)run_game()

并且修改 game_func.py 模块中的 check_events() 方式的处理,添加对 t 按键的暂停和继续的响应。


def check_keydown_events(event, ai_settings, screen, stats, ship, bullets):# 响应按键按下if event.key == pygame.K_RIGHT:ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Trueelif event.key == pygame.K_SPACE:# 创建一颗子弹,并将其加入编组bullets中fire_bullet(ai_settings, screen, ship, bullets)elif event.key == pygame.K_q:# 退出程序sys.exit()elif event.key == pygame.K_t:# 暂停游戏或者继续游戏if stats.game_active:stats.game_active = Falseelse:stats.game_active = Truedef check_keyup_events(event, ship):# 响应按键松开if event.key == pygame.K_RIGHT:ship.moving_right = Falseelif event.key == pygame.K_LEFT:ship.moving_left = Falsedef check_events(ai_settings, screen, stats, 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_settings, screen, stats, ship, bullets)elif event.type == pygame.KEYUP:check_keyup_events(event, ship)

添加 Play 按钮

接下来,我们将添加一个 Play 按钮,在游戏开始前出现,并在结束后再次出现,这样可以使得玩家能够再次开始新游戏。

当前,玩家在运行 alien_invasion.py 时就开始了。下面让游戏一开始处于非活动状态,并提示玩家单击 Play 按钮来开始游戏。为此,需要在 game_stats.py 模块中添加一下代码:

class GameStats():# 跟踪游戏的统计信息def __init__(self, ai_settings):# 初始化统计信息self.ai_settings = ai_settingsself.reset_stats()# 让游戏一开始处于非活动状态self.game_active = Falsedef reset_stats(self):# 初始化在游戏运行中可能变化的统计信息# 飞船限制次数self.ships_left = self.ai_settings.ship_limit

创建 Button 类

由于 pygame 没用内置创建按钮的方法,所以需要构建一个 Button 类,用于创建带标签的实心矩形,可以在游戏中使用这些代码来创建任何按钮。

创建文件 button.py ,在其中添加如下代码:

import pygame.fontclass Button():# 创建带有标签的实心矩形def __init__(self, ai_settings, screen, msg):# 初始化按钮属性self.screen = screenself.screen_rect = screen.get_rect()# 设置按钮的尺寸和其他属性self.width, self.height = 200, 500self.button_color = (0, 255, 0)self.text_color = (255, 255, 255)self.font = pygame.font.SysFont(None, 48)# 创建按钮的rect对象,并使其居中self.rect = pygame.Rect(0, 0, self.width, self.height)self.rect.center = self.screen_rect.center# 按钮标签创建self.prep_msg(msg)def prep_msg(self, msg):# 将 msg 渲染成图像,并使其在按钮中居中self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)self.msg_image_rect = self.msg_image.get_rect()self.msg_image_rect.center = self.rect.centerdef draw_button(self):# 绘制一个用颜色填充的按钮,在绘制其中文本self.screen.fill(self.button_color, self.rect)self.screen.blit(self.msg_image, self.msg_image_rect)

在屏幕上绘制按钮

接下来就是使用 Button 类在屏幕上创建一个 Play 按钮,直接在 alien_invasion.py 文件中创建,如下所示:

import sys
import pygame
from settings import Settings
from ship import Ship
from alien import Alien
from button import Button
from game_stats import GameStats
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))pygame.display.set_caption("Alien Invasion")# 创建play按钮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, ship, bullets)if stats.game_active:ship.update()# 调用 update_bullets() 更新子弹gf.update_bullets(ai_settings, screen, ship, aliens, bullets)# 调用 update_aliens() 更新外星人位置gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button)run_game()

修改 update_screen(ai_settings, screen, ship, aliens, bullets, play_button) 实现,如下:

def update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button):# 更新屏幕上的图像,并切换到新屏幕# 每次循环时都重新绘制屏幕screen.fill(ai_settings.bg_color)# 在飞船和外星人后面重新绘制子弹for bullet in bullets.sprites():bullet.draw_bullet()ship.blitme()aliens.draw(screen)# 如果游戏处于非活动状态,则绘制Play按钮if not stats.game_active:play_button.draw_button()# 让最近绘制的屏幕可见pygame.display.flip()

此时运行,开始画面如下:

开始游戏

为了在玩家点击 Play 按钮时开始新游戏,需要在 game_func.py 模块中添加一下代码,以实现与这个按钮相关的鼠标事件(game_func.py):

def check_play_button(stats, play_button, mouse_x, mouse_y):# 在玩家单击play按钮时开始游戏if play_button.rect.collidepoint(mouse_x, mouse_y):stats.game_active = Truedef check_events(ai_settings, screen, stats, 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_settings, screen, stats, ship, bullets)elif event.type == pygame.KEYUP:check_keyup_events(event, ship)elif event.type == pygame.MOUSEBUTTONDOWN:# 鼠标按下事件mouse_x, mouse_y = pygame.mouse.get_pos()check_play_button(stats, play_button, mouse_x, mouse_y)

并修改调用部分的参数传递,gf.check_events(ai_settings, screen, stats, play_button, ship, bullets) ,修改后运行如下:

重置游戏

前面编写的代码只处理了玩家第一次单击 Play 按钮的情况,但是还没有添加处理游戏结束的实现,因为没用重置导致游戏结束的条件。

为了在玩家每次单击 Play 按钮时都重置游戏,需要重置统计信息、删除现有的外星人和子弹,创建一群新的外星人,并使得飞船居中,修改如下(game_func.py):

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):# 在玩家单击play按钮时开始游戏if play_button.rect.collidepoint(mouse_x, mouse_y):# 重置游戏统计信息stats.reset_stats()stats.game_active = True# 清空外星人列表和子弹列表aliens.empty()bullets.empty()# 创建一群新外星人并将飞船居中create_fleet(ai_settings, screen, ship, aliens)ship.center_ship()

将 Play 按钮切换到非活动状态

当前,Play 按钮存在一个问题,即便按钮 Play 不可见,但是鼠标按下原来按钮区域依然会有响应,在游戏开始之后,玩家按下原来按钮区域就会导致游戏重置,所以需要修复这个问题,也就是仅在游戏处于未开始状态才可以对鼠标点击按钮区域做出响应,修改如下(game_func.py):

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):# 在玩家单击play按钮时开始游戏button_cliend = play_button.rect.collidepoint(mouse_x, mouse_y)if button_cliend and not stats.game_active:# 重置游戏统计信息stats.reset_stats()stats.game_active = True# 清空外星人列表和子弹列表aliens.empty()bullets.empty()# 创建一群新外星人并将飞船居中create_fleet(ai_settings, screen, ship, aliens)ship.center_ship()

经过修改后,在游戏进行状态下,鼠标按键不会做出响应。

隐藏光标

为了让玩家能够开始游戏,我们需要使得光标可见,但是在游戏开始之后,光标就不需要显示了,此时就需要使得游戏在活动状态时光标不可见。

game_func.py 中添加如下代码:

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):# 响应外星人撞到飞船# 将 ships_left 减 1if stats.ships_left > 0: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 = Falsepygame.mouse.set_visible(True)def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):# 在玩家单击play按钮时开始游戏button_cliend = play_button.rect.collidepoint(mouse_x, mouse_y)if button_cliend and not stats.game_active:# 隐藏光标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()

修改后,运行效果如下:

提高等级

当前,将整群外星人都消灭干净后,玩家将提高一个等级,但是游戏的难度并没有改变。下面,将增加一点趣味:当玩家将屏幕上的外星人都消灭后,将加快游戏的节奏,让游戏玩起来更有挑战一点。

修改速度设置

首先重新组织 Settings 类,将游戏设置划分为静态的和动态的两组。对于随着游戏进行而变化的设置,需要确保它们在开始新游戏时能够被重置。具体修改如下:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)# 飞船的设置self.ship_limit = 3# 子弹设置self.bullet_width = 1200self.bullet_height = 15self.bullet_color = 60, 60, 60self.bullets_allowed = 5# 外星人设置self.fleet_drop_speed = 50# 以什么样的速度加快游戏节奏self.speedup_scale = 1.1        self.initialize_dynamic_settings()def initialize_dynamic_settings(self):# 初始化动态设置参数self.ship_speed_factor = 1.5self.bullet_speed_factor = 3self.alien_speed_factor = 1# fleet_direction :1-表示向右移动,-1 表示向左移动self.fleet_direction = 1def increase_speed(self):# 提高速度设置self.ship_speed_factor *= self.speedup_scaleself.bullet_speed_factor *= self.speedup_scaleself.alien_speed_factor *= self.speedup_scale

并且在全部消灭外星人后,调用 increase_speed() 方法提升游戏节奏,修改如下:

def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets):# 删除发生碰撞的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)# 检查是否需要再次添加外星人if len(aliens) == 0:# 删除现有子弹并新建新的外星人群bullets.empty()ai_settings.increase_speed()create_fleet(ai_settings, screen, ship, aliens)

通过以上修改,就可以完成在每次消灭完全部外星人后,游戏速度会提升。

重置速度

每当玩家开始新游戏时,都需要将之前变化的参数重置为初始值,否则每次开始新游戏,游戏里的设置参数依然是前一次改变后的效果。修改 game_func.py, 如下:

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):# 在玩家单击play按钮时开始游戏button_cliend = play_button.rect.collidepoint(mouse_x, mouse_y)if button_cliend and not stats.game_active:# 重置游戏参数ai_settings.initialize_dynamic_settings()# 隐藏光标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()

记分

接下来就需要实现一个计分系统,以方便实时记录玩家的的分,并且显示最高的分、当前游戏等级以及剩余飞船数量。

记录得分是游戏的一项统计信息,所以需要在 GameStats 类中增加一个 score 属性,如下:

class GameStats():# 跟踪游戏的统计信息def __init__(self, ai_settings):# 初始化统计信息self.ai_settings = ai_settingsself.reset_stats() # 让游戏一开始处于非活动状态self.game_active = False# 记录游戏得分self.score = 0def reset_stats(self):# 初始化在游戏运行中可能变化的统计信息# 飞船限制次数self.ships_left = self.ai_settings.ship_limit

创建得分显示类

为方便在屏幕上显示得分,需要创建一个新类 Scoreboard ,这个类只显示当前得分,但后面也将会使用它来显示最高得分、等级以及剩余飞船数量。下面创建模块 scoreboard.py ,在文件中添加如下代码:

import pygame.fontclass Scoreboard():# 显示得分信息类def __init__(self, ai_settings, screen, stats):# 初始化显示得分类属性self.screen = screenself.screen_rect = screen.get_rect()self.ai_settings = ai_settingsself.stats = stats# 显示得分信息使用的字符设置self.text_color = (30, 30, 30)self.font = pygame.font.SysFont(None, 40)# 准备初始化得分图像self.prep_score()def prep_score(self):# 将得分转化为渲染图像score_str = str(self.stats.score)self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# 将得分放在屏幕右上角self.score_rect = self.score_image.get_rect()self.score_rect.right = self.screen_rect.right - 20self.score_rect.top = 20def show_score(self):# 在屏幕上显示self.screen.blit(self.score_image, self.score_rect)

创建记分牌

现在为了在画面中显示得分,在 alien_invasion.py 中创建 Scoreboard 实例,如下:

import sys
import pygame
from settings import Settings
from ship import Ship
from alien import Alien
from button import Button
from game_stats import GameStats
from scoreboard import Scoreboard
import game_func as gf
from pygame.sprite import Groupdef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))pygame.display.set_caption("Alien Invasion")# 创建play按钮play_button = Button(ai_settings, screen, "Play")# 创建用于存储游戏统计信息的实例stats = GameStats(ai_settings)# 创建存储游戏统计信息实例,并创建记分牌sb = Scoreboard(ai_settings, screen, stats)# 创建一艘飞船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()# 调用 update_bullets() 更新子弹gf.update_bullets(ai_settings, screen, ship, aliens, bullets)# 调用 update_aliens() 更新外星人位置gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)# 调用 update_screen() 更新屏幕gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)run_game()

修改 update_screen() 函数实现:


def update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button):# 更新屏幕上的图像,并切换到新屏幕# 每次循环时都重新绘制屏幕screen.fill(ai_settings.bg_color)# 在飞船和外星人后面重新绘制子弹for bullet in bullets.sprites():bullet.draw_bullet()ship.blitme()aliens.draw(screen)# 显示得分sb.show_score()# 如果游戏处于非活动状态,则绘制Play按钮if not stats.game_active:play_button.draw_button()# 让最近绘制的屏幕可见pygame.display.flip()

修改后,显示如下图,右上角会出现 0

消灭外星人后更新得分

在屏幕上实时显示得分,每当有外星人被击中时,都需要更新 stats.score 的值,然后调用 prep_score() 来更新得分图像,在 settings.py 中添加参数,指定每个外星人代表的积分数:

class Settings():'''存储《外星人大战》所有设置的类'''# 外星人设置self.fleet_drop_speed = 50# 积分self.alien_points = 50

然后在 game_func.py 中添加更新参数代码实现:


def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):# 更新子弹的位置bullets.update()# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)# 检查是否有子弹与外星人发送碰撞check_bullet_alien_collision(ai_settings, screen, stats, sb, ship, aliens, bullets)def check_bullet_alien_collision(ai_settings, screen, stats, sb, ship, aliens, bullets):# 删除发生碰撞的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)if collisions:# 有被击中的外星人stats.score += ai_settings.alien_pointssb.prep_score()# 检查是否需要再次添加外星人if len(aliens) == 0:# 删除现有子弹并新建新的外星人群bullets.empty()ai_settings.increase_speed()create_fleet(ai_settings, screen, ship, aliens)

修改后,执行如下图:

将消灭的每个外星人的点数都计入得分

当前,如果同时被击中多个外星人,也只是算一个50分,所以前面能够看出,我们一瞬间击中一排外星人(3个)也是50分,这里需要根据碰撞返回的字典进行增加得分,修改如下:

def check_bullet_alien_collision(ai_settings, screen, stats, sb, ship, aliens, bullets):# 删除发生碰撞的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)if collisions:# 有被击中的外星人for aliens in collisions.values():stats.score += ai_settings.alien_pointssb.prep_score()# 检查是否需要再次添加外星人if len(aliens) == 0:# 删除现有子弹并新建新的外星人群bullets.empty()ai_settings.increase_speed()create_fleet(ai_settings, screen, ship, aliens)

修改之后,得分增加效果如下:

提高点数

玩家没提高一个等级,游戏都会变得更艰难,所以在更高等级下,每个外星人代表的积分点数也应该更高。为此,在 settings.py 中添加如下代码:

class Settings():'''存储《外星人大战》所有设置的类'''def __init__(self):# 初始化游戏的设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 0, 0)# 飞船的设置self.ship_limit = 3# 子弹设置#self.bullet_width = 1200self.bullet_width = 120self.bullet_height = 15self.bullet_color = 60, 60, 60self.bullets_allowed = 5# 外星人设置self.fleet_drop_speed = 50# 积分self.alien_points = 50# 积分提高速度self.score_scale = 1.5# 以什么样的速度加快游戏节奏self.speedup_scale = 1.1        self.initialize_dynamic_settings()def initialize_dynamic_settings(self):# 初始化动态设置参数self.ship_speed_factor = 1.5self.bullet_speed_factor = 3self.alien_speed_factor = 1# fleet_direction :1-表示向右移动,-1 表示向左移动self.fleet_direction = 1def increase_speed(self):# 提高速度设置self.ship_speed_factor *= self.speedup_scaleself.bullet_speed_factor *= self.speedup_scaleself.alien_speed_factor *= self.speedup_scaleself.alien_points = int(self.alien_points * self.score_scale)

得分圆整

大多数街机风格的设计游戏都将得分显示为 10 的整数倍,下面也将遵循这一点来进行修改。在 Scoreboard 类中添加如下修改:

    def prep_score(self):# 将得分转化为渲染图像rounded_score = int(round(self.stats.score, -1))score_str = "{:,}".format(rounded_score)self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# score_str = str(self.stats.score)# self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# 将得分放在屏幕右上角self.score_rect = self.score_image.get_rect()self.score_rect.right = self.screen_rect.right - 20self.score_rect.top = 20

函数 round() 通常让小数精确到小数点后多少位,其中小数位数是由第二个实参指定的。然而,如果将第二个参数指定为负数, round() 将圆整到最近的10、100、1000等整数倍。

添加最高得分

在每次游戏后,将目前最高得分保存下来做完得分记录显示,为玩家提供超越的目标,在 game_stats.py 中添加如下代码:

import pygame.fontclass Scoreboard():# 显示得分信息类def __init__(self, ai_settings, screen, stats):# 初始化显示得分类属性self.screen = screenself.screen_rect = screen.get_rect()self.ai_settings = ai_settingsself.stats = stats# 显示得分信息使用的字符设置self.text_color = (30, 30, 30)self.font = pygame.font.SysFont(None, 40)# 准备初始化得分图像self.prep_score()self.prep_heigh_score()def prep_score(self):# 将得分转化为渲染图像rounded_score = int(round(self.stats.score, -1))score_str = "{:,}".format(rounded_score)self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# score_str = str(self.stats.score)# self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# 将得分放在屏幕右上角self.score_rect = self.score_image.get_rect()self.score_rect.right = self.screen_rect.right - 20self.score_rect.top = 20def prep_heigh_score(self):# 将最高得分转换成渲染的图像high_score = int(round(self.stats.high_score, -1)high_score_str = "{:,}".format(high_score)self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)# 将最高得分放在屏幕顶部中央self.high_score_rect = self.high_score_image.get_rect()self.high_score_rect.centerx = self.screen_rect.centerxself.high_score_rect.top = self.screen_rect.topdef show_score(self):# 在屏幕上显示self.screen.blit(self.score_image, self.score_rect)self.screen.blit(self.high_score_image, self.high_score_rect)

game_func.py 中添加一个新函数 check_high_score() 用来检查是否诞生了新的最高得分记录:

def check_high_score(stats, sb):# 检查是否诞生了新的最高记录if stats.score > stats.high_score:stats.high_score = stats.scoresb.prep_high_score()def check_bullet_alien_collision(ai_settings, screen, stats, sb, ship, aliens, bullets):# 删除发生碰撞的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)if collisions:# 有被击中的外星人for aliens in collisions.values():stats.score += ai_settings.alien_pointssb.prep_score()# 检查是否新记录check_high_score(stats, sb)# 检查是否需要再次添加外星人if len(aliens) == 0:# 删除现有子弹并新建新的外星人群bullets.empty()ai_settings.increase_speed()create_fleet(ai_settings, screen, ship, aliens)

修改后,效果如下:

显示等级

为了在游戏中显示玩家当前游戏等级,需要在 GameStats 中添加一个表示当前等级的属性,为了确保每次开始新游戏都重置等级,需要在 reset_stats() 中初始化它,具体如下:

class GameStats():# 跟踪游戏的统计信息def __init__(self, ai_settings):# 初始化统计信息self.ai_settings = ai_settingsself.reset_stats() # 让游戏一开始处于非活动状态self.game_active = False# 在任何情况下都不应该重置最高得分self.high_score = 0# 记录游戏得分self.score = 0# 记录当前游戏等级self.level = 1def reset_stats(self):# 初始化在游戏运行中可能变化的统计信息# 飞船限制次数self.ships_left = self.ai_settings.ship_limitself.score = 0self.level = 1

scoreboard.py 中添加等级显示,如下:

import pygame.fontclass Scoreboard():# 显示得分信息类def __init__(self, ai_settings, screen, stats):# 初始化显示得分类属性self.screen = screenself.screen_rect = screen.get_rect()self.ai_settings = ai_settingsself.stats = stats# 显示得分信息使用的字符设置self.text_color = (30, 30, 30)self.font = pygame.font.SysFont(None, 40)# 准备初始化得分图像self.prep_score()self.prep_high_score()self.prep_level()def prep_score(self):# 将得分转化为渲染图像rounded_score = int(round(self.stats.score, -1))score_str = "{:,}".format(rounded_score)self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# score_str = str(self.stats.score)# self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# 将得分放在屏幕右上角self.score_rect = self.score_image.get_rect()self.score_rect.right = self.screen_rect.right - 20self.score_rect.top = 20def prep_high_score(self):# 将最高得分转换成渲染的图像high_score = int(round(self.stats.high_score, -1))high_score_str = "{:,}".format(high_score)self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)# 将最高得分放在屏幕顶部中央self.high_score_rect = self.high_score_image.get_rect()self.high_score_rect.centerx = self.screen_rect.centerxself.high_score_rect.top = self.screen_rect.topdef prep_level(self):# 将登记转换成渲染的图像self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_settings.bg_color)# 将登记放在得分的位置下方self.level_rect = self.level_image.get_rect()self.level_rect.right = self.score_rect.rightself.level_rect.top = self.score_rect.bottom + 10def show_score(self):# 在屏幕上显示self.screen.blit(self.score_image, self.score_rect)self.screen.blit(self.high_score_image, self.high_score_rect)self.screen.blit(self.level_image, self.level_rect)

game_func.py 中添加等级显示:

def check_bullet_alien_collision(ai_settings, screen, stats, sb, ship, aliens, bullets):# 删除发生碰撞的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)if collisions:# 有被击中的外星人for aliens in collisions.values():stats.score += ai_settings.alien_pointssb.prep_score()# 检查是否新记录check_high_score(stats, sb)# 检查是否需要再次添加外星人if len(aliens) == 0:# 删除现有子弹并新建新的外星人群bullets.empty()ai_settings.increase_speed()# 提高等级stats.level += 1sb.prep_level()create_fleet(ai_settings, screen, ship, aliens)

修改后显示如下:

显示余下的飞船数量

为了方便提示玩家,在画面上提示当前剩余飞船数量,需要让 Ship 类继承 Sprite,以便能够创建飞船编组。

修改飞船模块 ship.py , 如下:

import pygame
from pygame.sprite import Spriteclass Ship(Sprite):# 管理飞船部分行为def __init__(self, ai_settings, screen):# 继承初始化super(Ship, self).__init__()# 初始化飞船并设置其初始位置self.screen = screenself.ai_settings = ai_settings# 加载飞船图像并获取其外接矩形self.image = pygame.image.load("images/ship.png")self.rect = self.image.get_rect()self.screen_rect = screen.get_rect()# 将每艘新产生的飞船放在屏幕底部正中央self.rect.centerx = self.screen_rect.centerxself.rect.bottom = self.screen_rect.bottom# 在飞船的属性center中存储小数值self.center = float(self.rect.centerx)# 移动标志self.moving_right = Falseself.moving_left = Falsedef update(self):# 根据移动标志调整位置# 更新飞船的 center值而不是rect值'''if self.moving_right:#self.rect.centerx += 1self.center += self.ai_settings.ship_speed_factorif self.center > self.screen_rect.width:self.center -= self.ai_settings.ship_speed_factorif self.moving_left:#self.rect.centerx -= 1self.center -= self.ai_settings.ship_speed_factorif self.center < 0:self.center += self.ai_settings.ship_speed_factorself.rect.centerx = self.center'''if self.moving_right and self.rect.right < self.screen_rect.right:self.center += self.ai_settings.ship_speed_factorif self.moving_left and self.rect.left > 0:self.center -= self.ai_settings.ship_speed_factorself.rect.centerx = self.centerdef blitme(self):# 在指定位置绘制飞船self.screen.blit(self.image, self.rect)def center_ship(self):# 居中飞船位置self.center = self.screen_rect.centerx

然后在 scoreboard.py 中使用编组包含飞船,如下:

import pygame.font
from pygame.sprite import Group
from ship import Shipclass Scoreboard():# 显示得分信息类def __init__(self, ai_settings, screen, stats):# 初始化显示得分类属性self.screen = screenself.screen_rect = screen.get_rect()self.ai_settings = ai_settingsself.stats = stats# 显示得分信息使用的字符设置self.text_color = (30, 30, 30)self.font = pygame.font.SysFont(None, 40)# 准备初始化得分图像self.prep_score()self.prep_high_score()self.prep_level()self.prep_ships()def prep_ships(self):# 显示剩余多少艘飞船self.ships = Group()for ship_number in range(self.stats.ships_left):ship = Ship(self.ai_settings, self.screen)ship.rect.x = 10 + ship_number * ship.rect.widthship.rect.y = 10self.ships.add(ship)def prep_score(self):# 将得分转化为渲染图像rounded_score = int(round(self.stats.score, -1))score_str = "{:,}".format(rounded_score)self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# score_str = str(self.stats.score)# self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)# 将得分放在屏幕右上角self.score_rect = self.score_image.get_rect()self.score_rect.right = self.screen_rect.right - 20self.score_rect.top = 20def prep_high_score(self):# 将最高得分转换成渲染的图像high_score = int(round(self.stats.high_score, -1))high_score_str = "{:,}".format(high_score)self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)# 将最高得分放在屏幕顶部中央self.high_score_rect = self.high_score_image.get_rect()self.high_score_rect.centerx = self.screen_rect.centerxself.high_score_rect.top = self.screen_rect.topdef prep_level(self):# 将登记转换成渲染的图像self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_settings.bg_color)# 将登记放在得分的位置下方self.level_rect = self.level_image.get_rect()self.level_rect.right = self.score_rect.rightself.level_rect.top = self.score_rect.bottom + 10def reset_prep(self):# 重置当前图像self.prep_score()self.prep_high_score()self.prep_level()self.prep_ships()def show_score(self):# 在屏幕上显示self.screen.blit(self.score_image, self.score_rect)self.screen.blit(self.high_score_image, self.high_score_rect)self.screen.blit(self.level_image, self.level_rect)# 绘制飞船self.ships.draw(self.screen)

修改后,画面显示如下:

总结

通过这个简单的游戏项目,可以大概了解到 Python 使用 pygame 模块进行游戏开发的简单操作流程了。这个游戏项目虽然小,但是其中也包含了对玩家操作响应、分数记录、游戏等级等等不同的处理过程,并且也基于面向对象的开发步骤,将飞船、外星人、子弹、按钮、基本参数、积分显示等等都创建成为类对象模块的方式进行管理,这样也方便后续的修改以及代码的维护。

整个项目的代码已经保存到了 GitHub 上,链接如下:

飞机大战项目链接

供大家一起参考学习,这里面还有很多没有考虑到的问题以及一些 bug 没有修改,大家可以研究研究(比如:飞船总是完整出现,能不能随机位置出现;一颗子弹就能消灭一竖行的敌人,能不能修改;每次关闭程序后,最高纪录都不能保存下来,如果修改呢?)等等问题,大家有兴趣可以改改玩一玩啊。

Python入门项目——飞机大战相关推荐

  1. python入门笔记——飞机大战(极简版、未进行继承优化)

    python入门笔记--飞机大战(极简版.未进行继承优化) import random import pygame# 引用pygame里的模块 from pygame.locals import *# ...

  2. VS/Qt C++ 入门项目飞机大战(内含全部源代码,素材,项目工程,项目祥解)可直接运行

    零.说在前面 最近做了个qt/c++的小项目飞机大战,主要是边玩边做,主要讲解一下设计思路,各个模块的实现原理,非常适合初学者拿来练手.需要源码.素材.项目详解.打包软件等整个项目用到的全部内容,可以 ...

  3. pygame小项目 ~ 3 :Python完成简易飞机大战

    pygame小项目 ~ 3 :Python完成简易飞机大战 子弹 敌机 我方战斗机全部采用图片 子弹击中和战斗机被击中全部采用碰撞检测 游戏的主函数代码 import spite from spite ...

  4. 名片管理系统--python入门项目

    名片管理系统--python入门项目 声明 功能展示 开发前准备 系统框架构建 保存名片数据的结构 新增名片功能 显示全部名片功能 查询名片功能 删除和修改名片 代码展示 总结 声明 该项目是针对py ...

  5. python小游戏——飞机大战小游戏(附源码)

    写在前面的一些P话: 大家之前用python编写过飞机大战的部分代码, 只能够展示英雄飞机,背景,敌机和发射子弹, 今天把背景音乐,击毁敌机,爆炸特效,得分等等相关功能一并加入进来, 代码有点长,三百 ...

  6. Python 之 游戏飞机大战项目实现

    项目实战 -- 飞机大战 目标 强化 面向对象 程序设计 体验使用 pygame 模块进行 游戏开发 实战步骤 pygame 快速体验 飞机大战 实战 确认模块 -- pygame pygame 就是 ...

  7. Python项目——飞机大战!

    文章目录 一.项目介绍--飞机大战 实战步骤 确认模块 -- pygame 安装 pygame 验证安装 二.pygame 快速入门 项目准备 1. 使用 `pygame` 创建图形窗口 小节目标 1 ...

  8. python项目:飞机大战(爆炸效果,血条,音效,buff加成,boss,菜单,完整详细注释的源码)

    文章目录 一. 总体概览 基本功能 细节部分(全部可以自定义) 可增添需求 二,技术框架 核心技术概述 1.游戏的初始化和退出 2 理解游戏中的坐标系 3 创建游戏主窗口 4. 理解 **图像** 并 ...

  9. Python代码实现飞机大战(经典)

    参考资料 <零基础入门学习python> 源代码以及素材资料(图片,音频)可从下面的github中下载: 飞机大战源代码以及素材资料github项目地址链接 --------------- ...

最新文章

  1. C语言之结构体以及结构体对齐访问
  2. 《HTML5游戏编程核心技术与实战》一2.6 其他全局属性
  3. ASP.NET中生成缩略图的代码
  4. UI-UIButton、UILable、UITextField总结
  5. IOCP编程小结(中)
  6. linux mysql 定时任务_Linux下Mysql定时任务备份数据的实现方法
  7. 校验插入指定结点是否导致编码循环的示例函数
  8. 接到个商城任务需要接入个微信或者QQ客服,找到了两篇解决文章
  9. 华为智能家居app未能连接上远程云服务_华为云主机远程连接不上 华为云服务登录手机版...
  10. 前端技术基础--笔记
  11. 51单片机-PWM调速(直流电机,智能小车的电机调速)
  12. 【JS教程】移动端 Touch(触摸)事件
  13. mac上编码转换工具_25个用于高效编码的Mac工具
  14. HDU 1808 Halloween treats (鸽巢原理)(数学)
  15. linux克隆tf卡中的内容,TF/SD内存卡数据克隆怎样做图文详细教程
  16. cs linux服务器ip,linux安装CS服务器详解
  17. QQ显IP显隐身制作原理介绍转)
  18. U盘作为启动盘使用GRUB2 引导 iso 文件安装各种系统
  19. 从Discuz!论坛管理看网站安全防护
  20. 万维网之父正投身的“互联网去中心化”事业,目前仍有这几大难题

热门文章

  1. 这些东西不宜空腹吃[转]
  2. 复旦计算机学院院长江,CCF 上海浦江大讲堂-杰出讲者报告会在复旦大学成功举行...
  3. python microbit typeerror,在MicroPython中使用microbit模块时出现索引错误
  4. 《MATLAB语音信号分析与合成(第二版)》:第10章 语音信号的合成算法
  5. windows下的OpenGL视频播放器开发环境
  6. 法律诉讼知识:民事诉讼流程
  7. FTP服务器部署与优化+Received message too long报错
  8. 学计算机画素描吗,怎样在电脑上画素描?
  9. 创客学院9天C语言三
  10. scum无法启动此程序以为计算机丢失,人渣SCUM电脑卡顿怎么办?人渣SCUM低配电脑设置方法介绍...