【Python】第十二章_外星人入侵_武装飞船
目录
项目概述:
1 项目需求分析
开发大型项目时, 做好项目需求分析后再动手编写项目很重要。 需求分析可确保把控项目的主模块逻辑设计, 从而提高项目成功的可能性。
下面来编写有关游戏《外星人入侵》 的描述:
在游戏《外星人入侵》 中, 玩家控制着一艘最初出现在屏幕底部中央的飞船。 玩家可以使用箭头键左右移动飞船, 还可使用空格键进行射击。 游戏开始时, 一群外星人出现在天空中, 他们在屏幕中向下移动。 玩家的任务是射杀这些外星人。 玩家将所有外星人都消灭干净后, 将出现一群新的外星人, 他们移动的速度更快。 只要有外星人撞到了玩家的飞船或到达了屏幕底部, 玩家就损失一艘飞船。 玩家损失三艘飞船后, 游戏结束。
在第一个开发阶段, 我们将创建一艘可左右移动的飞船, 这艘飞船在用户按空格键时能够开火。 设置好这种行为后, 我们就能够将注意力转向外星人, 并提高这款游戏的可玩性。
2 安装Pygame
这里仅演示在Windows系统中安装Pygame,尝试以下方法:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名
用pip管理工具安装库文件时,默认使用国外的源文件,因此在国内的下载速度会比较慢,可能只有50KB/s。幸好,国内的一些顶级科研机构已经给我们准备好了各种镜像,下载速度可达2MB/s。
其中,比较常用的国内镜像包括:
(1)阿里云 http://mirrors.aliyun.com/pypi/simple/
(2)豆瓣http://pypi.douban.com/simple/
(3)清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/
(4)中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/
(5)华中科技大学http://pypi.hustunique.com/
可以在使用pip的时候,加上参数-i和镜像地址(如https://pypi.tuna.tsinghua.edu.cn/simple),
例如:pip install -i Simple Index pygame,这样就会从清华镜像安装pygame库。
3 开始游戏项目
3.1 创建Pygame窗口以及响应用户输入
import sys
import pygame
def 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()
3.2 设置背景色
import sys
import pygame
def run_game():
#初始化游戏并创建一个屏幕对象
pygame.init()
screen = pygame.display.set_mode((1200,800))
pygame.display.set_caption("Alien Invasion")
#设置背景颜色
❶ bg_color = (230,230,230)
#开始游戏的主循环
while True:
#监视键盘和鼠标事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#每次循环时都重绘屏幕
❷ screen.fill(bg_color)
#让最近绘制的屏幕可见
pygame.display.flip()
run_game()
bg_color存储自定义背景框颜色(见❶) 。 该颜色只需指定一次, 因此我们在进入主while 循环前定义它。在Pygame中, 颜色是以RGB值指定的。我们这里将背景设置为一种浅灰色。在❷处, 我们调用方法screen.fill() , 用背景色填充屏幕; 这个方法只接受一个实参: 一种颜色。
3.3 创建设置类
编写settings 的模块(包含一个Settings 的类), 存储游戏的所有设置。 通过传递一个设置对象, 而不是众多不同的设置,让函数调用更简单, 在项目增大时只需修改settings.py中的一些值, 而无需查找散布在文件中的不同设置。
下面是最初的Settings 类:
settings.py
class Settings():
"""存储《外星人入侵》的所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
为创建Settings 实例并使用它来访问设置:
alien_invasion3.py
import sys
import pygame
from settings import Settings
def 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")
#开始游戏的主循环
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()
4 添加飞船图像
4.1 创建Ship 类
import pygame
class Ship():
def __init__(self,screen):
"""初始化飞船并设置其初始位置"""
self.screen = screen
#加载飞船图像并获取其外接矩形
❶ self.image = pygame.image.load('image/ship.bmp')
❷ self.rect = self.image.get_rect()
❸ self.screen_rect = screen.get_rect()
#将每艘飞船放在屏幕底部中央
❹ self.rect.centerx = self.screen.centerx
self.rect.bottom = self.screen_rect.bottom
❺ def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect)
Ship 的方法__init__() 接受两个参数: 引用self 和screen , 其中后者指定了要将飞船绘制到什么地方。调用pygame.image.load()来加载图像 (见❶) 。 self.image存储一个表示飞船的surface。然后我们可以使用get_rect() 获取飞船surface的属性rect (见❷) 。
处理rect 对象时, 可使用矩形四角和中心的 x 和 y 坐标。 可通过设置这些值来指定矩形的位置。要将游戏元素居中, 可设置相应rect 对象的属性center 、 centerx 或centery 。 要让游戏元素与屏幕边缘对齐, 可使用属性top 、 bottom 、 left 或right ; 要调整游戏元素的水平或垂直位置, 可使用属性x 和y , 它们分别是相应矩形左上角的 x 和 y 坐标。
注意 在Pygame中, 原点(0, 0)位于屏幕左上角, 向右下方移动时, 坐标值将增大。
屏幕的矩形存储在self.screen_rect 中(见❸) , 再将self.rect.centerx (飞船中心的x 坐标) 设置为表示屏幕的矩形的属性centerx (见❹) , 并将self.rect.bottom (飞船下边缘的y 坐标) 设置为表示屏幕的矩形的属性bottom 。 这样Pygame就会利用这些rect 属性将把飞船放在屏幕底部中央。最后,我们定义方法blitme() , 它根据self.rect 指定的位置将图像绘制到屏幕上(见❺)。
4.2 在屏幕上绘制飞船
下面演示创建一艘飞船, 并调用其方法blitme():
alien_invasion4.py
import sys
import pygame
from settings import Settings
from ship1 import Ship
def 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:
#监视键盘和鼠标事件
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()
导入Ship 类后创建屏幕后创建一个名为ship 的Ship 实例。填充背景后, 调用ship.blitme() 将飞船绘制到屏幕上, 确保它出现在背景前面(见❷) 。
运行alien_invasion.py, 如图12-2所示将看到飞船位于空游戏屏幕底部中央。
图12-2 游戏《外星人入侵》 屏幕底部中央有一艘飞船
5 重构: 模块game_functions
5.1 函数check_events()
import sys
import pygame
def check_events():
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
首先模块game_functions导入事件检查循环要使用的sys 和pygame 。 check_events()函数体复制了alien_invasion.py的事件循环。
下面在alien_invasion5.py, 导入模块game_functions , 并调用事件循环函数check_events() :
alien_invasion5.py
import pygame
from settings import Settings
from ship1 import Ship
import game_functions1 as gf
def 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()
#每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
run_game()
5.2 函数update_screen()
为进一步简化run_game() ,更新屏幕的代码移到模块game_functions的update_screen() 函数:
game_functions2.py
import sys
import pygame
def 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()
新函数update_screen() 包含三个形参: ai_settings 、 screen 和ship 。 下面在alien_invasion6.py, 导入模块game_functions , 并调用更新屏幕的函数update_screen() :
alien_invasion6.py
import pygame
from settings import Settings
from ship1 import Ship
import game_functions2 as gf
def 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()
gf.update_screen(ai_settings,screen,ship)
run_game()
6 驾驶飞船
6.1 响应按键
import sys
import pygame
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
❶❷ elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
#向右移动飞船
❸ ship.rect.centerx += 1
def update_screen(ai_settings,screen,ship):
"""更新屏幕上的图像,并切换到新屏幕"""
#每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
玩家按右箭头键时, 需要将飞船向右移动,因此函数check_events() 包含形参ship 。函数check_events() 内部的事件循环中elif 代码块是为了Pygame 检测到KEYDOWN事件时作出响应(见❶) 右箭头键(event.key == K_RIGHT )被按下时(见❷), ship.rect.centerx 的值加1, 飞船向右移动(见❸) 。
alien_invasion7.py
import pygame
from settings import Settings
from ship1 import Ship
import game_functions3 as gf
def 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(ship)
gf.update_screen(ai_settings,screen,ship)
run_game()
6.2 允许不断移动
玩家按住右箭头键不放时, 我们希望飞船不断地向右移动, 直到玩家松开为止。当玩家松开右箭头键时我们能够检测到pygame.KEYUP事件; 结合使用KEYDOWN和KEYUP事件, 以及一个moving_right 的标志位来实现持续移动。
飞船不动时, moving_right 为False 。 玩家按下右箭头键时,moving_right被设置为True ; 玩家松开时, moving_right被重新设置为False 。
飞船的属性都由Ship 类控制, 我们在Ship 类中添加moving_right 的属性和一个update() 的方法。 方法update() 检查标志moving_right 的状态,如果这个标志为True , 就调整飞船的位置。 每当需要调整飞船的位置时, 我们都调用这个方法。
根据上述描述对Ship 类所做以下修改:
ship2.py
class Ship():
def __init__(self,screen):
"""初始化飞船并设置其初始位置"""
self.screen = screen
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images/ship.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
#将每艘飞船放在屏幕底部中央
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
#移动标志
❶ self.moving_right = False
❷ def update(self):
"""根据移动标志调整飞船的位置"""
if self.moving_right:
self,rect.centerx += 1
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect)
在方法__init__() 中, 我们添加了属性self.moving_right , 并将其初始值设置为False (见❶) 。 添加方法update() , 当moving_right为True 时,向右移动飞船(见❷) 。
下面修改game_functions模块check_events()函数,根据右箭头按下和松开分别将moving_right设置为True和False。
game_functions4.py
import sys
import pygame
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
❶ ship.moving_right = True
elif event.type == pygame.KEYUP:
❷ if event.key == pygame.K_RIGHT:
ship.moving_right = False
def update_screen(ai_settings,screen,ship):
"""更新屏幕上的图像,并切换到新屏幕"""
#每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
玩家按下右箭头键时,不再直接移动飞船位置,而是将moving_right 设置为True(见❶) 。然后添加了一个新的elif 代码块, 用于响应KEYUP事件: 玩家松开右箭头键(event.key == pygame.K_RIGHT) 时(见❷), 我们将moving_right 设置为False 。最后, 在alien_invasion8.py 的while 循环中调用飞船的方法update() :
alien_invasion8.py
import pygame
from settings import Settings
from ship2 import Ship
import game_functions4 as gf
def 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(ship)
ship.update()
gf.update_screen(ai_settings,screen,ship)
run_game()
飞船的位置将在检测到键盘事件后(但在更新屏幕前) 更新。如果你现在运行alien_invasion8.py并按住右箭头键, 飞船将不断地向右移动, 直到你松开为止。
6.3 左右移动
飞船能够不断地向右移动后, 添加向左移动的逻辑很容易。 下面修改Ship 类和函数check_events() 使得飞船左右移动。 下面显示了对Ship 类的方法__init__() 和update() 所做的相关修改:
ship3.py
import pygame
class Ship():
def __init__(self,screen):
"""初始化飞船并设置其初始位置"""
self.screen = screen
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images/ship.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
#将每艘飞船放在屏幕底部中央
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
#移动标志
self.moving_right = False
self.moving_left = False
def update(self):
"""根据移动标志调整飞船的位置"""
if self.moving_right:
self.rect.centerx += 1
if self.moving_left:
self.rect.centerx -= 1
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect)
在方法__init__() 中, 我们添加了标志self.moving_left ; 在方法update() 中, 我们添加了一个if 代码块而不是elif 代码块, 这样如果玩家同时按下了左右箭头键, 将先增大飞船的rect.centerx 值, 再降低这个值, 即飞船的位置保持不变。 如果使用一个elif 代码块来处理向左移动的情况, 右箭头键将始终处于优先地位。 从向左移动切换到向右移动时, 玩家可能同时按住左右箭头键, 在这种情况下, 前面的做法让移动更准确。
我们还需对check_events() 作两方面的调整:
game_functions5.py
import sys
import pygame
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def update_screen(ai_settings,screen,ship):
"""更新屏幕上的图像,并切换到新屏幕"""
#每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
如果因玩家按下左键而触发了KEYDOWN事件, 我们就将moving_left 设置为True ; 如果因玩家松开左而触发了KEYUP事件, 我们就将moving_left 设置为False 。 这里之所以可以使用elif 代码块, 是因为每个事件都只与一个键相关联; 如果玩家同时按下了左右箭头键, 将检测到两个不同的事件。
如果此时运行alien_invasion9.py, 将能够不断地左右移动飞船; 如果你同时按左右箭头键, 飞船将纹丝不动。
alien_invasion9.py
import pygame
from settings import Settings
from ship3 import Ship
import game_functions5 as gf
def 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(ship)
ship.update()
gf.update_screen(ai_settings,screen,ship)
run_game()
6.4 调整飞船的速度
为了控制飞船的速度,我们在Settings 类中添加属性ship_speed_factor,允许每次执行循环时,飞船最多移动1像素,根据这个属性决定飞船在每次循环时最多移动多少距离。 下面演示了如何在settings1.py中添加这个新属性:
settings1.py
class Settings():
"""存储《外星人入侵》的所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
#飞船的位置
self.ship_speed_factor = 1.5
settings1.py中,ship_speed_factor 的初始值设置成了1.5,即每次移动1.5像素。
Ship 类 rect 的centerx 等属性只能存储整数值, 因此我们需要对Ship 类做些修改:
ship4.py
import pygame
class Ship():
❶ def __init__(self,ai_settings,screen):
"""初始化飞船并设置其初始位置"""
self.screen = screen
❷ self.ai_settings = ai_settings
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images/ship.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
#将每艘飞船放在屏幕底部中央
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
#在飞船的属性center中存储小数值
❸ self.center = float(self.rect.centerx)
#移动标志
self.moving_right = False
self.moving_left = False
def update(self):
"""根据移动标志调整飞船的位置"""
#更新飞船的center值,而不是rect
if self.moving_right:
❹ self.center += self.ai_settings.ship_speed_factor
if self.moving_left:
self.center -= self.ai_settings.ship_speed_factor
#根据self.center更新rect对象
❺ self.rect.centerx = self.center
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect)
在❶处, 我们在__init__() 的形参列表中添加了ai_settings ,并将形参ai_settings 的值存储在一个属性中,以便能够在update() 中让飞船能够获取其速度设置(见❷) 。现在我们通过增加或减去一个单位为像素的小数值来调整飞船的位置, 因此需要将位置存储在一个能够存储小数值的变量中。 可以使用小数来设置rect 的属性, 但rect 将只存储这个值的整数部分。 为准确地存储飞船的位置, 我们定义了一个可存储小数值的新属性self.center (见❸) 。 我们使用函数float()将self.rect.centerx 的值转换为小数, 并将结果存储到self.center 中。
调整飞船的位置的update() 函数中, 将self.center 的值增加或减去ai_settings.ship_speed_factor 的值(见❹) 。 更新self.center 后, 我们再根据它来更新控制飞船位置的self.rect.centerx (见❺) self.rect.centerx 将只存储self.center 的整数部分, 但对显示飞船而言, 这问题不大。
在alien_invasion10.py中创建Ship 实例时, 需要传入实参ai_settings :
alien_invasion10.py
import pygame
from settings1 import Settings
from ship4 import Ship
import game_functions5 as gf
def 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)
#开始游戏的主循环
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings,screen,ship)
run_game()
6.5 限制飞船的活动范围
当前, 如果玩家按住箭头键的时间足够长, 飞船将移到屏幕外面, 消失得无影无踪。 下面来修复这种问题, 让飞船到达屏幕边缘后停止移动。 为此, 我们将修改Ship 类的方法update() :
ship5.py
import pygame
class Ship():
def __init__(self,ai_settings,screen):
"""初始化飞船并设置其初始位置"""
self.screen = screen
self.ai_settings = ai_settings
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images/ship.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
#将每艘飞船放在屏幕底部中央
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
#在飞船的属性center中存储小数值
self.center = float(self.rect.centerx)
#移动标志
self.moving_right = False
self.moving_left = False
def update(self):
"""根据移动标志调整飞船的位置"""
#更新飞船的center值,而不是rect
❶ if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
❷ if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
#根据self.center更新rect对象
self.rect.centerx = self.center
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect)
上述代码确保在修改self.center 的值之前,飞船位置在屏幕内移动。 self.rect.right 返回飞船外接矩形的右边缘的 x 坐标, 如果这个值小于self.screen_rect.right 的值,就说明飞船未触及屏幕右边缘(见❶) 。 self.rect.left 的左边缘的 x 坐标大于零, 就说明飞船未触及屏幕左边缘(见❷) 。
如果此时运行alien_invasion11.py, 飞船将在触及屏幕左边缘或右边缘后停止移动。
alien_invasion11.py
import pygame
from settings1 import Settings
from ship5 import Ship
import game_functions5 as gf
def 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)
#开始游戏的主循环
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings,screen,ship)
run_game()
6.6 重构check_events()
随着游戏开发的进行, 函数check_events() 将越来越长, 我们将其部分代码放在两个函数中: 一个处理MOUSEBUTTONDOWN事件, 另一个处理MOUSEBUTTONUP事件:
game_functions6.py
import sys
import pygame
def check_keydown_events(event,ship):
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
def check_keyup_events(event,ship):
"""响应松开"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event,ship)
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
def update_screen(ai_settings,screen,ship):
"""更新屏幕上的图像,并切换到新屏幕"""
#每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
如果此时运行alien_invasion12.py, 飞船将在触及屏幕左边缘或右边缘后停止移动。
alien_invasion12.py
import pygame
from settings1 import Settings
from ship5 import Ship
import game_functions6 as gf
def 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)
#开始游戏的主循环
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings,screen,ship)
run_game()
我们创建了两个新函数: check_keydown_events() 和check_keyup_events() , 分别实现鼠标按下和松开事件,它们都包含形参event 和ship 。然后简化check_events函数 ,对这两个函数的调用。 这样 使得函数check_events() 更简单, 代码结构更清晰。
7 射击
下面来添加射击功能。 我们将编写玩家按空格键时发射子弹(小矩形) 的代码。 子弹将在屏幕中向上穿行, 抵达屏幕上边缘后消失。
7.1 添加子弹设置
首先, 更新settings2.py, 在其方法__init__() 末尾存储新类Bullet 所需的值:
settings2.py
class Settings():
"""存储《外星人入侵》的所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
#飞船的位置
self.ship_speed_factor = 1.5
#子弹设置:创建宽3像素、 高15像素的深灰色子弹
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60,60,60
7.2 创建Bullet 类
下面来创建存储Bullet 类的文件bullet1.py, 其前半部分如下:
bullet1.py
import pygame
from pygame.sprite import Sprite
class 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.centerx
❸ self.rect.top = ship.rect.top
#存储用小数表示的子弹位置
❹ self.y = float(self.rect.y)
❺ self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
Bullet 类继承了我们从模块pygame.sprite 中导入的Sprite 类。 通过使用精灵, 可将游戏中相关的元素编组, 进而同时操作编组中的所有元素。 为创建子弹实例, 需要向__init__() 传递ai_settings 、 screen 和ship 实例, 还调用了super() 来继承Sprite 。
这里的子弹并非基于图像,故使用pygame.Rect() 类从空白开始创建一个矩形作为子弹的属性rect(见❶), 创建实例时, 必须提供矩形左上角的
x 坐标和 y 坐标, 还有矩形的宽度和高度。
在❷处, 我们将子弹的centerx 设置为飞船的rect.centerx 。 子弹应从飞船顶部射出, 因此我们将表示子弹的rect 的top 属性设置为飞船的rect 的top 属性(见❸) 。
我们将子弹的 y 坐标存储为小数值, 以便能够微调子弹的速度(见❹) 。 在❺处, 我们将子弹的颜色和速度设置分别存储到self.color 和self.speed_factor 中。
下面是bullet.py的第二部分——方法update() 和draw_bullet() :
bullet1.py
def update(self):
"""向上移动子弹"""
#更新表示子弹位置的小数值
❶ self.y -= self.speed_factor
#更新表示子弹的rect的位置
❷ self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上绘制子弹"""
❸ pygame.draw.rect(self.screen, self.color, self.rect)
我们调用update() 方法管理子弹的位置。 子弹发射出去后,在屏幕中向上移动,y 坐标会不断减小,将self.y 中减去self.speed_factor的值(见❶)来更新子弹的位置 。 接下来, 我们将self.rect.y 设置为self.y 的值(见❷) 。
我们调用draw_bullet() 绘制子弹。 函数draw.rect() 使用存储在self.color 中的颜色填充表示子弹的rect 占据的屏幕部分(见❸) 。
7.3 将子弹存储到编组中
定义Bullet 类和必要的设置后, 我们定义玩家每次按空格键时都射出一发子弹。 首先, 我们将在alien_invasion13.py中创建一个编组(group) , 用于存储所有有效的子弹, 以便能够管理发射出去的所有子弹。 这个编组将是pygame.sprite.Group 类的一个实例; pygame.sprite.Group 类类似于列表, 但提供了有助于开发游戏的额外功能。 在主循环中, 我们将使用这个编组在屏幕上绘制子弹, 以及更新每颗子弹的位置:
alien_invasion13.py
import pygame
from pygame.sprite import Group
from settings2 import Settings
from ship5 import Ship
import game_functions7 as gf
def 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()
gf.update_screen(ai_settings,screen,ship,bullets)
run_game()
我们导入了pygame.sprite 中的Group 类。 在❶处, 我们创建了一个Group 实例, 并将其命名为bullets 。并在每次循环时,使用创建的子弹编组。 在check_events() 中, 需要在玩家按空格键时处理bullets ; 而在update_screen() 中, 需要更新要绘制到屏幕上的bullets 。故需要将bullets 传递给了check_events() 和update_screen() 。
当你对编组调用update() 时(见❷), 编组将自动对其中的每个精灵调用update() , 因此代码行bullets.update() 将为编组bullets 中的每颗子弹调用bullet.update()。
想要运行alien_invasion13.py,需要适当修改game_functions7.py文件,将bullets 传递给了check_events() 和update_screen() :
game_functions7.py
import sys
import pygame
from bullet1 import Bullet
def check_keydown_events(event,ship):
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
def check_keyup_events(event,ship):
"""响应松开"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def check_events(ai_settings,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,ship)
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
def update_screen(ai_settings,screen,ship,bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
#每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
7.4 开火
在game_functions8.py中, 我们需要修改check_keydown_events() , 以便在玩家按空格键时发射一颗子弹。 我们无需修改check_keyup_events() , 因为玩家松开空格键时什么都不会发生。 我们还需修改update_screen() , 确保在调用flip() 前在屏幕上重绘每颗子弹。 下面是对game_functions8.py所做的相关修改:
game_functions8.py
import sys
import pygame
from bullet1 import Bullet
❶ def check_keydown_events(event, ai_settings, 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.button == pygame.K_SPACE:
#创建一颗子弹, 并将其加入到编组bullets中
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, 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, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
❹ 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()
编组bulltes 传递给了check_keydown_events() (见❶) 。 玩家按空格键时, 创建一颗新子弹(一个名为new_bullet 的Bullet 实例) , 并使用bullets.add(new_bullet) 将新子弹存储到编组bullets 中(见❷)。
在check_events() 的定义中, 我们需要添加形参bullets (见❸) ; 调用check_keydown_events() 时, 需要将bullets 作为实参传递给它。
在❹处, 我们给在屏幕上绘制子弹的update_screen() 添加了形参bullets 。 方法bullets.sprites() 返回一个列表, 其中包含编组bullets 中的所有精灵。 为在屏幕上绘制发射的所有子弹, 我们遍历编组bullets 中的精灵, 并对每个精灵都调用draw_bullet() (见❺) 。
运行alien_invasion14.py,不仅能够左右移动飞船,飞船还可以发射一系列子弹。还可在settings2.py中修改子弹的尺寸、颜色和速度。
import pygame
from pygame.sprite import Group
from settings2 import Settings
from ship5 import Ship
import game_functions8 as gf
def 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()
gf.update_screen(ai_settings,screen,ship,bullets)
run_game()
如图12-3所示。
图12-3 飞船发射一系列子弹后的《外星人入侵》 游戏
7.5 删除已消失的子弹
虽然子弹在屏幕上向上穿行时抵达屏幕顶部后会消失,但这仅仅是因为Pygame无法在屏幕外面绘制它们。 这些子弹实际上依然存在, 它们的 y 坐标为负数, 且越来越小。 这是个问题, 因为它们将继续消耗内存和处理能力。
所以需要将这些已消失的子弹删除, 否则游戏所做的无谓工作将越来越多, 进而变得越来越慢。 为此, 我们需要检测这样的条件, 即表示子弹的rect 的bottom 属性为零, 它表明子弹已穿过屏幕顶端:
alien_invasion15.py
import pygame
from pygame.sprite import Group
from settings2 import Settings
from ship5 import Ship
import game_functions8 as gf
def 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))
gf.update_screen(ai_settings,screen,ship,bullets)
run_game()
在for 循环中, 不应从列表或编组中删除条目,而是应该遍历编组的副本。 我们使用了方法copy() 来设置for 循环(见❶) , 这让我们能够在循环中修改bullets 。首先判断每颗子弹从屏幕顶端消失(见❷) 。 一旦消失,将其从bullets 中删除(见❸) 。 并在for循环中使用一条print 语句, 以显示当前还有多少颗子弹(见❹),随着子弹一颗颗地在屏幕顶端消失, 子弹数将逐渐降为零。 运行这个游戏并确认子弹已被删除后, 将这条print 语句删除。 如果你留下这条语句, 游戏的速度将大大降低, 因为将输出写入到终端而花费的时间比将图形绘制到游戏窗口花费的时间还多。
7.6 限制子弹数量
很多射击游戏都对可同时出现在屏幕上的子弹数量进行限制,下面在settings3.py中存储所允许的最大子弹数:
settings3.py
class Settings():
"""存储《外星人入侵》的所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
#飞船的位置
self.ship_speed_factor = 1.5
#子弹设置:创建宽3像素、 高15像素的深灰色子弹
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60,60,60
self.bullets_allowed = 3 #未消失的子弹数限制为3颗
在game_functions9.py的check_keydown_events() 中, 我们在创建新子弹前检查未消失的子弹数是否小于该设置:
game_functions9.py
import sys
import pygame
from bullet1 import Bullet
def check_keydown_events(event, ai_settings, 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:
#创建一颗子弹, 并将其加入到编组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, 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, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
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()
玩家按空格键时, 当界面上还有三颗子弹,即bullets 的长度大于3,则不创建新子弹。 如果len(bullets) 小于3, 我们就创建一个新子弹; 现在运行这个游戏程序alien_invasion16.py, 屏幕上最多只能有3颗子弹。
alien_invasion16.py
import pygame
from pygame.sprite import Group
from settings3 import Settings
from ship5 import Ship
import game_functions9 as gf
def 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))
gf.update_screen(ai_settings,screen,ship,bullets)
run_game()
7.7 创建函数update_bullets()
让主程序文件alien_invasion17.py尽可能简单,可将子弹管理代码移到模块game_functions 中。 我们创建一个名为update_bullets() 的新函数, 并将其添加到game_functions10.py的末尾:
game_functions10.py
import sys
import pygame
from bullet1 import Bullet
def check_keydown_events(event, ai_settings, 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:
#创建一颗子弹, 并将其加入到编组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, 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, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
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()
def update_bullets(bullets):
"""更新子弹的位置,并删除已消失的子弹"""
#更新子弹的位置
bullets.update()
#删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
编组bullets作为参数传入update_bullets() 函数,alien_invasion17.py中的while 循环就变得很简单了:
alien_invasion17.py
import pygame
from pygame.sprite import Group
from settings3 import Settings
from ship5 import Ship
import game_functions10 as gf
def 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()
❸ gf.update_bullets(bullets)
❹ gf.update_screen(ai_settings,screen,ship,bullets)
run_game()
我们让主循环尽可能精简, 通过函数名就能迅速知道游戏中发生的情况。 主循环检查玩家的输入(见❶) , 然后更新飞船的位置(见❷) 和所有未消失的子弹的位置(见❸) 。 接下来, 我们使用更新后的位置来绘制新屏幕(见❹) 。
7.8 创建函数fire_bullet()
下面将发射子弹的代码移到一个独立的函数中, 这样, 在check_keydown_events() 中只需使用一行代码来发射子弹, 让elif 代码块变得非常简单:
game_functions11.py
import sys
import pygame
from bullet1 import Bullet
def check_keydown_events(event, ai_settings, 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_settings, screen, ship, bullets)
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, 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, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
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()
def update_bullets(bullets):
"""更新子弹的位置,并删除已消失的子弹"""
#更新子弹的位置
bullets.update()
#删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
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)
函数fire_bullet() 只包含玩家按空格键时用于发射子弹的代码; 在check_keydown_events() 中, 我们在玩家按空格键时调用fire_bullet() 。
请再次运行alien_invasion18.py, 确认发射子弹时依然没有错误。
alien_invasion18.py
import pygame
from pygame.sprite import Group
from settings3 import Settings
from ship5 import Ship
import game_functions11 as gf
def 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()
gf.update_bullets(bullets)
gf.update_screen(ai_settings,screen,ship,bullets)
run_game()
8 小结
原文地址:https://blog.csdn.net/u011671745/article/details/136800257
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!