从本章开始,将进入本书的下半部分:项目篇。在项目篇内,主要由三个部分组成:1、用Python开发游戏:外星人入侵 2、数据可视化 3、Web应用程序。
在实战项目中,我们将一边动手,一边复习过往的知识。
鉴于实战内容并不像理论内容一样,需要仔细钻研,从本章开始,读书笔记将恢复最开始的打算,即选择重点,而不是一字一句摘抄。
那么,首先就先让我们进入《外星人大战》篇之《武装飞船》吧。
12.1 规划项目
《外星人入侵》描述:在游戏中,玩家控制一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格键射击。游戏开始时,一群外星人出现在天空中,并向屏幕下方移动。玩家的人物是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一群新的外星人,其移动速度更快。只要有外星人撞到玩家的飞船或到屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。
开发的第一个阶段将创建一艘飞船,它可左右移动,并且能在用户按空格键时开火。设置这种行为后,就可以创建外星人并提高游戏的可玩性了。
12.2 安装Pygame
开始编码前,先来安装Pygame。可使用pip模块来帮助下载并安装Python包。要安装Pygame,在终端提示符下执行如下命令:
$ python -m pip install --user pygame
这个命令让Python运行pip模块,将Pygame包添加到当前用户的Python安装中。如果你运行程序或启动终端会话时使用的命令不是python,而是python3,请执行如下命令来安装Pygame:
$python3 -m pip install --user pygame
注意:如果该命令在macOS系统中不管用,请尝试在不指定标志--user 的情况下再次执行。
这里额外再介绍另外一种安装方式:MacOS上安装和运行pygame
如果书中的方式不好用的话,可以多试试看。
如果你跟我一样,使用的pycharm,那么安装在pycharm中的安装教程如下:
首先,打开Pycharm,点击“Configure”按钮(或选择cmd键+ ,)再选择你要安装的项目,点击左下角“+”号,从新弹出的窗口上方搜索“Pygame”,找到一个名为“Pygame”的模块,再点击窗口左下角的“Install Package”等待安装完毕,重启Pycharm即可(安装后的模块只能在选择的项目里使用)。
12.3 开始游戏项目
首先创建一个空的Pygame窗口,供之后用来绘制游戏元素,如飞船和外星人。我们还将让这个游戏响应用户输入,设置背景色,以及加载飞船图像。
12.3.1 创建Pygame窗口及响应用户输入
下面创建一个表示游戏的类,以创建空的Pygame窗口。为此,在文本编辑器中新建一个文件,将其保存为alien_invasion.py,再在其中输入如下代码:
import sys
import pygame
class AlienInvasion:
"""管理游戏资源和行为的类"""
def __init__(self):
"""初始化游戏并创建游戏资源"""
① pygame.init()
② self.screen = pygame.display.set_mode((1200,800))
pygame.display.set_caption("Alien Invassion")
def run_game(self):
"""开始游戏的主循环"""
③ while True:
# 监视键盘和鼠标事件
④ for event in pygame.event.get():
⑤ if event.type == pygame.QUIT:
sys.exit()
#让最近绘制的屏幕可见
⑥ pygame.display.flip()
if __name__ == '_main_':
# 创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
首先,导入模块sys 和pygame。模块pygame包含开发游戏所需的功能。玩家退出时,我们将使用模块sys中的工具来退出游戏。
为开发游戏《外星人入侵》,我们创建了一个表示它的类,名为AlienInvasion。在这个类的方法__init__()中,调用函数pygame.init()来初始化背景设置,让pygame能够正确地工作(见①)。在②处,调用pygame.display.srt_caption()来创建一个显示窗口,游戏的所有图形元素都将在其中绘制。实参(1200,800)是一个元组,指定了游戏窗口的尺寸——宽1200像素,高800像素(你可以根据自己的显示器调整这些值)。将这个显示窗口赋给属性 self.screen,让这个类中的方法都能够使用它。
赋给元素self.screen的对象是一个surface。在pygame中,surface是屏幕的一部分,用于显示游戏元素。在这个游戏中,每个元素(外星人或飞船)都是一个surface。
display.set_model()返回的surface表示整个游戏窗口。激活游戏的动画循环后,每经过一次循环都将自动重绘这个surface,将用户输入出发的所有变化都反映出来。
这个游戏由方法run_game()控制。该方法包含一个不断运行的while循环(见③),而这个循环包含一个事件循环以及管理屏幕更新的代码。事件是用户玩游戏时执行的操作,如按键或移动鼠标。为程序响应时间,可编写一个事件循环,以侦听时间并根据发生的时间类型执行合适的人物。④处的for循环就是一个事件循环。
为访问pygame检测到的时间,我们使用了函数pygame.event.get()。这个函数返回一个列表,其中包含它在上一次被调用后发生的所有哦时间。所有键盘和鼠标时间都将导致这个for循环运行。在这个循环中,我们将编写一系列if语句来检测并响应特定的时间。例如,当玩家单机游戏窗口的关闭按钮时,将检测到pygame.QUIT事件,进而调用sys.exit()来退出游戏(见⑤)。
⑥处调用了pygame.display.flip(),命令pygame让最近绘制的屏幕可见。在这里,它在每次执行while循环时都绘制一个空屏幕,并擦去旧屏幕,使得只有新屏幕可见。我们移动游戏元素时,pygame.display.flip()将不断更新屏幕,以显示元素的 新位置,并且在原来的位置隐藏元素,从而营造平滑移动的效果。
在这个文件末尾,创建一个游戏实例并调用run_game()。这个代码放在一个if代码块中,仅当直接运行该文件时,它们才会执行。如果此时运行alien_invasion.py,将看到一个空的pygame窗口。
12.3.2 设置背景色
pygame 默认创建一个黑色屏幕,这太乏味了。下面来将背景设置为另一种颜色,这是在方法__init__()末尾进行的:
import sys
import pygame
class AlienInvasion:
"""管理游戏资源和行为的类"""
def __init__(self):
"""初始化游戏并创建游戏资源"""
pygame.init()
self.screen = pygame.display.set_mode((1200,800))
pygame.display.set_caption("Alien Invasion")
#设置背景色
self.bg_color = (230,230,230)
def run_game(self):
"""开始游戏的主循环"""
while True:
# 监视键盘和鼠标事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 每次循环时都重绘屏幕
self.screen.fill(self.bg_color)
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__ == '__main__':
# 创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
在pygame中,颜色是以RGB值指定的。在颜色值(230,230,230)中,红色、绿色和蓝色的量相同,它生成一种浅灰色。我们将这种颜色赋给了self.bg_color。
调用方法fill()用这种背景色填充屏幕。方法fill()用于处理surface,只接受一个实参:一种颜色。
12.3.3 创建设置类
下面来编写一个名叫settings的模块,在其中包含一个名叫Setting的类,用于将所有设置都存储在一个地方,以免在代码中到处添加设置。
在文件夹alien_invasion中,新建一个名叫settings.py的文件,并在其中添加如下settings类:
class Setting:
"""存储游戏《外星人入侵》中所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
为在项目中创建Settings实例并用它来访问设置,需要将alien_invasion.py修改成下面这样:
import sys
import pygame
from settings import Settings
class AlienInvasion:
"""管理游戏资源和行为的类"""
def __init__(self):
"""初始化游戏并创建游戏资源"""
pygame.init()
self.settings = Settings()
self.screen = pygame.display.set_mode(
(self.settings.screen_width,self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
#设置背景色
self.bg_color = self.settings.bg_color
def run_game(self):
"""开始游戏的主循环"""
while True:
# 监视键盘和鼠标事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 每次循环时都重绘屏幕
self.screen.fill(self.bg_color)
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__ == '__main__':
# 创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
在主程序中,导入Setting类,调用pygame.init(),再创建一个Settings实例并将其赋给self.settings。创建屏幕时,使用了self.settings的属性screen_width和screen_heigt。接下来填充屏幕时,也是用了self.setting来访问背景色。
现在运行程序与之前不会有任何不同,因为我们只是将设置移到了不同的地方。现在可以在屏幕上添加新元素了。
12.4 添加飞船图像
下面将飞船加入游戏中。为了在屏幕上绘制玩家的飞船,我们将加载一副图像,再使用pygame方法blit()绘制它。
在游戏中可以使用任何图像文件,但是用(.bmp)文件最简单。
选择图像时,要特别注意背景色。请尽可能选择北京为透明或纯色的图像,便于使用图像编辑器将其背景替换为任何颜色。
就本项目而言,可使用文件ship.bmp(该文件可在本书源代码文件中找到)。请在项目文件夹中新建一个名为images的文件夹,并将文件ship.bmp保存在其中。
12.4.1 创建ship类
选择用于表示飞船的图像后,需要将其显示到屏幕上。我们创建一个名叫ship的模块,其中包含ship类,负责管理飞船的大部分行为。
import pygame
class ship:
def __init__(self,ai_game):
"""初始化飞船并设置其初始位置"""
① self.screen = ai_game.screen
② self.screen_rect = ai_game.screen.get_rect()
#加载飞船图像并获取其外接矩形
③ self.image = pygame.image.load('images/ship.png')
self.rect = self.image.get_rect()
#对于每艘新飞船,都将其放在屏幕底部的中央。
④ self.rect.midbottom = self.screen_rect.midbottom
⑤ def blitme(self):
"""在指定位置绘制飞船。"""
self.screen.blit(self.image, self.rect)
Pygame之所以高效,是因为它让你能够处理矩形(rect对象)一样处理所有的游戏元素,即使其形状并非矩形。通过将游戏元素视为矩形,pygame能够更快地判断它们是否发生了碰撞。这种做法效果通常很好。在这个类中,我们将把飞船和屏幕作为矩形进行处理。
定义这个类之前,导入了模块pygame。ship的方法__init__()接受两个参数:yunyongself和指向当前AlienInvasion实例中的引用。这让ship能够访问AlienInvasion中定义的所有游戏资源。在①处,将屏幕赋给了ship的一个属性,以便在这个类的所有方法中轻松访问。在②处,使用方法get_rect()访问屏幕的属性rect,并将其赋给了self.screen_rect,这让我们能够将飞船放到屏幕正确位置。
调用pygame.image.load()加载图像,并将飞船图像的位置传递给它(见④)。该函数返回一个表示飞船的surface,而我们将这个surface赋给了self.image。加载图像后,使用get_rect()
获取相应surface的属性rect,以便后面能够使用它来指定飞船的位置。
处理rect对象时,可使用矩形四角和中心的x坐标和y坐标。可通过设置这些值来制定矩形的位置。要让游戏元素居中,可设置相应rect对象的属性center、centerx或centery;要让游戏元素与屏幕边缘对齐,可使用属性top、bottom、left或right。除此之外,还有一些组合属性,如midbottom、midtop、midleft和midright。要调整游戏元素的水平或垂直位置,可使用属性x和y,分别相应矩形左上角x坐标和y坐标。这些属性让你无需做游戏开发人员原本需要手工完成的计算,因此会经常用到。
注意:在Python中,原点(0,0)位于屏幕左上角,向右下方移动时,坐标值将增大。在1200X800的屏幕上,原点位于左上角,而右下角的坐标为(1200,800)。这些坐标对应的是游戏窗口,而不是物理屏幕。
我们要将飞船放在屏幕地步的中央。为此,将self.rect.midbottom设置为表示屏幕矩形的属性midbottom(见④)。Python使用这些rect属性来放置飞船图像,使其与屏幕下边缘对齐并水平居中。
在⑤处,定义了方法blitme(),它将图像绘制到self.rect指定的位置。
12.4.2 在屏幕上绘制飞船
下面更新alien_invasion.py,创建一艘飞船并调用方法blitme():
import sys
import pygame
from settings import Settings
from ship import Ship
class AlienInvasion:
"""管理游戏资源和行为的类"""
def __init__(self):
"""初始化游戏并创建游戏资源"""
pygame.init()
self.settings = Settings()
self.screen = pygame.display.set_mode(
(self.settings.screen_width,self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
self.ship = Ship(self)
#设置背景色
self.bg_color = self.settings.bg_color
def run_game(self):
"""开始游戏的主循环"""
while True:
# 监视键盘和鼠标事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 每次循环时都重绘屏幕
self.screen.fill(self.bg_color)
self.ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__ == '__main__':
# 创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
导入ship类,并在创建屏幕后创建一个ship实例。调用ship)()时,必须提供一个参数:一个AlienInvasion实例。在这里,self指向的是当前AlienInvasion实例。这个参数让ship能够访问游戏资源,如对象screen。我们将这个ship实例赋给了self.ship。
填充背景后,调用ship.blitme()将飞船绘制到屏幕上,确保它出现在背景前面。现在如果运行alien_invasion.py,将看到飞船位于空游戏屏幕底部的中央。
12.5 重构:方法_check_events()和__upadte_screen()
在大型项目中,经常需要在添加新代码其按重构既有代码。重构旨在简化既有代码的结构,使其更容易扩展。
12.5.1 方法_check_events()
我们将把管理实践的代码移到一个名为_check_events()的方法中,以简化run_game()并隔离事件管理循环。通过隔离事件循环,可将事件管理与游戏的其他方面分离。
下面新增方法_check_events()后的AlienInvasion类,只有run_game()的代码受到影响:
import sys
import pygame
from settings import Settings
from ship import Ship
class AlienInvasion:
"""管理游戏资源和行为的类"""
def __init__(self):
"""初始化游戏并创建游戏资源"""
pygame.init()
self.settings = Settings()
self.screen = pygame.display.set_mode(
(self.settings.screen_width,self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
self.ship = Ship(self)
#设置背景色
self.bg_color = self.settings.bg_color
def run_game(self):
"""开始游戏的主循环"""
while True:
self._check_events()
# 每次循环时都重绘屏幕
self.screen.fill(self.bg_color)
self.ship.blitme()
def _check_events(self):
""" 监视键盘和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__ == '__main__':
# 创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
新增方法_check_events(),并将检查玩家是否单击了关闭窗口按钮的代码移到该方法中。
要调用当前类的方法,可使用句点表示法,并指定变量名self和要调用的方法的名称。我们在run_game()的while循环中调用这个新增的方法。
12.5.2 方法_update_screen()
为进一步简化run_game(),将更新屏幕的代码移到一个名为_update_screen()的方法中:
import sys
import pygame
from settings import Settings
from ship import Ship
class AlienInvasion:
"""管理游戏资源和行为的类"""
def __init__(self):
"""初始化游戏并创建游戏资源"""
pygame.init()
self.settings = Settings()
self.screen = pygame.display.set_mode(
(self.settings.screen_width,self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
self.ship = Ship(self)
#设置背景色
self.bg_color = self.settings.bg_color
def run_game(self):
"""开始游戏的主循环"""
while True:
self._check_events()
self._update_screen()
def _check_events(self):
""" 监视键盘和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
def _update_screen(self):
"""更新屏幕上的图像,并切换到新屏幕"""
# 每次循环时都重绘屏幕
self.screen.fill(self.bg_color)
self.ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__ == '__main__':
# 创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
我们将绘制背景和飞船以及切换屏幕的代码移动了方法_update_screen()中。现在,run_game()中的主循环简单多了,很容易看出在每次循环中都检测了新发生的事件并更新了屏幕。
因为内容比较多,本章节将放到上下两章。到了实战中,理论的内容互相交叉,不是很容易懂,建议自己多尝试着复盘下。