12.4 添加飞船图像
下面将飞船加入游戏中。为了在屏幕上绘制玩家的飞船,我们将加载一幅图像,再使用Pygame方法blit() 绘制它。
为游戏选择素材时,务必要注意许可。最安全、最不费钱的方式是使用Pixabay等网站提供的免费图形,无须授权许可即可使用并修改。
在游戏中几乎可以使用任何类型的图像文件,但使用位图(.bmp)文件最为简单,因为Pygame默认加载位图。虽然可配置Pygame以使用其他文件类型,但有些文件类型要求你在计算机上安装相应的图像库。大多数图像为.jpg、.png或.gif格式,但可使用Photoshop、GIMP和Paint等工具将其转换为位图。
选择图像时,要特别注意背景色。请尽可能选择背景为透明或纯色的图像,便于使用图像编辑器将其背景替换为任意颜色。图像的背景色与游戏的背景色匹配时,游戏看起来最漂亮。你也可以将游戏的背景色设置成图像的背景色。
就游戏《外星人入侵》而言,可使用文件ship.bmp(如图12-1所示),该文件可在本书主页(ituring.cn/book/2784)的“随书下载”中找到。这个文件的背景色与项目使用的设置相同。请在项目文件夹(alien_invasion)中新建一个名为images的文件夹,并将文件ship.bmp保存在其中。
图12-1 游戏《外星人入侵》中的飞船
12.4.1 创建Ship 类
选择用于表示飞船的图像后,需要将其显示到屏幕上。我们创建一个名为ship 的模块,其中包含Ship 类,负责管理飞船的大部分行为。
ship.py
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.bmp')
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() 接受两个参数:引用self 和指向当前AlienInvasion 实例的引用。这让Ship 能够访问AlienInvasion 中定义的所有游戏资源。在❶处,将屏幕赋给了Ship 的一个属性,以便在这个类的所有方法中轻松访问。在❷处,使用方法get_rect() 访问屏幕的属性rect ,并将其赋给了self.screen_rect ,这让我们能够将飞船放到屏幕的正确位置。
调用pygame.image.load() 加载图像,并将飞船图像的位置传递给它(见❸)。该函数返回一个表示飞船的surface,而我们将这个surface赋给了self.image 。加载图像后,使用get_rect() 获取相应surface的属性rect ,以便后面能够使用它来指定飞船的位置。
处理rect 对象时,可使用矩形四角和中心的 坐标和
坐标。可通过设置这些值来指定矩形的位置。要让游戏元素居中,可设置相应rect 对象的属性center 、centerx 或centery ;要让游戏元素与屏幕边缘对齐,可使用属性top 、bottom 、left 或right 。除此之外,还有一些组合属性,如midbottom 、midtop 、midleft 和midright 。要调整游戏元素的水平或垂直位置,可使用属性x 和y ,分别是相应矩形左上角的
坐标和
坐标。这些属性让你无须做游戏开发人员原本需要手工完成的计算,因此会经常用到。
注意 在Pygame中,原点(0, 0)位于屏幕左上角,向右下方移动时,坐标值将增大。在1200 × 800的屏幕上,原点位于左上角,而右下角的坐标为(1200, 800)。这些坐标对应的是游戏窗口,而不是物理屏幕。
我们要将飞船放在屏幕底部的中央。为此,将self.rect.midbottom 设置为表示屏幕的矩形的属性midbottom (见❹)。Pygame使用这些rect 属性来放置飞船图像,使其与屏幕下边缘对齐并水平居中。
在❺处,定义了方法blitme() ,它将图像绘制到self.rect 指定的位置。
12.4.2 在屏幕上绘制飞船
下面更新alien_invasion.py,创建一艘飞船并调用其方法blitme() :
alien_invasion.py
—snip—
from settings import Settings
from ship import Ship
class AlienInvasion:
"""管理游戏资源和行为的类"""
def init(self):
—snip—
pygame.display.set_caption("Alien Invasion")
❶ self.ship = Ship(self)
def run_game(self):
—snip—
# 每次循环时都重绘屏幕。
self.screen.fill(self.settings.bg_color)
❷ self.ship.blitme()
# 让最近绘制的屏幕可见。
pygame.display.flip()
—snip—
导入Ship 类,并在创建屏幕后创建一个Ship 实例(见❶)。调用Ship() 时,必须提供一个参数:一个AlienInvasion 实例。在这里,self 指向的是当前AlienInvasion 实例。这个参数让Ship 能够访问游戏资源,如对象screen 。我们将这个Ship 实例赋给了self.ship 。
填充背景后,调用ship.blitme() 将飞船绘制到屏幕上,确保它出现在背景前面(见❷)。
现在如果运行alien_invasion.py,将看到飞船位于空游戏屏幕底部的中央,如图12-2所示。
图12-2 游戏《外星人入侵》屏幕底部的中央有一艘飞船
12.5 重构:方法checkevents() 和_updatescreen()
在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易扩展。本节将把越来越长的方法run_game() 拆分成两个辅助方法(helper method)。辅助方法 在类中执行任务,但并非是通过实例调用的。在Python中,辅助方法的名称以单个下划线打头。
12.5.1 方法checkevents()
我们将把管理事件的代码移到一个名为checkevents() 的方法中,以简化run_game() 并隔离事件管理循环。通过隔离事件循环,可将事件管理与游戏的其他方面(如更新屏幕)分离。
下面是新增方法checkevents() 后的AlienInvasion 类,只有run_game() 的代码受到影响:
alien_invasion.py
def run_game(self):
"""开始游戏主循环。"""
while True:
❶ self.checkevents()
# 每次循环时都重绘屏幕。
—snip—
❷ def checkevents(self):
"""响应按键和鼠标事件。"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
新增方法checkevents() (见❷),并将检查玩家是否单击了关闭窗口按钮的代码移到该方法中。
要调用当前类的方法,可使用句点表示法,并指定变量名self 和要调用的方法的名称(见❶)。我们在run_game() 的while 循环中调用这个新增的方法。
12.5.2 方法updatescreen()
为进一步简化run_game() ,将更新屏幕的代码移到一个名为updatescreen() 的方法中:
alien_invasion.py
def run_game(self):
"""开始游戏主循环。"""
while True:
self.checkevents()
self.updatescreen()
def checkevents(self):
—snip—
def updatescreen(self):
"""更新屏幕上的图像,并切换到新屏幕。"""
self.screen.fill(self.settings.bg_color)
self.ship.blitme()
pygame.display.flip()
我们将绘制背景和飞船以及切换屏幕的代码移到了方法updatescreen() 中。现在,run_game() 中的主循环简单多了,很容易看出在每次循环中都检测了新发生的事件并更新了屏幕。
如果你开发过大量的游戏,可能早就开始像这样将代码放到不同的方法中了。不过如果你从未开发过这样的项目,可能不知道如何组织代码。这里采用的做法是,先编写可行的代码,等代码越来越复杂时再进行重构,以向你展示真正的开发过程:先编写尽可能简单的代码,等项目越来越复杂后对其进行重构。
对代码进行重构使其更容易扩展后,可以开始处理游戏的动态方面了!
动手试一试
练习12-1:蓝色天空 创建一个背景为蓝色的Pygame窗口。
练习12-2:游戏角色 找一幅你喜欢的游戏角色位图图像或将一幅图像转换为位图。创建一个类,将该角色绘制到屏幕中央,并将该图像的背景色设置为屏幕背景色,或者将屏幕背景色设置为该图像的背景色。