赞
踩
这篇文章主要复现书上讲的《外星人入侵》小游戏,在游戏《外星人入侵》中,玩家控制着一艘最初出现在屏幕中央的飞船,玩家可以使用箭头键左右移动飞船,还可以使用空格键进行射击。游戏开始时,一群外星人出现在天空,他们在屏幕中向下移动。玩家的任务时射杀这些外星人,当所有的外星人被消灭干净后,将出现一群新的外星人,他们的移动速度会更快。只要有外星人撞到了玩家的飞船或者到达了屏幕底部,玩家就损失一部飞船。玩家损失三部飞船后,游戏结束。
由于该项目中将包含不同的文件,因此首先我们创建一个新的文件夹,并将其命名为alien_invasion,我们之后要把所有的项目都存储在这个文件夹中。
在windows环境下,我们首先使用WIN+R
,键入cmd
,打开命令提示窗口。之后输入python,查看使用的python版本。
如下可以看出我们使用的Python版本时3.7,之后我们检测有没有安装pip,可以使用指令python -m pip --version
来查看,如果成功安装显示如下:
如果没有安装pip我们可以使用输入如下指令安装:
python get-pip.py
确认我们windows中包含有pip后,我们就可以开始安装pip了,首先我们需要在下边网址下载Pygame安装文件:
打开后我们会发现有很多版本,我们需要下载与我们使用的python版本相对应的一个:本文使用的Python版本时3.7,因此下载的是cp37
,电脑是64位操作系统的,因此下载的是图中圈红的:
下载完成后我们将whl文件放在之前创建的好的文件夹中进行安装。对于whl文件,我们使用pip进行安装,首先我们需要切换到文件所在的文件夹,之后使用pip来运行它:
python -m pip install pygame-1.9.6-cp37-cp37m-win_amd64.whl
,我们需要将install后边的内容替换为自己的whl文件的名称,安装之后结果如下图所示:
安装完成后我们就可以进行游戏开发了。
首先创建一个空的Pygame窗口,共后边用来绘制游戏元素,在这部分还会让游戏响应用户输入,设置背景颜色以及加载飞船图像
首先创建一个新的模块alien_invasion.py
,在其中我们要创建一个空的Pygame窗口。使用Pygame编写的游戏的基本结构如下:
import sys # 使用sys模块来退出游戏 import pygame # 包含了游戏开发所需的功能 def run_game(): """初始化游戏并创建一个屏幕对象""" pygame.init() # 进行初始化,检测工具包是否完整 screen = pygame.display.set_mode((800, 600)) # 创建一个大小为1200*800的窗口 pygame.display.set_caption("外星人入侵") # 开始游戏的主循环 while True: # 监视键盘和鼠标 for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() # 让最近绘制的屏幕可见 pygame.display.flip() run_game()
我们使用pygame.display.set_mode((800, 600))
来创建一个名为screen的显示窗口,游戏中的所有图形都在其中绘制。实参(800, 600)是一个元组,指定了游戏的窗口尺寸,我们可以根据自己显示器的大小来调整这些值。
对象screen是一个surface,在Python中,surface是屏幕的一部分,用来显示游戏元素,这个游戏中每个元素(比如外星人和飞船)都是一个surface。
游戏由一个while循环来控制,其中包含事件循环以及管理屏幕更新的代码。事件是用户玩游戏的操作,比如按键或者移动鼠标。我们使用while循环来持续刷新屏幕并监控用户事件。
为了访问Pygame检测到的事件,我们使用方法pygame.event.get()
,所有的键盘或者鼠标操作都会促进for循环,在这个for循环中,我们使用了if语句来检测用户是否想退出,当检测到pygame.QUIT
事件时,就使用sys.exit()
来退出游戏。
pygame.display.flip()
方法命令Pygame让最近绘制的屏幕可见,我们在地洞游戏元素的时候,这个方法会不断地更新屏幕,显示元素的新位置,并在原来的位置隐藏元素,从而营造平滑移动的效果。
Pygame默认创建一个黑色的屏幕,我们可以通过修改RGB值来将背景设置为另一种颜色,将alien_invasion.py
文件修改如下:
import sys # 使用sys模块来退出游戏 import pygame # 包含了游戏开发所需的功能 def run_game(): """初始化游戏并创建一个屏幕对象""" pygame.init() # 进行初始化,检测工具包是否完整 screen = pygame.display.set_mode((800, 600)) # 创建一个大小为1200*800的窗口 pygame.display.set_caption("外星人入侵") # 设置背景色为浅灰色 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循环外边即可。其中的颜色是由RGB指定的,我们可以通过修改RGB的值来将背景色转换为我们喜欢的颜色。
方法screen.fill()
是用指定的颜色填充屏幕,他只接收一个实参,一个指定的颜色。
每次给游戏添加游戏新功能时也会引入一些新的设置,下边编写以后个名为settings.py
的模块,其中包括一个名为Settings的类,用于将所有设置保存在一个地方。这样我再在后续修改设置的时候只需要修改settings.py
中的一些值,而无需查找散布在文件中的不同的设置,能保证代码的结构性。
# 文件settings.py
class Settings:
"""存储外星人入侵的有关的所有的类"""
def __init__(self):
"""初始化游戏设置"""
# 屏幕设置
self.screen_width = 800 # 设置窗口高度
self.screen_length = 600 # 设置窗口长度
self.bg_color = (230, 230, 230) # 设置背景颜色
之后我们修改alien_invasion.py
中的代码,通过创建对象的方式来访问设置:
# 文件alien_invasion.py import sys # 使用sys模块来退出游戏 import pygame # 包含了游戏开发所需的功能 from settings import Settings def run_game(): """初始化游戏并创建一个屏幕对象""" pygame.init() # 进行初始化,检测工具包是否完整 ai_settings = Settings() # 创建一个大小为800*600的窗口 screen = pygame.display.set_mode( (ai_settings.screen_width, ai_settings.screen_length)) pygame.display.set_caption("外星人入侵") # 开始游戏的主循环 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()
绘制它。
在游戏中几乎可以使用任何类型的文件,但是用位图(.bmp)
文件最为简单,因为Pygame默认加载位图。在选择图像时需要注意背景色,尽可能选择背景透明的颜色,这样可以使用图像编辑器将其背景设置为任何颜色。图像的背景色与游戏背景色相同时,游戏看起来最漂亮。
就游戏而言可以使用下图,请在主项目文件(alien_invasion)创建一个新的文件夹,将其命名为images,并将下图命名为ship.bmp
保存到这个文件夹中。
选择用于表示飞船的图像后,需要将其显示到屏幕上,我们将创建一个名为ship的模块,在其中编写Ship类,这个类将管理飞船的绝大多数行为。
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 # 将飞船中心的x坐标的值设置为屏幕的中点 self.rect.bottom = self.screen_rect.bottom # 将飞船底部的值设置为屏幕的底部 def blitme(self): """在指定位置绘制飞船""" self.screen.blit(self.image, self.rect)
ship的方法__init__()
接受两个参数,引用self和screen,其中后者指定了要将飞船绘制在什么地方。
为了加载图像,我们调用了pygame.image.load()
方法,这个函数返回飞船的一个surface。
我们使用get_rect()
获取相应的surface的属性rect
。,处理rect
对象时,可以通过设置这些值来指出矩形的位置。
要将游戏元素居中,可以设置相应的rect
对象的属性center, centerx, centery
,要让游戏元素与屏幕边缘对齐,可以使用属性top, bottom, left, right
,要调整游戏元素的水平或者垂直位置,可使用属性x或者y。
在Pygame中,原点(0, 0)位于屏幕的左上角,向右下方移动时,坐标将增大。
接下来更新alien_invasion.py
,使屏幕上能够出现一艘飞船
# alien_invasion.py import sys # 使用sys模块来退出游戏 import pygame # 包含了游戏开发所需的功能 from settings import Settings from ship import Ship def run_game(): """初始化游戏并创建一个屏幕对象""" pygame.init() # 进行初始化,检测工具包是否完整 ai_settings = Settings() screen = pygame.display.set_mode( (ai_settings.screen_width, ai_settings.screen_length)) # 创建一个大小为800*600的窗口 pygame.display.set_caption("外星人入侵") 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_function的模块,存储大量让游戏运行的函数,来简化alien_invasion.py
。
首先将管理事件的代码移动到一个名为check_events()
的函数中,用来简化run_game()
,并且隔离事件循环。
# game_function.py
import sys # 使用sys模块来退出游戏
import pygame # 包含了游戏开发所需的功能
def check_events():
"""响应键盘和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
这个模块导入了事件循环要使用的sys和pygame,当前函数不需要任何形参。
在模块game_function.py
增加新的函数update_screen()
,并将屏幕更新的代码移动到其中:
# game_function.py
def update_screen(ai_settings, screen, ship):
"""更新屏幕上的图像,并且切换新屏幕"""
screen.fill(ai_settings.bg_color) # 用指定的颜色填充屏幕
ship.blitme() # 将飞船显示在屏幕中
pygame.display.flip() # 将最近绘制的屏幕显示出来
将上述两个函数重构完后我们可以修改alien_invasion.py
# alien_invasion.py import pygame # 包含了游戏开发所需的功能 from settings import Settings from ship import Ship import game_function as gf def run_game(): """初始化游戏并创建一个屏幕对象""" pygame.init() # 进行初始化,检测工具包是否完整 ai_settings = Settings() screen = pygame.display.set_mode( (ai_settings.screen_width, ai_settings.screen_length)) # 创建一个大小为800*600的窗口 pygame.display.set_caption("外星人入侵") ship = Ship(screen) # 开始游戏的主循环 while True: gf.check_events() gf.update_screen(ai_settings, screen, ship) run_game() # 运行程序
接下来主要让玩家能左右移动控制飞船,为此我们会编写代码,在用户按左或者右箭头时做出响应。首先我们编写向右移动的代码,在同理得出向左移动的代码,通过这种方式,修会如何让控制屏幕移动。
每当用户按键时,都会在Pygame中注册为一个事件,事件是通过pygame.event.get()
方法获取的,因此在check_events()
,我们需要指定检查哪些类型的事件,每次按键都被注册为一个KEYDOWN事件。
检测到KEYDOWN事件后,我们需要检查按下的是否是特定的键。例如如果按下的是右箭头,我们就增大飞船的centerx值,将飞船向右移动。
如下,我们更新game_function.py
中的check_events()
函数:
# game_function.py
import sys # 使用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
之后我们在alien_invasion.py
中将调用的check_events()
函数进行更新,将ship作为方法传递给它。
event.key
在按下按键时返回按键的标识符。按键标识符是表示键盘按钮的字符串,该属性的返回值可以是:单个字母 (如 “a”, “W”, “4”, “+” 或 “$”),多个字母 (如 “F1”, “Enter”, “HOME” 或 “CAPS LOCK”)
最终实现玩家每按下右箭头键一次,飞船向右移动一个像素。
玩家按住右箭头不放时,我们希望飞船不断地向右移动,知道玩家松开为止。我们让游戏检测pygame.KEYUP
事件,以便我们能知道何时玩家松开了箭头。之后我们将结合使用KEYDOWN
和KEYUP
事件,以及一个moving_right
的标志来实现移动。
飞船不动时,将moving_right设置为False。玩家按下右箭头,我们再把标志设置为True,当玩家松开时,再把标志设置为False.
飞船的属性都由Ship类控制,因此我们给这个类再增加一个名为moving_right的属性以及一个名为update()
的方法,检查标志的状态。
# ship.py import pygame class Ship: def __init__(self, screen): """初始化飞船并且设置其初始位置""" self.screen = screen self.moving_right = False # 移动标志 # 加载飞船图像,并且获取其外接矩形 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 # 获取屏幕的x轴的中点数据并赋值给rect self.rect.bottom= self.screen_rect.bottom # 获取屏幕的底部位置数据并赋值给rect def blitme(self): """在指定位置绘制飞船""" self.screen.blit(self.image, self.rect) def update(self): """检查标志的状态,如果标志为True就移动飞船""" if self.moving_right: self.rect.centerx += 1
接下来修改check_events()
,使其在玩家按下右箭头时将moving_right设置为True,松开时将其设置为False。
# game_function.py
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
最后我们修改alien_invasion.py
中的while循环:
#`alien_invasion.py`
# 开始游戏的主循环
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings, screen, ship)
实现飞船向右移动后,我们也可以很容易的实现飞船向左移动。首先我们在ship.py
中修改Ship类,为向左移动增加标志位:
# `ship.py` import pygame class Ship: def __init__(self, screen): """初始化飞船并且设置其初始位置""" self.screen = screen self.moving_right = False # 向右移动标志 self.moving_left = False # 向左移动标志 # 加载飞船图像,并且获取其外接矩形 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 # 获取屏幕的x轴的中点数据并赋值给rect self.rect.bottom= self.screen_rect.bottom # 获取屏幕的底部位置数据并赋值给rect def blitme(self): """在指定位置绘制飞船""" self.screen.blit(self.image, self.rect) def update(self): """检查标志的状态,如果标志为True就移动飞船""" if self.moving_right: self.rect.centerx += 1 if self.moving_left: self.rect.centerx -= 1
在update()函数
中,之所以使用了一个新的if
语句,而没有用elif
代码块,是为了在玩家同时按下左右键时,飞船先向右移动,再向左移动,使飞船位置不变。
接下来还需要对check_events()
函数做相应的调整:
# game_function.py 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 if event.key == pygame.K_LEFT: ship.moving_left = False
这个时候我们再运行程序就发现飞船能左右移动了,当我们同时按住左右键时,飞船会保持不动。
当前执行while循环时,飞船最多移动一个像素,但是我们可以在Settings类中添加属性ship_speed
,用来控制飞船速度:
class Settings:
"""存储外星人入侵的有关的所有的类"""
def __init__(self):
"""初始化游戏设置"""
# 屏幕设置
self.screen_width = 800 # 设置窗口高度
self.screen_length = 600 # 设置窗口长度
self.bg_color = (230, 230, 230) # 设置背景颜色
self.ship_speed = 0.5 # 设置速度的初始值
我们将速度设置为小数可以更加细致的控制飞船的移动,但是rect的centerx等属性只能存储整数值,因此我们需要修改Ship类:
# ship.py import pygame class Ship: def __init__(self, ai_settings,screen): """初始化飞船并且设置其初始位置""" self.screen = screen self.moving_right = False # 向右移动标志 self.moving_left = False # 向左移动标志 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 # 获取屏幕的x轴的中点数据并赋值给rect self.rect.bottom= self.screen_rect.bottom # 获取屏幕的底部位置数据并赋值给rect # 再飞船的属性center中存储小数 self.center = float(self.rect.centerx) # 设置一个存储小数的新属性 def blitme(self): """在指定位置绘制飞船""" self.screen.blit(self.image, self.rect) def update(self): """检查标志的状态,如果标志为True就移动飞船""" # 更新飞船的center值而不是rect值 if self.moving_right: self.center += self.ai_settings.ship_speed # 向右移动就加上飞船的速度 if self.moving_left: self.center -= self.ai_settings.ship_speed # 向左移动就减去飞船的速度 # 根据self.center的值更新self.centerx的值 self.rect.centerx = self.center # 再将小数转换为cnterx类的整数,小数部分会被直接截断
首先我们在__init__()
的形参中增加了ai_settings
,使我们能调用Settings类中的飞船速度属性。
由于rect只能存储整数值,为了准确的记录飞机的位置,我们设置了新的属性self.center
,我们将rect的值转换为浮点数并存储在self.center
中,使用self.center
对飞船当前的位置进行计算。计算完毕后,再将值保存回self.rect.centerx
中,虽然self.rect.centerx
只保存整数部分,但是对于显示飞船来说,问题不大。
最后在alien_invasion.py
创建Ship实例时,需要传入实参ai_settings
:
# alien_invasion.py
ship = Ship(ai_settings, screen)
目前,如果玩家按住箭头的时间足够长,飞船将移动到屏幕外消失。接下来我们限制飞船的活动范围,使其到达屏幕边缘后停止。
# ship.py
def update(self):
"""检查标志的状态,如果标志为True就移动飞船"""
# 更新飞船的center值而不是rect值
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed
# 根据self.center的值更新self.centerx的值
self.rect.centerx = self.center
self.rect.right
返回飞船外接矩形的右边缘的x坐标,如果这个值小于self.screen_rect.right
(屏幕右边缘的值),说明飞船为触及屏幕右边缘。左边缘的情况同理,由于pygame的原点在左上角,因此只需要使self.rect.left
大于0即可。
随着游戏开发,check_events()
函数会越来越长,目前我们将其中的代码放在两个函数中,一个处理KEYDOWN事件,另一个处理KEYUP事件。
import sys # 使用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() # 将最近绘制的屏幕显示出来
接下来主要添加射击功能,我们将编写玩家按空格键时发射子弹(小矩形)的代码,子弹将在屏幕中向上穿行,抵达屏幕边缘后消失。
首先我们更新Settings类,在其中存储子弹的属性,包括子弹的速度,颜色,宽度,高度的信息。
# settings.py class Settings: """存储外星人入侵的有关的所有的类""" def __init__(self): """初始化游戏设置""" # 屏幕设置 self.screen_width = 800 # 设置窗口高度 self.screen_length = 600 # 设置窗口长度 self.bg_color = (230, 230, 230) # 设置背景颜色 self.ship_speed = 0.5 # 设置速度的初始值 # 子弹设置 self.bullet_speed = 0.3 # 子弹的速度 self.bullet_width = 3 # 子弹的宽度 self.bullet_height = 15 # 子弹的高度 self.bullet_color = (60, 60, 60) # 子弹的颜色
接下来创建Bullet.py
,在其中增加Bullet类:
# bullet.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 # 将子弹的centerx设置为飞船的centerx self.rect.top = ship.rect.top # 将子弹的top设置为飞船的top # 存储小数表示的子弹位置 self.y = float(self.rect.y) # 将子弹的y坐标按照小数存储 self.color = ai_settings.bullet_color # 设置子弹的颜色 self.speed = ai_settings.bullet_speed # 设置子弹的速度
Bullet类继承了我们从Pygame模块中导入的类,使用该类主要为了将游戏中的相关元素进行编组,进而同时操作编组中的所有元素,具体使用在后边介绍。
在第15行,我们创建了子弹属性rect,由于子弹并非是基于图像的,因此我们必须指明矩形左上角的坐标,以及矩形的高度和宽度。
第18行,将子弹的rect的top属性设置为飞船的top属性,让子弹看起来像是从飞船中射出的。同时我们将子弹的y坐标存储为小数值,便于之后对子弹的速度进行微调。
接下来我们编写移动子弹和显示子弹的函数:
# bullet.py
def update(self):
"""向上移动子弹"""
self.y -= self.ai_settings.bullet_spped
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上绘制子弹"""
pygame.draw.rect(self.screen, self.color, self.rect)
应以好Bullet类和必要的设置后,就可以编写代码了。我们希望玩家每次按下空格都会发射一颗子弹,首先,我们在alien_invasion.py
中创建一个编组(group),用于存储所有的子弹,这个编组是pygame.sprite.Group
类的一个实例,该类类似于列表,在主循环中我们会使用该编组在屏幕上绘制子弹并且更新子弹的位置。
# alien_invasion.py import pygame # 包含了游戏开发所需的功能 from settings import Settings from ship import Ship import game_function as gf from pygame.sprite import Group def run_game(): """初始化游戏并创建一个屏幕对象""" pygame.init() # 进行初始化,检测工具包是否完整 ai_settings = Settings() screen = pygame.display.set_mode( (ai_settings.screen_width, ai_settings.screen_length)) # 创建一个大小为800*600的窗口 pygame.display.set_caption("外星人入侵") # 设置屏幕名字 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() # 运行程序
在这里我们首先创建了一个子弹编组,并且将其命名为bullets,我们应该在while循环外创建这个编组,避免在游戏运行时创建数千个子弹编组,使游戏卡顿。
我们将bullets传递给check_events()
和update_screen()
,前者用来响应空格时发射子弹,后者用来在屏幕上显示子弹。
当我们对编组调用update()
时,编组会自动对其中的每个元素调用update()
在game_function.py
中,我们要修改check_keydown_events()
,以便于在玩家每次按下空格时发射一个子弹,同时我们无需修改check_keyup_events()
,因为松开空格什么都不会发生。
# game_function.py import sys # 使用sys模块来退出游戏 import pygame # 包含了游戏开发所需的功能 from bullet 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: # 判断按下的是不是空格 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: # 循环打印每一个子弹 bullet.draw_bullet() ship.blitme() # 将飞船显示在屏幕中 pygame.display.flip() # 将最近绘制的屏幕显示出来
当前,子弹抵达屏幕据顶端就会消失,但是这仅仅因为pygame无法绘制屏幕外的子弹,他们实际上依然存在并且占用系统内存,它们的y坐标为负数,并且越来越小,为此,我们要将消失的子弹删除,否则随着游戏进程的增加,游戏运行会越来越慢,为此我们要检测这样的条件,当子弹的bottom为0,即子弹已经穿过屏幕顶端。
# alien_invasion.py
# 开始游戏的主循环
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
# 删除已经消失的门槛
for bullet in bullets.copy(): # 在for循环中不应该修改列表或者编组的数目,这样会导致遍历缺失
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# print(len(bullets)) #显示还有多少个子弹,这个知识测试,运行时把这个语句删除可以降低内存
gf.update_screen(ai_settings, screen, ship, bullets)
接下来我们要对屏幕上同时出现的子弹的数量进行限制,鼓励玩家有目标的射击:
首先在settings.py
中存储允许存在的子弹的最大数目:
# settings.py
# 子弹设置
self.bullet_speed = 0.5 # 子弹的速度
self.bullet_width = 3 # 子弹的宽度
self.bullet_height = 15 # 子弹的高度
self.bullet_color = (60, 60, 60) # 子弹的颜色
self.bullets_allowed = 3 # 将未消失的子弹限制为3颗
接下来在game_function.py
的check_kewdown_events()
中,我们在创建子弹前要检测没有消失的子弹数是否小于该设置:
# game_function.py
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: # 判断按下的是不是空格
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship) # 创建一个子弹的实例
bullets.add(new_bullet) # 将子弹实例放在编组中
这个时候当屏幕上的子弹大于3时再按空格键就什么都不会发生
编写并检查子弹管理代码后,我们可以将其移动到模块game_function.py
中,来使主程序尽可能的简单:
# game.function.py
def update_bullets(bullets):
"""更新子弹的位置,并且删除已经消失的子弹"""
bullets.update()
for bullet in bullets.copy(): # 在for循环中不应该修改列表或者编组的数目,这样会导致遍历缺失
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# print(len(bullets)) # 显示还有多少个子弹,这个知识测试,运行时把这个语句删除可以降低内存
之后我们在主函数的while循环中调用update_bullets()
即可:
# alien_invasion
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets)
接下来将发射子弹的代码放在一个单独的函数fire_bullet()
中,这样我们在check_events()
中只需要一行单独的代码就可以发射子弹:
# game_function.py 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 fire_bullet(ai_settings, screen, ship, bullets): """如果没有达到发射限制就发射一个子弹""" if len(bullets) < ai_settings.bullets_allowed: new_bullet = Bullet(ai_settings, screen, ship) # 创建一个子弹的实例 bullets.add(new_bullet) # 将子弹实例放在编组中
截至目前为止我们已经完成了宇宙飞船的设计,并实现了飞船的显示,移动和射击功能。目前我们一共创建了5个模块,下边一次说明每个模块包含的功能以及代码:
首先是主程序alien_invasion.py
,这个文件的主要功能是创建一系列游戏需要用到的对象,并且实现屏幕的显示以及实现飞船各个功能的函数的调用。
# alien_invasion.py import pygame # 包含了游戏开发所需的功能 from settings import Settings from ship import Ship import game_function as gf from pygame.sprite import Group def run_game(): """初始化游戏并创建一个屏幕对象""" pygame.init() # 进行初始化,检测工具包是否完整 ai_settings = Settings() screen = pygame.display.set_mode( (ai_settings.screen_width, ai_settings.screen_length)) # 创建一个大小为800*600的窗口 pygame.display.set_caption("外星人入侵") # 设置屏幕名字 ship = Ship(ai_settings, screen) # 创建一艘飞船 bullets = Group() # 创建一个存储子弹的编组 # 开始游戏的主循环 while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(bullets) gf.update_screen(ai_settings, screen, ship, bullets) run_game() # 运行程序
接着是settings.py
,这里初始化游戏的外观,飞船,子弹等一系列属性:
# settings.py class Settings: """存储外星人入侵的有关的所有的类""" def __init__(self): """初始化游戏设置""" # 屏幕设置 self.screen_width = 800 # 设置窗口高度 self.screen_length = 600 # 设置窗口长度 self.bg_color = (230, 230, 230) # 设置背景颜色 self.ship_speed = 0.5 # 设置速度的初始值 # 子弹设置 self.bullet_speed = 0.5 # 子弹的速度 self.bullet_width = 3 # 子弹的宽度 self.bullet_height = 15 # 子弹的高度 self.bullet_color = (60, 60, 60) # 子弹的颜色 self.bullets_allowed = 3 # 将未消失的子弹限制为3颗
其次是ship.py
,这个类包含飞船的移动标志,图像矩阵,以及管理飞船的位置和在屏幕上绘制飞船等方法:
# ship.py import pygame class Ship: def __init__(self, ai_settings,screen): """初始化飞船并且设置其初始位置""" self.screen = screen self.moving_right = False # 能否向右移动标志 self.moving_left = False # 能否向左移动标志 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 # 获取屏幕的x轴的中点数据并赋值给rect self.rect.bottom= self.screen_rect.bottom # 获取屏幕的底部位置数据并赋值给rect # 再飞船的属性center中存储小数 self.center = float(self.rect.centerx) # 设置一个存储小数的新属性 def blitme(self): """在指定位置绘制飞船""" self.screen.blit(self.image, self.rect) def update(self): """检查标志的状态,如果标志为True就移动飞船""" # 更新飞船的center值而不是rect值 if self.moving_right and self.rect.right < self.screen_rect.right: self.center += self.ai_settings.ship_speed if self.moving_left and self.rect.left > 0: self.center -= self.ai_settings.ship_speed # 根据self.center的值更新self.centerx的值 self.rect.centerx = self.center
然后是bullet.py
,这个文件包含创建子弹的方法,已经在屏幕上显示子弹和管理子弹的位置:
# bullet.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 # 将子弹的centerx设置为飞船的centerx self.rect.top = ship.rect.top # 将子弹的top设置为飞船的top # 存储小数表示的子弹位置 self.y = float(self.rect.y) # 将子弹的y坐标按照小数存储 self.color = ai_settings.bullet_color # 设置子弹的颜色 self.speed = ai_settings.bullet_speed # 设置子弹的速度 def update(self): """向上移动子弹""" self.y -= self.speed self.rect.y = self.y def draw_bullet(self): """在屏幕上绘制子弹""" pygame.draw.rect(self.screen, self.color, self.rect)
最后是game_function.py
,在这个模块中,包含一系列函数,来时响应鼠标,键盘,重绘屏幕等一些列函数:
# game_function.py import sys # 使用sys模块来退出游戏 import pygame # 包含了游戏开发所需的功能 from bullet 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: bullet.draw_bullet() ship.blitme() # 将飞船显示在屏幕中 pygame.display.flip() # 将最近绘制的屏幕显示出来 def update_bullets(bullets): """更新子弹的位置,并且删除已经消失的子弹""" bullets.update() for bullet in bullets.copy(): # 在for循环中不应该修改列表或者编组的数目,这样会导致遍历缺失 if bullet.rect.bottom <= 0: bullets.remove(bullet) # print(len(bullets)) # 显示还有多少个子弹,这个知识测试,运行时把这个语句删除可以降低内存 def fire_bullet(ai_settings, screen, ship, bullets): """如果没有达到发射限制就发射一个子弹""" if len(bullets) < ai_settings.bullets_allowed: new_bullet = Bullet(ai_settings, screen, ship) # 创建一个子弹的实例 bullets.add(new_bullet) # 将子弹实例放在编组中
目前我们编写完上述代码,并且运行alien_invasion.py
后我们就能在屏幕底部的中间看到宇宙飞船,并通过左右键控制飞船移动,通过空格键控制飞船发射子弹,并且屏幕上最多同时有三个子弹。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。