惠州企业网站设计,wordpress分类目录文章排序,线上销售水果营销方案,wordpress foreach十九、碰撞检测 原文#xff1a;inventwithpython.com/invent4thed/chapter19.html 译者#xff1a;飞龙 协议#xff1a;CC BY-NC-SA 4.0 碰撞检测涉及确定屏幕上的两个物体何时相互接触#xff08;即发生碰撞#xff09;。碰撞检测对于游戏非常有用。例如#xff0c;如…十九、碰撞检测 原文inventwithpython.com/invent4thed/chapter19.html 译者飞龙 协议CC BY-NC-SA 4.0 碰撞检测涉及确定屏幕上的两个物体何时相互接触即发生碰撞。碰撞检测对于游戏非常有用。例如如果玩家触碰到敌人他们可能会失去生命值。或者如果玩家触碰到硬币他们应该自动捡起它。碰撞检测可以帮助确定游戏角色是否站在坚实的地面上或者他们脚下只有空气。
在我们的游戏中碰撞检测将确定两个矩形是否重叠。本章的示例程序将涵盖这种基本技术。我们还将看看我们的pygame程序如何通过键盘和鼠标接受玩家的输入。这比我们为文本程序所做的调用input()函数要复杂一些。但在 GUI 程序中使用键盘要更加互动而在我们的文本游戏中甚至无法使用鼠标。这两个概念将使您的游戏更加令人兴奋
本章涵盖的主题 Clock对象 pygame中的键盘输入 pygame中的鼠标输入 碰撞检测 在迭代列表时不修改列表
碰撞检测程序的示例运行
在这个程序中玩家使用键盘的箭头键在屏幕上移动一个黑色的方块。较小的绿色方块代表食物出现在屏幕上方块触碰到它们时会“吃”掉它们。玩家可以在窗口的任何地方点击以创建新的食物方块。此外按 ESC 键退出程序按 X 键将玩家传送到屏幕上的随机位置。
图 19-1 显示了程序完成后的样子。 图 19-1 pygame 碰撞检测程序的屏幕截图
碰撞检测程序的源代码
开始一个新文件输入以下代码然后将其保存为collisionDetection.py。如果在输入此代码后出现错误请使用在线 diff 工具将您输入的代码与本书代码进行比较网址为www.nostarch.com/inventwithpython#diff。 collision Detection.py
import pygame, sys, random
from pygame.locals import *# Set up pygame.
pygame.init()
mainClock pygame.time.Clock()# Set up the window.
WINDOWWIDTH 400
WINDOWHEIGHT 400
windowSurface pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),0, 32)
pygame.display.set_caption(Collision Detection)# Set up the colors.
BLACK (0, 0, 0)
GREEN (0, 255, 0)
WHITE (255, 255, 255)# Set up the player and food data structures.
foodCounter 0
NEWFOOD 40
FOODSIZE 20
player pygame.Rect(300, 100, 50, 50)
foods []
for i in range(20):foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - FOODSIZE),random.randint(0, WINDOWHEIGHT - FOODSIZE), FOODSIZE, FOODSIZE))# Set up movement variables.
moveLeft False
moveRight False
moveUp False
moveDown FalseMOVESPEED 6# Run the game loop.
while True:# Check for events.for event in pygame.event.get():if event.type QUIT:pygame.quit()sys.exit()if event.type KEYDOWN:# Change the keyboard variables.if event.key K_LEFT or event.key K_a:moveRight FalsemoveLeft Trueif event.key K_RIGHT or event.key K_d:moveLeft FalsemoveRight Trueif event.key K_UP or event.key K_w:moveDown FalsemoveUp Trueif event.key K_DOWN or event.key K_s:moveUp FalsemoveDown Trueif event.type KEYUP:if event.key K_ESCAPE:pygame.quit()sys.exit()if event.key K_LEFT or event.key K_a:moveLeft Falseif event.key K_RIGHT or event.key K_d:moveRight Falseif event.key K_UP or event.key K_w:moveUp Falseif event.key K_DOWN or event.key K_s:moveDown Falseif event.key K_x:player.top random.randint(0, WINDOWHEIGHT -player.height)player.left random.randint(0, WINDOWWIDTH -player.width)if event.type MOUSEBUTTONUP:foods.append(pygame.Rect(event.pos[0], event.pos[1],FOODSIZE, FOODSIZE))foodCounter 1if foodCounter NEWFOOD:# Add new food.foodCounter 0foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH -FOODSIZE), random.randint(0, WINDOWHEIGHT - FOODSIZE),FOODSIZE, FOODSIZE))# Draw the white background onto the surface.windowSurface.fill(WHITE)# Move the player.if moveDown and player.bottom WINDOWHEIGHT:player.top MOVESPEEDif moveUp and player.top 0:player.top - MOVESPEEDif moveLeft and player.left 0:player.left - MOVESPEEDif moveRight and player.right WINDOWWIDTH:player.right MOVESPEED# Draw the player onto the surface.pygame.draw.rect(windowSurface, BLACK, player)# Check whether the player has intersected with any food squares.for food in foods[:]:if player.colliderect(food):foods.remove(food)# Draw the food.for i in range(len(foods)):pygame.draw.rect(windowSurface, GREEN, foods[i])# Draw the window onto the screen.pygame.display.update()mainClock.tick(40)导入模块
pygame 碰撞检测程序导入了与第 18 章中的动画程序相同的模块还有random模块
import pygame, sys, random
from pygame.locals import *使用时钟来控制程序的节奏
第 5 到 17 行大部分做的事情与动画程序相同它们初始化了pygame设置了WINDOWHEIGHT和WINDOWWIDTH并分配了颜色和方向常量。
然而第 6 行是新的
mainClock pygame.time.Clock()在动画程序中调用time.sleep(0.02)会减慢程序的运行速度以防止它运行得太快。虽然这个调用在所有计算机上都会暂停 0.02 秒但程序的其余部分的速度取决于计算机的速度。如果我们希望这个程序在任何计算机上以相同的速度运行我们需要一个函数在快速计算机上暂停时间更长在慢速计算机上暂停时间更短。
pygame.time.Clock对象可以在任何计算机上暂停适当的时间。第 110 行在游戏循环内调用了mainClock.tick(40)。对Clock对象的tick()方法的调用等待足够的时间以便它以大约 40 次迭代每秒的速度运行无论计算机的速度如何。这确保游戏永远不会比您预期的速度更快。对tick()的调用应该只出现一次在游戏循环中。
设置窗口和数据结构
第 19 到 22 行设置了一些用于在屏幕上出现的食物方块的变量
# Set up the player and food data structures.
foodCounter 0
NEWFOOD 40
FOODSIZE 20foodCounter变量将从值0开始NEWFOOD为40FOODSIZE为20。稍后我们将看到这些变量在创建食物时如何使用。
第 23 行设置了玩家位置的pygame.Rect对象
player pygame.Rect(300, 100, 50, 50)player变量有一个pygame.Rect对象表示方块的大小和位置。玩家的方块将像动画程序中的方块一样移动参见“移动每个方块”在第 280 页但在这个程序中玩家可以控制方块的移动方向。
接下来我们设置了一些代码来跟踪食物方块
foods []
for i in range(20):foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - FOODSIZE),random.randint(0, WINDOWHEIGHT - FOODSIZE), FOODSIZE, FOODSIZE))程序将使用foods列表来跟踪每个食物方块的Rect对象。第 25 和 26 行在屏幕周围随机放置了 20 个食物方块。您可以使用random.randint()函数来生成随机的 x 和 y 坐标。
在第 26 行程序调用pygame.Rect()构造函数来返回一个新的pygame.Rect对象。它将表示一个新食物方块的位置和大小。pygame.Rect()的前两个参数是左上角的 x 和 y 坐标。您希望随机坐标在0和窗口大小减去食物方块大小之间。如果将随机坐标设置在0和窗口大小之间那么食物方块可能会被推到窗口之外就像图 19-2 中一样。
pygame.Rect()的第三个和第四个参数是食物方块的宽度和高度。宽度和高度都是FOODSIZE常量中的值。 图 19-2对于 400×400 窗口中的 100×100 方块将左上角设置为 400 会将矩形放在窗口外。要在内部左边缘应该设置为 300。
pygame.Rect()的第三个和第四个参数是食物方块的宽度和高度。宽度和高度都是FOODSIZE常量中的值。
设置跟踪移动的变量
从第 29 行开始代码设置了一些变量用于跟踪玩家方块的移动方向
# Set up movement variables.
moveLeft False
moveRight False
moveUp False
moveDown False这四个变量具有布尔值用于跟踪哪个箭头键被按下并最初设置为False。例如当玩家按下键盘上的左箭头键移动方块时moveLeft被设置为True。当他们松开键时moveLeft被设置回False。
第 34 到 43 行几乎与以前的pygame程序中的代码相同。这些行处理游戏循环的开始以及玩家退出程序时的操作。我们将跳过对此代码的解释因为我们在上一章中已经涵盖过了。
处理事件
pygame模块可以根据鼠标或键盘的用户输入生成事件。以下是pygame.event.get()可以返回的事件
QUIT 当玩家关闭窗口时生成。
KEYDOWN 当玩家按下键时生成。具有key属性告诉按下了哪个键。还有一个mod属性告诉按下该键时是否按下了 SHIFT、CTRL、ALT 或其他键。
KEYUP 当玩家释放键时生成。具有与KEYDOWN类似的key和mod属性。
MOUSEMOTION每当鼠标在窗口上移动时生成。具有pos属性缩写为position返回窗口中鼠标位置的元组(x, y)。rel属性还返回一个(x, y)元组但它给出自上一个MOUSEMOTION事件以来的相对坐标。例如如果鼠标从(200, 200)向左移动 4 像素到(196, 200)那么rel将是元组值(-4, 0)。button属性返回一个三个整数的元组。元组中的第一个整数是左鼠标按钮第二个整数是中间鼠标按钮如果存在第三个整数是右鼠标按钮。如果鼠标移动时它们没有被按下则这些整数将为0如果它们被按下则为1。
MOUSEBUTTONDOWN当鼠标在窗口中按下按钮时生成。此事件具有pos属性它是鼠标按下按钮时鼠标位置的(x, y)元组。还有一个button属性它是从1到5的整数告诉哪个鼠标按钮被按下如表 19-1 中所述。
MOUSEBUTTONUP当鼠标按钮释放时生成。这与MOUSEBUTTONDOWN具有相同的属性。
当生成MOUSEBUTTONDOWN事件时它具有button属性。button属性是与鼠标可能具有的不同类型的按钮相关联的值。例如左键的值为1右键的值为3。表 19-1 列出了鼠标事件的所有button属性但请注意鼠标可能没有这里列出的所有button值。
表 19-1 button属性值
button的值鼠标按钮1左键2中键3右键4滚轮向上滚动5滚轮向下滚动
我们将使用这些事件来让玩家使用KEYDOWN事件和鼠标按钮点击来控制框。
处理 KEYDOWN 事件
处理按键和释放事件的代码从第 44 行开始它包括KEYDOWN事件类型 if event.type KEYDOWN:如果事件类型是KEYDOWN则Event对象具有一个key属性指示按下了哪个键。当玩家按下箭头键或 WASD 键发音为wazz-dee这些键与箭头键的布局相同但位于键盘左侧时我们希望移动框。我们将使用if语句来检查按下的键以便确定框应该移动的方向。
第 46 行将key属性与K_LEFT和K_a进行比较它们是表示键盘上左箭头键和 WASD 中 A 的pygame.locals常量。第 46 至 57 行检查每个箭头和 WASD 键 # Change the keyboard variables.if event.key K_LEFT or event.key K_a:moveRight FalsemoveLeft Trueif event.key K_RIGHT or event.key K_d:moveLeft FalsemoveRight Trueif event.key K_UP or event.key K_w:moveDown FalsemoveUp Trueif event.key K_DOWN or event.key K_s:moveUp FalsemoveDown True当按下这些键之一时代码告诉 Python 将相应的移动变量设置为True。Python 还会将相反方向的移动变量设置为False。
例如当按下左箭头键时程序执行第 47 和 48 行。在这种情况下Python 将moveLeft设置为TruemoveRight设置为False即使moveRight可能已经是FalsePython 也会将其设置为False以确保。
在第 46 行event.key可以等于K_LEFT或K_a。如果按下左箭头键则event.key中的值将设置为与K_LEFT相同的值如果按下 A 键则设置为与K_a相同的值。
通过执行第 47 和 48 行的代码如果按键是K_LEFT或K_a则左箭头键和 A 键将执行相同的操作。W、A、S 和 D 键用作更改移动变量的替代键让玩家可以使用左手而不是右手。您可以在图 19-3 中看到这两组键的示例。 图 19-3WASD 键可以编程为与箭头键执行相同的操作。
字母和数字键的常量很容易找到A 键的常量是K_aB 键的常量是K_b依此类推。3 键的常量是K_3。表 19-2 列出了其他键盘键的常用常量变量。
**表 19-2**键盘键的常量变量
pygame 常量变量键盘键K_LEFT左箭头K_RIGHT右箭头K_UP上箭头K_DOWN下箭头K_ESCAPEESCK_BACKSPACE退格键K_TABTABK_RETURNRETURN 或 ENTERK_SPACE空格键K_DELETEDELK_LSHIFT左 SHIFTK_RSHIFT右 SHIFTK_LCTRL左 CTRLK_RCTRL右 CTRLK_LALT左 ALTK_RALT右 ALTK_HOMEHOMEK_ENDENDK_PAGEUPPGUPK_PAGEDOWNPGDNK_F1F1K_F2F2K_F3F3K_F4F4K_F5F5K_F6F6K_F7F7K_F8F8K_F9F9K_F10F10K_F11F11K_F12F12
处理 KEYUP 事件
当玩家释放他们按下的键时将生成一个KEYUP事件 if event.type KEYUP:如果玩家释放的键是 ESC则 Python 应终止程序。请记住在pygame中您必须在调用sys.exit()函数之前调用pygame.quit()函数我们在第 59 到 61 行中这样做 if event.key K_ESCAPE:pygame.quit()sys.exit()第 62 到 69 行如果释放了该方向键则将移动变量设置为False if event.key K_LEFT or event.key K_a:moveLeft Falseif event.key K_RIGHT or event.key K_d:moveRight Falseif event.key K_UP or event.key K_w:moveUp Falseif event.key K_DOWN or event.key K_s:moveDown False通过KEYUP事件将移动变量设置为False会使框停止移动。
传送玩家
您还可以将传送添加到游戏中。如果玩家按下 X 键则第 71 和 72 行将玩家框的位置设置为窗口上的随机位置 if event.key K_x:player.top random.randint(0, WINDOWHEIGHT -player.height)player.left random.randint(0, WINDOWWIDTH -player.width)第 70 行检查玩家是否按下了 X 键。然后第 71 行设置一个随机的 x 坐标将玩家传送到窗口的高度减去玩家矩形的高度之间。第 72 行执行类似的代码但是针对 y 坐标。这使玩家可以通过按 X 键在窗口周围传送但他们无法控制将传送到哪里——这是完全随机的。
添加新的食物方块
玩家可以通过两种方式向屏幕添加新的食物方块。他们可以单击窗口中希望新食物方块出现的位置或者他们可以等到游戏循环迭代NEWFOOD次数这样新的食物方块将在窗口上随机生成。
我们首先看一下如何通过玩家的鼠标输入添加食物 if event.type MOUSEBUTTONUP:foods.append(pygame.Rect(event.pos[0], event.pos[1],FOODSIZE, FOODSIZE))鼠标输入与键盘输入一样通过事件处理。当玩家在单击鼠标后释放鼠标按钮时将发生MOUSEBUTTONUP事件。
第 75 行中x 坐标存储在event.pos[0]中y 坐标存储在event.pos[1]中。第 75 行创建一个新的Rect对象来表示一个新的食物方块并将其放置在MOUSEBUTTONUP事件发生的地方。通过向foods列表添加新的Rect对象代码在屏幕上显示一个新的食物方块。
除了可以由玩家自行添加外食物方块还可以通过第 77 到 81 行的代码自动生成 foodCounter 1if foodCounter NEWFOOD:# Add new food.foodCounter 0foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH -FOODSIZE), random.randint(0, WINDOWHEIGHT - FOODSIZE),FOODSIZE, FOODSIZE))变量foodCounter跟踪应添加食物的频率。每次游戏循环迭代时foodCounter在第 77 行增加1。
一旦foodCounter大于或等于常量NEWFOODfoodCounter将被重置并且通过第 81 行生成一个新的食物方块。您可以通过调整第 21 行上的NEWFOOD来改变添加新食物方块的速度。
84 行只是用白色填充窗口表面我们在“处理玩家退出时”和第 279 页中已经讨论过了所以我们将继续讨论玩家如何在屏幕上移动。
在窗口中移动玩家
我们已将移动变量moveDownmoveUpmoveLeft和moveRight设置为True或False具体取决于玩家按下了哪些键。现在我们需要移动玩家的方框该方框由存储在player中的pygame.Rect对象表示。我们将通过调整player的 x 和 y 坐标来实现这一点。 # Move the player.if moveDown and player.bottom WINDOWHEIGHT:player.top MOVESPEEDif moveUp and player.top 0:player.top - MOVESPEEDif moveLeft and player.left 0:player.left - MOVESPEEDif moveRight and player.right WINDOWWIDTH:player.right MOVESPEED如果moveDown设置为True并且玩家的方框底部不在窗口的底部之下则第 88 行将通过将MOVESPEED添加到玩家当前的top属性来向下移动玩家的方框。第 89 到 94 行对其他三个方向执行相同的操作。
在窗口上绘制玩家
第 97 行在窗口上绘制玩家的方框 # Draw the player onto the surface.pygame.draw.rect(windowSurface, BLACK, player)在移动方框之后第 97 行将其绘制在新位置。传递给第一个参数的windowSurface告诉 Python 在哪个Surface对象上绘制矩形。存储在BLACK变量中的(0, 0, 0)告诉 Python 绘制黑色矩形。存储在player变量中的Rect对象告诉 Python 要绘制的矩形的位置和大小。
检查碰撞
在绘制食物方块之前程序需要检查玩家的方框是否与任何方块重叠。如果是则需要从foods列表中删除该方块。这样Python 就不会绘制任何盒子已经吃掉的食物方块。
我们将在第 101 行使用所有Rect对象都具有的碰撞检测方法colliderect() # Check whether the player has intersected with any food squares.for food in foods[:]:if player.colliderect(food):foods.remove(food)在每次for循环迭代中将foods复数列表中的当前食物方块放入变量food单数中。pygame.Rect对象的colliderect()方法将玩家矩形的pygame.Rect对象作为参数并在两个矩形发生碰撞时返回True如果它们没有发生碰撞则返回False。如果为True第 102 行将从foods列表中移除重叠的食物方块。
不要在迭代列表时更改列表
请注意这个for循环与我们以前看到的任何其他for循环略有不同。如果您仔细看第 100 行它并不是在foods上进行迭代而是在foods[:]上进行迭代。
记住切片的工作原理。foods[:2]将计算列表的副本其中包含从开头到但不包括索引2的项目。foods[:]将为您提供包含从开头到结尾的项目的列表的副本。基本上foods[:]创建一个新列表其中包含foods中所有项目的副本。这是复制列表的一种更简洁的方法比如在第 10 章的井字棋游戏中getBoardCopy()函数所做的。
在迭代列表时您不能添加或删除项目。如果 foods 列表的大小始终在变化Python 可能会丢失 food 变量的下一个值应该是什么。想象一下当有人添加或删除果冻豆时要数出罐子里果冻豆的数量会有多困难。
但是如果您迭代列表的副本并且副本永远不会更改则从原始列表中添加或删除项目将不会成为问题。
在窗口上绘制食物方块
第 105 行和 106 行的代码类似于我们用来为玩家绘制黑色方框的代码 # Draw the food.for i in range(len(foods)):pygame.draw.rect(windowSurface, GREEN, foods[i])第 105 行循环遍历foods列表中的每个食物方块第 106 行将食物方块绘制到windowSurface上。
现在玩家和食物方块都在屏幕上窗口已准备好更新因此我们在第 109 行调用update()方法并通过在我们之前创建的Clock对象上调用tick()方法来完成程序 # Draw the window onto the screen.pygame.display.update()mainClock.tick(40)程序将继续通过游戏循环并保持更新直到玩家退出。
总结
本章介绍了碰撞检测的概念。在图形游戏中检测两个矩形之间的碰撞是如此普遍以至于pygame为pygame.Rect对象提供了自己的碰撞检测方法名为colliderect()。
这本书中的前几个游戏都是基于文本的。程序的输出是打印在屏幕上的文本输入是玩家在键盘上输入的文本。但是图形程序也可以接受键盘和鼠标输入。
此外这些程序可以在玩家按下或释放单个键时响应单个按键。玩家不必输入整个响应并按下 ENTER 键。这样可以实现即时反馈和更加互动的游戏。
这个交互式程序很有趣但让我们超越绘制矩形。在第 20 章中你将学习如何使用pygame加载图像和播放音效。
二十、使用声音和图像 原文inventwithpython.com/invent4thed/chapter20.html 译者飞龙 协议CC BY-NC-SA 4.0 在第 18 章和第 19 章中您学习了如何制作具有图形并可以接受键盘和鼠标输入的 GUI 程序。您还学会了如何绘制不同的形状。在本章中您将学习如何向游戏中添加声音、音乐和图像。
本章涵盖的主题 声音和图像文件 绘制和调整精灵的大小 添加音乐和声音 切换声音开关
使用精灵添加图像
sprite是屏幕上用作图形的一部分的单个二维图像。图 20-1 显示了一些示例 sprite。 图 20-1一些精灵的示例
精灵图像绘制在背景上。您可以水平翻转精灵图像使其面向另一边。您还可以在同一窗口上多次绘制相同的精灵图像并且可以调整精灵的大小使其比原始精灵图像大或小。背景图像也可以被视为一个大精灵。图 20-2 显示了精灵一起使用。 图 20-2一个完整的场景精灵绘制在背景上
下一个程序将演示如何使用pygame播放声音和绘制精灵。
声音和图像文件
精灵存储在计算机上的图像文件中。pygame可以使用几种图像格式。要了解图像文件使用的格式请查看文件名的末尾最后一个句点之后。这称为文件扩展名。例如文件player.png是 PNG 格式。pygame支持的图像格式包括 BMP、PNG、JPG 和 GIF。
您可以从网络浏览器下载图像。在大多数网络浏览器上您可以通过右键单击网页中的图像然后从出现的菜单中选择“保存”来这样做。记住您保存图像文件的硬盘位置因为您需要将下载的图像文件复制到与 Python 程序的*.py*文件相同的文件夹中。您还可以使用诸如 Microsoft Paint 或 Tux Paint 之类的绘图程序创建自己的图像。
pygame支持的声音文件格式为 MIDI、WAV 和 MP3。您可以像下载图像文件一样从互联网下载音效文件但音频文件必须是这三种格式之一。如果您的计算机有麦克风您还可以录制声音并制作自己的 WAV 文件以在游戏中使用。
Sprites and Sounds 程序的示例运行
本章的程序与第 19 章的碰撞检测程序相同。但是在本程序中我们将使用精灵而不是普通的方块。我们将使用一个人的精灵来代表玩家而不是黑色方块以及樱桃的精灵而不是绿色的食物方块。我们还将播放背景音乐并在玩家精灵吃掉樱桃时添加声音效果。
在这个游戏中玩家精灵将吃掉樱桃精灵并且在吃掉樱桃时它会变大。当您运行程序时游戏将看起来像图 20-3。 图 20-3Sprites and Sounds 游戏的屏幕截图
Sprites and Sounds 程序的源代码
开始一个新文件输入以下代码然后将其保存为spritesAndSounds.py。你可以从本书的网站www.nostarch.com/inventwithpython/下载我们在本程序中使用的图像和声音文件。将这些文件放在与spritesAndSounds.py程序相同的文件夹中。 如果在输入此代码后出现错误请使用在线的 diff 工具比较你输入的代码和书中的代码网址为www.nostarch.com/inventwithpython#diff。
spritesAnd Sounds.py
import pygame, sys, time, random
from pygame.locals import *# Set up pygame.
pygame.init()
mainClock pygame.time.Clock()# Set up the window.
WINDOWWIDTH 400
WINDOWHEIGHT 400
windowSurface pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),0, 32)
pygame.display.set_caption(Sprites and Sounds)# Set up the colors.
WHITE (255, 255, 255)# Set up the block data structure.
player pygame.Rect(300, 100, 40, 40)
playerImage pygame.image.load(player.png)
playerStretchedImage pygame.transform.scale(playerImage, (40, 40))
foodImage pygame.image.load(cherry.png)
foods []
for i in range(20):foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - 20),random.randint(0, WINDOWHEIGHT - 20), 20, 20))foodCounter 0
NEWFOOD 40# Set up keyboard variables.
moveLeft False
moveRight False
moveUp False
moveDown FalseMOVESPEED 6# Set up the music.
pickUpSound pygame.mixer.Sound(pickup.wav)
pygame.mixer.music.load(background.mid)
pygame.mixer.music.play(-1, 0.0)
musicPlaying True# Run the game loop.
while True:# Check for the QUIT event.for event in pygame.event.get():if event.type QUIT:pygame.quit()sys.exit()if event.type KEYDOWN:# Change the keyboard variables.if event.key K_LEFT or event.key K_a:moveRight FalsemoveLeft Trueif event.key K_RIGHT or event.key K_d:moveLeft FalsemoveRight Trueif event.key K_UP or event.key K_w:moveDown FalsemoveUp Trueif event.key K_DOWN or event.key K_s:moveUp FalsemoveDown Trueif event.type KEYUP:if event.key K_ESCAPE:pygame.quit()sys.exit()if event.key K_LEFT or event.key K_a:moveLeft Falseif event.key K_RIGHT or event.key K_d:moveRight Falseif event.key K_UP or event.key K_w:moveUp Falseif event.key K_DOWN or event.key K_s:moveDown Falseif event.key K_x:player.top random.randint(0, WINDOWHEIGHT -player.height)player.left random.randint(0, WINDOWWIDTH -player.width)if event.key K_m:if musicPlaying:pygame.mixer.music.stop()else:pygame.mixer.music.play(-1, 0.0)musicPlaying not musicPlayingif event.type MOUSEBUTTONUP:foods.append(pygame.Rect(event.pos[0] - 10,event.pos[1] - 10, 20, 20))foodCounter 1if foodCounter NEWFOOD:# Add new food.foodCounter 0foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - 20),random.randint(0, WINDOWHEIGHT - 20), 20, 20))# Draw the white background onto the surface.windowSurface.fill(WHITE)# Move the player.if moveDown and player.bottom WINDOWHEIGHT:player.top MOVESPEEDif moveUp and player.top 0:player.top - MOVESPEEDif moveLeft and player.left 0:player.left - MOVESPEEDif moveRight and player.right WINDOWWIDTH:player.right MOVESPEED# Draw the block onto the surface.windowSurface.blit(playerStretchedImage, player)# Check whether the block has intersected with any food squares.for food in foods[:]:if player.colliderect(food):foods.remove(food)player pygame.Rect(player.left, player.top,player.width 2, player.height 2)playerStretchedImage pygame.transform.scale(playerImage,(player.width, player.height))if musicPlaying:pickUpSound.play()# Draw the food.for food in foods:windowSurface.blit(foodImage, food)# Draw the window onto the screen.pygame.display.update()mainClock.tick(40)设置窗口和数据结构
这个程序中的大部分代码与第 19 章中的碰撞检测程序相同。我们只关注添加精灵和声音的部分。首先在第 12 行让我们将标题栏的标题设置为描述这个程序的字符串
pygame.display.set_caption(Sprites and Sounds)为了设置标题你需要将字符串Sprites and Sounds传递给pygame.display.set_caption()函数。
添加一个精灵
现在我们已经设置好标题我们需要实际的精灵。我们将使用三个变量来表示玩家而不是之前程序中只使用一个。
# Set up the block data structure.
player pygame.Rect(300, 100, 40, 40)
playerImage pygame.image.load(player.png)
playerStretchedImage pygame.transform.scale(playerImage, (40, 40))
foodImage pygame.image.load(cherry.png)第 18 行的player变量将存储一个Rect对象用于跟踪玩家的位置和大小。player变量不包含玩家的图像。在程序开始时玩家的左上角位于(300, 100)玩家的初始高度和宽度为 40 像素。
表示玩家的第二个变量是第 19 行的playerImage。pygame.image.load()函数传递了要加载的图像文件的文件名字符串。返回值是一个Surface对象其中包含图像文件中的图形。我们将这个Surface对象存储在playerImage中。
改变精灵的大小
在第 20 行我们将使用pygame.transform模块中的一个新函数。pygame.transform.scale()函数可以缩小或放大精灵。第一个参数是一个带有图像的Surface对象。第二个参数是一个元组表示第一个参数中图像的新宽度和高度。scale()函数返回一个带有以新尺寸绘制的图像的Surface对象。在本章的程序中当玩家吃更多樱桃时我们将使玩家精灵拉伸变大。我们将原始图像存储在playerImage变量中而拉伸后的图像存储在playerStretchedImage变量中。
在第 21 行我们再次调用load()来创建一个带有樱桃图像的Surface对象。确保player.png和cherry.png文件与spritesAndSounds.py文件在同一个文件夹中否则pygame将无法找到它们并报错。
设置音乐和声音
接下来需要加载声音文件。pygame中有两个用于声音的模块。pygame.mixer模块可以在游戏过程中播放短声音效果。pygame.mixer.music模块可以播放背景音乐。
添加声音文件
调用pygame.mixer.Sound()构造函数来创建一个pygame.mixer.Sound对象简称为Sound对象。这个对象有一个play()方法当调用时会播放声音效果。
# Set up the music.
pickUpSound pygame.mixer.Sound(pickup.wav)
pygame.mixer.music.load(background.mid)
pygame.mixer.music.play(-1, 0.0)
musicPlaying True第 39 行调用pygame.mixer.music.load()来加载背景音乐第 40 行调用pygame.mixer.music.play()来开始播放它。第一个参数告诉pygame在第一次播放后播放背景音乐的次数。因此传递5会导致pygame播放背景音乐六次。在这里我们传递参数-1这是一个特殊值使背景音乐永远重复播放。
play()的第二个参数是开始播放声音文件的时间点。传递0.0将从开头播放背景音乐。传递2.5将从开头开始播放背景音乐 2.5 秒。
最后musicPlaying变量具有一个布尔值告诉程序是否应该播放背景音乐和声音效果。给玩家选择在没有声音的情况下运行程序是很好的。
切换声音的开关
按 M 键可以打开或关闭背景音乐。如果musicPlaying设置为True则当前正在播放背景音乐我们应该通过调用pygame.mixer.music.stop()来停止它。如果musicPlaying设置为False则当前没有播放背景音乐我们应该通过调用play()来开始播放。第 79 到 84 行使用if语句来实现这一点 if event.key K_m:if musicPlaying:pygame.mixer.music.stop()else:pygame.mixer.music.play(-1, 0.0)musicPlaying not musicPlaying无论音乐是否正在播放我们都希望切换musicPlaying中的值。切换布尔值意味着将值设置为其当前值的相反值。musicPlaying not musicPlaying这一行将变量设置为False如果它当前为True或者如果它当前为False则将其设置为True。想象一下切换就像你打开或关闭灯开关时发生的事情切换灯开关会将其设置为相反的设置。
在窗口上绘制玩家
请记住存储在playerStretchedImage中的值是一个Surface对象。第 110 行使用blit()将玩家的精灵绘制到窗口的Surface对象上存储在windowSurface中 # Draw the block onto the surface.windowSurface.blit(playerStretchedImage, player)blit()方法的第二个参数是一个Rect对象指定在Surface对象上绘制精灵的位置。程序使用存储在player中的Rect对象它跟踪玩家在窗口中的位置。
检查碰撞
这段代码与以前的程序中的代码类似但有几行新代码 if player.colliderect(food):foods.remove(food)player pygame.Rect(player.left, player.top,player.width 2, player.height 2)playerStretchedImage pygame.transform.scale(playerImage,(player.width, player.height))if musicPlaying:pickUpSound.play()当玩家精灵吃掉樱桃之一时其大小增加两个像素的高度和宽度。在第 116 行一个比旧的Rect对象大两个像素的新Rect对象将被分配为player的新值。
虽然Rect对象表示玩家的位置和大小但玩家的图像存储在playerStretchedImage中作为Surface对象。在第 117 行程序通过调用scale()创建一个新的拉伸图像。
拉伸图像通常会使图像略微失真。如果你不断地重新拉伸已经拉伸过的图像失真会迅速累积。但通过每次将原始图像拉伸到新的尺寸——通过传递playerImage而不是playerStretchedImage作为scale()的第一个参数——你只会使图像失真一次。
最后第 119 行调用存储在pickUpSound变量中的Sound对象上的play()方法。但只有当musicPlaying设置为True时才会这样做这意味着声音已打开。
在窗口上绘制樱桃
在以前的程序中你调用pygame.draw.rect()函数为foods列表中存储的每个Rect对象绘制一个绿色的正方形。然而在这个程序中你想要绘制樱桃精灵。调用blit()方法并传递存储在foodImage中的Surface对象上面绘制了樱桃图像 # Draw the food.for food in foods:windowSurface.blit(foodImage, food)food变量包含foods中的每个Rect对象在每次for循环中告诉blit()方法在哪里绘制foodImage。
总结
你已经为你的游戏添加了图像和声音。这些称为精灵的图像看起来比以前的程序中使用的简单绘制的形状要好得多。精灵可以被缩放即拉伸到更大或更小的尺寸因此我们可以以任何我们想要的尺寸显示精灵。本章介绍的游戏还有一个背景并播放声音效果。
现在我们知道如何创建窗口显示精灵绘制基本图形收集键盘和鼠标输入播放声音并实现碰撞检测我们准备在pygame中创建一个图形游戏。第 21 章将所有这些元素结合起来打造我们迄今为止最先进的游戏。
二十一、有声音和图像的《躲避者》游戏 原文inventwithpython.com/invent4thed/chapter21.html 译者飞龙 协议CC BY-NC-SA 4.0 前面的四章介绍了 pygame 模块并演示了如何使用它的许多功能。在本章中我们将利用这些知识创建一个名为《躲避者》的图形游戏。
本章涵盖的主题 pygame.FULLSCREEN 标志 move_ip() Rect 方法 实现作弊码 修改《躲避者》游戏
在《躲避者》游戏中玩家控制一个精灵玩家角色必须躲避从屏幕顶部掉落的一大堆坏人。玩家能够躲避坏人的时间越长他们的得分就会越高。
只是为了好玩我们还将在这个游戏中添加一些作弊模式。如果玩家按住 X 键每个坏人的速度都会降低到超慢的速度。如果玩家按住 Z 键坏人将会改变方向向上而不是向下移动。
基本 pygame 数据类型的回顾
在我们开始制作《躲避者》之前让我们回顾一下 pygame 中使用的一些基本数据类型
pygame.Rect
Rect 对象表示矩形空间的位置和大小。位置由 Rect 对象的 topleft 属性或 topright、bottomleft 和 bottomright 属性确定。这些角属性是 x 和 y 坐标的整数元组。大小由 width 和 height 属性确定这些属性是指示矩形有多长或多高的整数像素。Rect 对象有一个 colliderect() 方法用于检查它们是否与另一个 Rect 对象发生碰撞。
pygame.Surface
Surface 对象是有色像素区域。Surface 对象表示一个矩形图像而 Rect 对象只表示一个矩形空间和位置。Surface 对象有一个 blit() 方法用于将一个 Surface 对象上的图像绘制到另一个 Surface 对象上。pygame.display.set_mode() 函数返回的 Surface 对象是特殊的因为在该 Surface 对象上绘制的任何东西在调用 pygame.display.update() 时会显示在用户的屏幕上。
pygame.event.Event
pygame.event 模块在用户提供键盘、鼠标或其他输入时生成 Event 对象。pygame.event.get() 函数返回这些 Event 对象的列表。您可以通过检查其 type 属性来确定 Event 对象的类型。QUIT、KEYDOWN 和 MOUSEBUTTONUP 是一些事件类型的示例有关所有事件类型的完整列表请参见“处理事件”第 292 页。
pygame.font.Font
pygame.font 模块使用 Font 数据类型表示 pygame 中文本使用的字体。传递给 pygame.font.SysFont() 的参数是字体名称的字符串通常传递 None 作为字体名称以获取默认系统字体和字体大小的整数。
pygame.time.Clock
pygame.time 模块中的 Clock 对象有助于防止我们的游戏运行得比玩家能看到的更快。Clock 对象有一个 tick() 方法可以传递我们希望游戏运行的每秒帧数FPS。FPS 越高游戏运行得越快。
《躲避者》的示例运行
当您运行这个程序时游戏将会看起来像图 21-1。 图 21-1《躲避者》游戏的屏幕截图
《躲避者》的源代码
在一个新文件中输入以下代码并将其保存为 dodger.py。您可以从 www.nostarch.com/inventwithpython/ 下载代码、图像和声音文件。将图像和声音文件放在与 dodger.py 相同的文件夹中。 如果在输入此代码后出现错误请使用在线 diff 工具将你输入的代码与本书代码进行比较网址为 www.nostarch.com/inventwithpython#diff。
dodger.py
import pygame, random, sys
from pygame.locals import *WINDOWWIDTH 600
WINDOWHEIGHT 600
TEXTCOLOR (0, 0, 0)
BACKGROUNDCOLOR (255, 255, 255)
FPS 60
BADDIEMINSIZE 10
BADDIEMAXSIZE 40
BADDIEMINSPEED 1
BADDIEMAXSPEED 8
ADDNEWBADDIERATE 6
PLAYERMOVERATE 5def terminate():pygame.quit()sys.exit()def waitForPlayerToPressKey():while True:for event in pygame.event.get():if event.type QUIT:terminate()if event.type KEYDOWN:if event.key K_ESCAPE: # Pressing ESC quits.terminate()returndef playerHasHitBaddie(playerRect, baddies):for b in baddies:if playerRect.colliderect(b[rect]):return Truereturn Falsedef drawText(text, font, surface, x, y):textobj font.render(text, 1, TEXTCOLOR)textrect textobj.get_rect()textrect.topleft (x, y)surface.blit(textobj, textrect)# Set up pygame, the window, and the mouse cursor.
pygame.init()
mainClock pygame.time.Clock()
windowSurface pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption(Dodger)
pygame.mouse.set_visible(False)# Set up the fonts.
font pygame.font.SysFont(None, 48)# Set up sounds.
gameOverSound pygame.mixer.Sound(gameover.wav)
pygame.mixer.music.load(background.mid)# Set up images.
playerImage pygame.image.load(player.png)
playerRect playerImage.get_rect()
baddieImage pygame.image.load(baddie.png)# Show the Start screen.
windowSurface.fill(BACKGROUNDCOLOR)
drawText(Dodger, font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))
drawText(Press a key to start., font, windowSurface,(WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) 50)
pygame.display.update()
waitForPlayerToPressKey()topScore 0
while True:# Set up the start of the game.baddies []score 0playerRect.topleft (WINDOWWIDTH / 2, WINDOWHEIGHT - 50)moveLeft moveRight moveUp moveDown FalsereverseCheat slowCheat FalsebaddieAddCounter 0pygame.mixer.music.play(-1, 0.0)while True: # The game loop runs while the game part is playing.score 1 # Increase score.for event in pygame.event.get():if event.type QUIT:terminate()if event.type KEYDOWN:if event.key K_z:reverseCheat Trueif event.key K_x:slowCheat Trueif event.key K_LEFT or event.key K_a:moveRight FalsemoveLeft Trueif event.key K_RIGHT or event.key K_d:moveLeft FalsemoveRight Trueif event.key K_UP or event.key K_w:moveDown FalsemoveUp Trueif event.key K_DOWN or event.key K_s:moveUp FalsemoveDown Trueif event.type KEYUP:if event.key K_z:reverseCheat Falsescore 0if event.key K_x:slowCheat Falsescore 0if event.key K_ESCAPE:terminate()if event.key K_LEFT or event.key K_a:moveLeft Falseif event.key K_RIGHT or event.key K_d:moveRight Falseif event.key K_UP or event.key K_w:moveUp Falseif event.key K_DOWN or event.key K_s:moveDown Falseif event.type MOUSEMOTION:# If the mouse moves, move the player to the cursor.playerRect.centerx event.pos[0]playerRect.centery event.pos[1]# Add new baddies at the top of the screen, if needed.if not reverseCheat and not slowCheat:baddieAddCounter 1if baddieAddCounter ADDNEWBADDIERATE:baddieAddCounter 0baddieSize random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)newBaddie {rect: pygame.Rect(random.randint(0,WINDOWWIDTH - baddieSize), 0 - baddieSize,baddieSize, baddieSize),speed: random.randint(BADDIEMINSPEED,BADDIEMAXSPEED),surface:pygame.transform.scale(baddieImage,(baddieSize, baddieSize)),}baddies.append(newBaddie)# Move the player around.if moveLeft and playerRect.left 0:playerRect.move_ip(-1 * PLAYERMOVERATE, 0)if moveRight and playerRect.right WINDOWWIDTH:playerRect.move_ip(PLAYERMOVERATE, 0)if moveUp and playerRect.top 0:playerRect.move_ip(0, -1 * PLAYERMOVERATE)if moveDown and playerRect.bottom WINDOWHEIGHT:playerRect.move_ip(0, PLAYERMOVERATE)# Move the baddies down.for b in baddies:if not reverseCheat and not slowCheat:b[rect].move_ip(0, b[speed])elif reverseCheat:b[rect].move_ip(0, -5)elif slowCheat:b[rect].move_ip(0, 1)# Delete baddies that have fallen past the bottom.for b in baddies[:]:if b[rect].top WINDOWHEIGHT:baddies.remove(b)# Draw the game world on the window.windowSurface.fill(BACKGROUNDCOLOR)# Draw the score and top score.drawText(Score: %s % (score), font, windowSurface, 10, 0)drawText(Top Score: %s % (topScore), font, windowSurface,10, 40)# Draw the players rectangle.windowSurface.blit(playerImage, playerRect)# Draw each baddie.for b in baddies:windowSurface.blit(b[surface], b[rect])pygame.display.update()# Check if any of the baddies have hit the player.if playerHasHitBaddie(playerRect, baddies):if score topScore:topScore score # Set new top score.breakmainClock.tick(FPS)# Stop the game and show the Game Over screen.pygame.mixer.music.stop()gameOverSound.play()drawText(GAME OVER, font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))drawText(Press a key to play again., font, windowSurface,(WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) 50)pygame.display.update()waitForPlayerToPressKey()gameOverSound.stop()导入模块
Dodger 游戏导入了与之前的 pygame 程序相同的模块pygame、random、sys 和 pygame.locals。
import pygame, random, sys
from pygame.locals import *pygame.locals 模块包含了 pygame 使用的几个常量变量比如事件类型QUITKEYDOWN 等和键盘按键K_ESCAPEK_LEFT 等。通过使用 from pygame.locals import * 语法你可以在源代码中直接使用 QUIT 而不是 pygame.locals.QUIT。
设置常量变量
第 4 到 7 行设置了窗口尺寸、文本颜色和背景颜色的常量
WINDOWWIDTH 600
WINDOWHEIGHT 600
TEXTCOLOR (0, 0, 0)
BACKGROUNDCOLOR (255, 255, 255)我们使用常量变量是因为它们比我们手动输入的值更具描述性。例如windowSurface.fill(BACKGROUNDCOLOR) 这一行比 windowSurface.fill((255, 255, 255)) 更容易理解。
你可以通过改变常量变量来轻松改变游戏。通过改变第 4 行的 WINDOWWIDTH你会自动改变代码中所有使用 WINDOWWIDTH 的地方。如果你使用的是值 600那么你需要在代码中每次出现 600 的地方都进行修改。改变常量的值一次会更容易。
在第 8 行你设置了 FPS 的常量即每秒帧数你希望游戏运行的帧数。
FPS 60帧 是通过游戏循环的单次迭代绘制的屏幕。你将 FPS 传递给第 186 行的 mainClock.tick() 方法以便函数知道暂停程序的时间。这里 FPS 设置为 60但你可以将 FPS 更改为更高的值以使游戏运行更快或者更改为更低的值以减慢游戏速度。
第 9 到 13 行设置了更多的坏蛋下落的常量变量
BADDIEMINSIZE 10
BADDIEMAXSIZE 40
BADDIEMINSPEED 1
BADDIEMAXSPEED 8
ADDNEWBADDIERATE 6坏蛋的宽度和高度将在 BADDIEMINSIZE 和 BADDIEMAXSIZE 之间。坏蛋在屏幕上下落的速度将在 BADDIEMINSPEED 和 BADDIEMAXSPEED 之间每次游戏循环迭代的像素数。并且每经过 ADDNEWBADDIERATE 次游戏循环迭代一个新的坏蛋将被添加到窗口顶部。
最后PLAYERMOVERATE 存储了玩家角色在游戏循环的每次迭代中在窗口中移动的像素数如果角色正在移动
PLAYERMOVERATE 5通过增加这个数字你可以增加角色移动的速度。
定义函数
你将为这个游戏创建几个函数。terminate() 和 waitForPlayerToPressKey() 函数将分别结束和暂停游戏playerHasHitBaddie() 函数将跟踪玩家与坏蛋的碰撞drawText() 函数将在屏幕上绘制得分和其他文本。
结束和暂停游戏
pygame 模块要求你同时调用 pygame.quit() 和 sys.exit() 来结束游戏。第 16 到 18 行将它们都放入一个名为 terminate() 的函数中。
def terminate():pygame.quit()sys.exit()现在你只需要调用 terminate() 而不是同时调用 pygame.quit() 和 sys.exit()。
有时你会希望暂停程序直到玩家按下一个键比如在游戏开始时出现 Dodger 标题文本或者在结束时显示 Game Over 时。第 20 到 24 行创建了一个名为 waitForPlayerToPressKey() 的新函数
def waitForPlayerToPressKey():while True:for event in pygame.event.get():if event.type QUIT:terminate()在这个函数内部有一个无限循环只有在接收到 KEYDOWN 或 QUIT 事件时才会中断。在循环开始时pygame.event.get() 返回一个 Event 对象列表供检查。
如果玩家在程序等待玩家按键时关闭了窗口pygame 将生成一个 QUIT 事件在第 23 行通过 event.type 进行检查。如果玩家退出Python 将在第 24 行调用 terminate() 函数。
如果游戏收到KEYDOWN事件它应首先检查是否按下了 ESC 键 if event.type KEYDOWN:if event.key K_ESCAPE: # Pressing ESC quits.terminate()return如果玩家按下 ESC则程序应该终止。如果不是这种情况那么执行将跳过第 27 行的if块直接到return语句退出waitForPlayerToPressKey()函数。
如果没有生成QUIT或KEYDOWN事件代码将继续循环。由于循环什么也不做这将使游戏看起来像已经冻结直到玩家按下键。
跟踪坏人碰撞
如果玩家的角色与坏人之一发生碰撞则playerHasHitBaddie()函数将返回True
def playerHasHitBaddie(playerRect, baddies):for b in baddies:if playerRect.colliderect(b[rect]):return Truereturn Falsebaddies参数是坏人字典数据结构的列表。这些字典中的每一个都有一个’rect’键该键的值是表示坏人大小和位置的Rect对象。
playerRect也是一个Rect对象。Rect对象有一个名为colliderect()的方法如果Rect对象与传递给它的Rect对象发生碰撞则返回True。否则colliderect()返回False。
第 31 行的for循环遍历baddies列表中的每个坏人字典。如果任何这些坏人与玩家的角色发生碰撞则playerHasHitBaddie()返回True。如果代码成功遍历baddies列表中的所有坏人而没有检测到碰撞则playerHasHitBaddie()返回False。
向窗口绘制文本
在窗口上绘制文本涉及一些步骤我们通过drawText()来完成。这样当我们想要在屏幕上显示玩家得分或游戏结束文本时只需要调用一个函数。
def drawText(text, font, surface, x, y):textobj font.render(text, 1, TEXTCOLOR)textrect textobj.get_rect()textrect.topleft (x, y)surface.blit(textobj, textrect)首先第 37 行的render()方法调用创建了一个Surface对象以特定字体呈现文本。
接下来您需要知道Surface对象的大小和位置。您可以使用get_rect() Surface方法获取包含此信息的Rect对象。
从第 38 行的get_rect()返回的Rect对象中复制了Surface对象的宽度和高度信息。第 39 行通过为其topleft属性设置一个新的元组值来更改Rect对象的位置。
最后第 40 行将渲染文本的Surface对象绘制到传递给drawText()函数的Surface对象上。在pygame中显示文本比简单调用print()函数需要更多步骤。但是如果将此代码放入名为drawText()的单个函数中那么您只需要调用此函数即可在屏幕上显示文本。
初始化 pygame 和设置窗口
现在常量变量和函数已经完成我们将开始调用设置窗口和时钟的pygame函数
# Set up pygame, the window, and the mouse cursor.
pygame.init()
mainClock pygame.time.Clock()第 43 行通过调用pygame.init()函数设置了pygame。第 44 行创建了一个pygame.time.Clock()对象并将其存储在mainClock变量中。这个对象将帮助我们防止程序运行得太快。
第 45 行创建了一个用于窗口显示的新Surface对象
windowSurface pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))请注意pygame.display.set_mode()只传递了一个参数一个元组。pygame.display.set_mode()的参数不是两个整数而是一个包含两个整数的元组。您可以通过传递一个包含WINDOWWIDTH和WINDOWHEIGHT常量变量的元组来指定此Surface对象和窗口的宽度和高度。
pygame.display.set_mode()函数有第二个可选参数。您可以传递pygame.FULLSCREEN常量以使窗口填满整个屏幕。看一下对第 45 行的修改
windowSurface pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),pygame.FULLSCREEN)WINDOWWIDTH和WINDOWHEIGHT参数仍然用于窗口的宽度和高度但图像将被拉伸以适应屏幕。尝试在全屏模式和非全屏模式下运行程序。
第 46 行将窗口的标题设置为字符串’Dodger’
pygame.display.set_caption(Dodger)此标题将显示在窗口顶部的标题栏中。
在 Dodger 中鼠标光标不应该可见。您希望鼠标能够移动玩家角色在屏幕上移动但鼠标光标会妨碍角色图像。我们可以用一行代码使鼠标不可见
pygame.mouse.set_visible(False)调用pygame.mouse.set_visible(False)告诉pygame使光标不可见。
设置字体、声音和图像对象
由于我们在这个程序中在屏幕上显示文本我们需要为文本提供一个Font对象给pygame模块使用。第 50 行通过调用pygame.font.SysFont()创建了一个Font对象
# Set up the fonts.
font pygame.font.SysFont(None, 48)传递None使用默认字体。传递48给字体一个 48 点的大小。
接下来我们将创建Sound对象并设置背景音乐
# Set up sounds.
gameOverSound pygame.mixer.Sound(gameover.wav)
pygame.mixer.music.load(background.mid)pygame.mixer.Sound()构造函数创建一个新的Sound对象并将对此对象的引用存储在gameOverSound变量中。在您自己的游戏中您可以创建任意数量的Sound对象每个对象都有不同的声音文件。
pygame.mixer.music.load()函数加载一个声音文件用于背景音乐。这个函数不返回任何对象一次只能加载一个背景音乐文件。背景音乐将在游戏期间持续播放但Sound对象只会在玩家撞到坏人而输掉游戏时播放。
您可以为这个游戏使用任何 WAV 或 MIDI 文件。一些声音文件可以从本书的网站www.nostarch.com/inventwithpython/下载。您也可以为这个游戏使用自己的声音文件只要您将文件命名为gameover.wav和background.mid或者更改第 53 和 54 行使用的字符串以匹配您想要的文件名。
接下来您将加载用于玩家角色和坏人的图像文件
# Set up images.
playerImage pygame.image.load(player.png)
playerRect playerImage.get_rect()
baddieImage pygame.image.load(baddie.png)角色的图像存储在player.png中坏人的图像存储在baddie.png中。所有坏人看起来都一样所以你只需要一个图像文件。您可以从本书的网站www.nostarch.com/inventwithpython/下载这些图像。
显示开始画面
游戏刚开始时Python 应该在屏幕上显示 Dodger 标题。您还希望告诉玩家他们可以通过按任意键开始游戏。这个画面出现是为了让玩家在运行程序后有时间准备开始玩。
在 63 和 64 行我们编写代码调用drawText()函数
# Show the Start screen.
windowSurface.fill(BACKGROUNDCOLOR)
drawText(Dodger, font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))
drawText(Press a key to start., font, windowSurface,(WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) 50)
pygame.display.update()
waitForPlayerToPressKey()我们将向此函数传递五个参数 您希望出现的文本字符串 您希望字符串出现的字体 文本将被渲染到的Surface对象 在Surface对象上的 x 坐标用于绘制文本 在Surface对象上的 y 坐标用于绘制文本
这可能看起来是一个很多参数的函数调用但请记住每次调用此函数调用将替换五行代码。这缩短了程序并使查找错误变得更容易因为要检查的代码更少。
waitForPlayerToPressKey()函数通过循环暂停游戏直到生成KEYDOWN事件。然后执行中断循环程序继续运行。
开始游戏
现在所有函数都已定义我们可以开始编写主游戏代码。第 68 行及以后将调用我们之前定义的函数。程序首次运行时topScore变量的值为0。每当玩家输掉游戏并且得分大于当前最高分时最高分将被替换为这个更大的分数。
topScore 0
while True:从第 69 行开始的无限循环在技术上不是游戏循环。游戏循环处理游戏运行时的事件和绘制窗口。相反这个while循环在每次玩家开始新游戏时迭代。当玩家输掉游戏并且游戏重置时程序的执行会循环回到第 69 行。
一开始您还希望将baddies设置为空列表 # Set up the start of the game.baddies []score 0baddies变量是一个包含以下键的字典对象列表
rect’描述了坏人的位置和大小的Rect对象。
speed’坏人下落的速度。这个整数表示每次游戏循环迭代的像素。
surface’拥有缩放的坏人图像绘制在上面的Surface对象。这是绘制到pygame.display.set_mode()返回的Surface对象的Surface。
第 72 行将玩家的分数重置为0。
玩家的起始位置在屏幕中央距离底部 50 像素由第 73 行设置 playerRect.topleft (WINDOWWIDTH / 2, WINDOWHEIGHT - 50)第 73 行元组的第一个项目是左边缘的 x 坐标第二个项目是顶边缘的 y 坐标。
接下来我们设置玩家移动和作弊的变量 moveLeft moveRight moveUp moveDown FalsereverseCheat slowCheat FalsebaddieAddCounter 0移动变量moveLeft、moveRight、moveUp和moveDown都设置为False。reverseCheat和slowCheat变量也设置为False。只有当玩家按住 Z 和 X 键启用这些作弊时它们才会被设置为True。
baddieAddCounter变量是一个计数器告诉程序何时在屏幕顶部添加一个新的坏人。baddieAddCounter的值每次游戏循环迭代时增加 1。这类似于“添加新食物方块”中的代码在第 295 页。
当baddieAddCounter等于ADDNEWBADDIERATE时baddieAddCounter重置为0并在屏幕顶部添加一个新的坏人。这个检查稍后在第 130 行进行。
背景音乐在第 77 行开始播放调用了pygame.mixer.music.play()函数 pygame.mixer.music.play(-1, 0.0)因为第一个参数是-1pygame会无限重复播放音乐。第二个参数是一个浮点数表示音乐开始播放的秒数。传递0.0意味着音乐从头开始播放。
游戏循环
游戏循环的代码不断更新游戏世界的状态改变玩家和坏人的位置处理由pygame生成的事件并在屏幕上绘制游戏世界。所有这些都会在几十次每秒发生使游戏实时运行。
第 79 行是主游戏循环的开始 while True: # The game loop runs while the game part is playing.score 1 # Increase score.第 80 行在游戏循环的每次迭代中增加玩家的分数。玩家能够在不失去的情况下走得越久他们的分数就越高。循环只有在玩家输掉游戏或退出程序时才会退出。
处理键盘事件
程序将处理四种类型的事件QUIT、KEYDOWN、KEYUP和MOUSEMOTION。
第 82 行是事件处理代码的开始 for event in pygame.event.get():if event.type QUIT:terminate()它调用pygame.event.get()返回一个Event对象列表。每个Event对象表示自上次调用pygame.event.get()以来发生的事件。代码检查Event对象的type属性看看它是什么类型的事件然后相应地处理它。
如果Event对象的type属性等于QUIT那么用户已经关闭了程序。QUIT常量变量是从pygame.locals模块导入的。
如果事件的类型是KEYDOWN玩家已经按下了一个键 if event.type KEYDOWN:if event.key K_z:reverseCheat Trueif event.key K_x:slowCheat True第 87 行检查事件是否描述了按下Z键条件为event.key K_z。如果条件为TruePython 将reverseCheat变量设置为True以激活反向作弊。类似地第 89 行检查是否按下X键以激活减速作弊。
第 91 到 102 行检查事件是否由玩家按下箭头或 WASD 键生成。这段代码类似于前几章的与键盘相关的代码。
如果事件的类型是KEYUP玩家已经释放了一个键 if event.type KEYUP:if event.key K_z:reverseCheat Falsescore 0if event.key K_x:slowCheat Falsescore 0第 105 行检查玩家是否释放了 Z 键这将停用反向作弊。在这种情况下第 106 行将reverseCheat设置为False第 107 行将分数重置为0。分数重置是为了阻止玩家使用作弊。
第 108 行到第 110 行对 X 键和慢速作弊做了同样的事情。释放 X 键时slowCheat设置为False玩家的分数重置为0。
在游戏进行期间玩家可以随时按 ESC 键退出 if event.key K_ESCAPE:terminate()第 111 行通过检查event.key K_ESCAPE来确定释放的键是否是 ESC。如果是第 112 行调用terminate()函数退出程序。
第 114 行到第 121 行检查玩家是否停止按住箭头或 WASD 键之一。在这种情况下代码将相应的移动变量设置为False。这类似于第 19 章和第 20 章程序中的移动代码。
处理鼠标移动
现在你已经处理了键盘事件让我们处理可能生成的任何鼠标事件。《躲避球》游戏如果玩家点击了鼠标按钮不会有任何反应但是当玩家移动鼠标时会有反应。这给玩家在游戏中控制角色的两种方式键盘或鼠标。
MOUSEMOTION事件在鼠标移动时生成 if event.type MOUSEMOTION:# If the mouse moves, move the player to the cursor.playerRect.centerx event.pos[0]playerRect.centery event.pos[1]type设置为MOUSEMOTION的Event对象还有一个名为pos的属性用于存储鼠标事件的位置。pos属性存储了鼠标光标在窗口中移动的 x 和 y 坐标的元组。如果事件的类型是MOUSEMOTION玩家的角色将移动到鼠标光标的位置。
第 125 行和第 126 行将玩家角色的中心 x 和 y 坐标设置为鼠标光标的 x 和 y 坐标。
添加新的坏蛋
在游戏循环的每次迭代中代码将baddieAddCounter变量增加一 # Add new baddies at the top of the screen, if needed.if not reverseCheat and not slowCheat:baddieAddCounter 1只有在作弊未启用时才会发生。请记住只要按住 Z 和 X 键reverseCheat和slowCheat就会设置为True。在按住 Z 和 X 键时baddieAddCounter不会增加。因此新的坏蛋不会出现在屏幕顶部。
当baddieAddCounter达到ADDNEWBADDIERATE中的值时是时候在屏幕顶部添加一个新的坏蛋了。首先将baddieAddCounter重置为0 if baddieAddCounter ADDNEWBADDIERATE:baddieAddCounter 0baddieSize random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)newBaddie {rect: pygame.Rect(random.randint(0,WINDOWWIDTH - baddieSize), 0 - baddieSize,baddieSize, baddieSize),speed: random.randint(BADDIEMINSPEED,BADDIEMAXSPEED),surface:pygame.transform.scale(baddieImage,(baddieSize, baddieSize)),}第 132 行生成了坏蛋的像素大小。大小将是BADDIEMINSIZE和BADDIEMAXSIZE之间的随机整数这些常量分别在第 9 行和第 10 行设置为10和40。
第 133 行是创建新坏蛋数据结构的地方。请记住baddies的数据结构只是一个带有键rect、speed和surface的字典。rect键保存对存储坏蛋位置和大小的Rect对象的引用。对pygame.Rect()构造函数的调用有四个参数区域顶部边缘的 x 坐标、区域左边缘的 y 坐标、像素宽度和像素高度。
坏蛋需要出现在窗口顶部的随机位置因此将random.randint(0, WINDOWWIDTH - baddieSize)传递给坏蛋左边缘的 x 坐标。之所以传递WINDOWWIDTH - baddieSize而不是WINDOWWIDTH是因为如果坏蛋的左边缘太靠右那么坏蛋的一部分将超出窗口边缘不会在屏幕上可见。
坏蛋的底边应该位于窗口顶边的上方。窗口顶边的 y 坐标是0。为了将坏蛋的底边放在那里将顶边设置为0 - baddieSize。
坏蛋的宽度和高度应该相同图像是一个正方形因此将baddieSize传递给第三个和第四个参数。
坏人在屏幕上移动的速度设置在speed键中。将其设置为BADDIEMINSPEED和BADDIEMAXSPEED之间的随机整数。
然后在第 138 行将新创建的坏人数据结构添加到坏人数据结构列表中 baddies.append(newBaddie)程序使用这个列表来检查玩家是否与任何坏人发生了碰撞并确定在窗口上绘制坏人的位置。
移动玩家角色和坏人
四个移动变量moveLeft、moveRight、moveUp和moveDown在pygame生成KEYDOWN和KEYUP事件时分别设置为True和False。
如果玩家的角色向左移动并且玩家角色的左边缘大于0即窗口的左边缘那么playerRect应该向左移动 # Move the player around.if moveLeft and playerRect.left 0:playerRect.move_ip(-1 * PLAYERMOVERATE, 0)move_ip()方法将Rect对象的位置水平或垂直移动一定数量的像素。move_ip()的第一个参数是将Rect对象向右移动的像素数要向左移动传递一个负整数。第二个参数是将Rect对象向下移动的像素数要向上移动传递一个负整数。例如playerRect.move_ip(10, 20)将使Rect对象向右移动 10 个像素向下移动 20 个像素playerRect.move_ip(-5, -15)将使Rect对象向左移动 5 个像素向上移动 15 个像素。
move_ip()末尾的ip代表“原地”。这是因为该方法改变了Rect对象本身而不是返回具有更改的新Rect对象。还有一个move()方法它不会改变Rect对象而是在新位置创建并返回一个新的Rect对象。
你总是会移动playerRect对象的像素数为PLAYERMOVERATE。要得到一个整数的负形式将其乘以-1。在第 142 行由于PLAYERMOVERATE中存储了5表达式-1 * PLAYERMOVERATE的值为-5。因此调用playerRect.move_ip(-1 * PLAYERMOVERATE, 0)将使playerRect的位置向左移动 5 个像素。
第 143 到 148 行对其他三个方向进行了相同的操作右、上和下。 if moveRight and playerRect.right WINDOWWIDTH:playerRect.move_ip(PLAYERMOVERATE, 0)if moveUp and playerRect.top 0:playerRect.move_ip(0, -1 * PLAYERMOVERATE)if moveDown and playerRect.bottom WINDOWHEIGHT:playerRect.move_ip(0, PLAYERMOVERATE)在第 143 到 148 行的三个if语句中检查其移动变量是否设置为True并且玩家的Rect对象的边缘是否在窗口内。然后调用move_ip()来移动Rect对象。
现在代码循环遍历baddies列表中的每个坏人数据结构使它们向下移动一点 # Move the baddies down.for b in baddies:if not reverseCheat and not slowCheat:b[rect].move_ip(0, b[speed])如果没有激活任何作弊码那么坏人的位置向下移动与其速度存储在speed键中相等的像素数。
实现作弊码
如果反向作弊被激活那么坏人应该向上移动 5 个像素 elif reverseCheat:b[rect].move_ip(0, -5)将move_ip()的第二个参数传递为-5将使Rect对象向上移动 5 个像素。
如果慢速作弊被激活那么坏人仍然应该向下移动但速度为每次游戏循环迭代 1 个像素 elif slowCheat:b[rect].move_ip(0, 1)当慢速作弊被激活时坏人的正常速度同样存储在坏人数据结构的speed键中将被忽略。
移除坏人
任何掉到窗口底部以下的坏人都应该从baddies列表中移除。记住不应该在迭代列表时添加或移除列表项。不要使用for循环迭代baddies列表而是使用baddies列表的副本进行迭代。要创建这个副本使用空切片操作符[:] # Delete baddies that have fallen past the bottom.for b in baddies[:]:第 160 行的for循环使用变量b来遍历baddies[:]中的当前项。如果坏人在窗口的底部以下我们应该将其移除这在第 162 行中完成 if b[rect].top WINDOWHEIGHT:baddies.remove(b)b字典是baddies[:]列表中的当前坏蛋数据结构。列表中的每个坏蛋数据结构都是一个带有rect键的字典该键存储一个Rect对象。因此b[rect]是坏蛋的Rect对象。最后top属性是矩形区域顶部边缘的 y 坐标。请记住y 坐标向下增加。因此b[rect].top WINDOWHEIGHT将检查坏蛋的顶部边缘是否在窗口底部以下。如果这个条件为True那么第 162 行将从baddies列表中删除坏蛋数据结构。
绘制窗口
在更新所有数据结构之后应使用pygame的图像函数绘制游戏世界。因为游戏循环每秒执行多次当坏蛋和玩家在新位置绘制时它们看起来就像是平稳移动的。
在绘制任何其他内容之前第 165 行填充整个屏幕以擦除先前绘制的任何内容 # Draw the game world on the window.windowSurface.fill(BACKGROUNDCOLOR)请记住windowSurface中的Surface对象很特殊因为它是由pygame.display.set_mode()返回的。因此在该Surface对象上绘制的任何内容都将在调用pygame.display.update()后出现在屏幕上。
绘制玩家得分
第 168 和 169 行在窗口的左上角渲染了当前得分和最高得分的文本。 # Draw the score and top score.drawText(Score: %s % (score), font, windowSurface, 10, 0)drawText(Top Score: %s % (topScore), font, windowSurface,10, 40)Score: %s % (score) 表达式使用字符串插值将score变量的值插入字符串中。这个字符串、存储在font变量中的Font对象、用于绘制文本的Surface对象以及文本应放置的 x 和 y 坐标都被传递给drawText()方法该方法将处理对render()和blit()方法的调用。
对于最高得分做同样的事情。将40作为 y 坐标传递而不是0这样最高得分的文本就会出现在当前得分的文本下方。
绘制玩家角色和坏蛋
关于玩家的信息保存在两个不同的变量中。playerImage是一个包含玩家角色图像的所有彩色像素的Surface对象。playerRect是一个存储玩家角色大小和位置的Rect对象。
blit()方法在windowSurface上绘制玩家角色的图像在playerImage中在playerRect的位置 # Draw the players rectangle.windowSurface.blit(playerImage, playerRect)第 175 行的for循环在windowSurface对象上绘制每个坏蛋 # Draw each baddie.for b in baddies:windowSurface.blit(b[surface], b[rect])baddies列表中的每个项目都是一个字典。字典的surface和rect键包含了带有坏蛋图像的Surface对象和带有位置和大小信息的Rect对象。
现在所有内容都已经绘制到windowSurface上我们需要更新屏幕以便玩家可以看到其中的内容 pygame.display.update()通过调用update()将这个Surface对象绘制到屏幕上。
检查碰撞
第 181 行检查玩家是否与任何坏蛋发生碰撞调用playerHasHitBaddie()。如果玩家的角色与baddies列表中的任何一个坏蛋发生碰撞则此函数将返回True。否则该函数返回False。 # Check if any of the baddies have hit the player.if playerHasHitBaddie(playerRect, baddies):if score topScore:topScore score # Set new top score.break如果玩家的角色撞到了坏蛋并且当前得分高于最高得分那么第 182 和 183 行将更新最高得分。程序的执行会在第 184 行跳出游戏循环并移动到第 189 行结束游戏。
为了防止计算机尽可能快地运行游戏循环这对玩家来说太快了调用mainClock.tick()来暂停游戏很短的时间 mainClock.tick(FPS)这个暂停时间将足够长以确保每秒大约进行40次存储在FPS变量内部的值游戏循环迭代。
游戏结束画面
当玩家失败时游戏停止播放背景音乐并播放“游戏结束”音效 # Stop the game and show the Game Over screen.pygame.mixer.music.stop()gameOverSound.play()第 189 行调用pygame.mixer.music模块中的stop()函数来停止背景音乐。第 190 行调用gameOverSound中存储的Sound对象的play()方法。
然后第 192 行和第 193 行调用drawText()函数将“游戏结束”文本绘制到windowSurface对象上 drawText(GAME OVER, font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))drawText(Press a key to play again., font, windowSurface,(WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) 50)pygame.display.update()waitForPlayerToPressKey()第 194 行调用update()来将这个Surface对象绘制到屏幕上。在显示这个文本后游戏会停止直到玩家按下键调用waitForPlayerToPressKey()函数。
玩家按下键后程序执行从第 195 行的waitForPlayerToPressKey()调用返回。根据玩家按键的时间长短可能会播放“游戏结束”音效。为了在新游戏开始之前停止这个音效第 197 行调用gameOverSound.stop() gameOverSound.stop()我们的图形游戏就到这里了
修改躲避者游戏
你可能会发现游戏太容易或太难。幸运的是游戏很容易修改因为我们花时间使用常量变量而不是直接输入值。现在我们只需要修改常量变量中设置的值就可以改变游戏。
例如如果你想让游戏总体运行速度变慢可以将第 8 行的FPS变量更改为较小的值比如20。这将使坏人和玩家角色移动得更慢因为游戏循环每秒只执行20次而不是40次。
如果你只想减慢坏人的速度而不是玩家的速度那么将BADDIEMAXSPEED更改为较小的值比如4。这将使所有坏人在游戏循环中的每次迭代之间移动 1BADDIEMINSPEED中的值到 4 个像素而不是 1 到 8 个像素。
如果你想让游戏有更少但更大的坏人而不是许多较小的坏人那么将ADDNEWBADDIERATE增加到12BADDIEMINSIZE增加到40BADDIEMAXSIZE增加到80。现在坏人每 12 次游戏循环添加一次而不是每 6 次所以坏人的数量将减少一半。但为了保持游戏的趣味性坏人会更大。
保持基本游戏不变你可以修改任何常量变量从而显著影响游戏的玩法。不断尝试新的常量变量值直到找到最喜欢的值组合。
总结
与我们的文本游戏不同躲避者看起来真的像一款现代电脑游戏。它有图形和音乐并且使用鼠标。虽然pygame提供函数和数据类型作为构建块但是你作为程序员将它们组合在一起创造出有趣的互动游戏。
你可以做到这一切因为你知道如何逐步指导计算机做事一行一行地。通过使用计算机的语言你可以让它为你进行数字计算和绘图。这是一项有用的技能我希望你会继续学习更多关于 Python 编程的知识。还有很多东西要学
现在开始发挥你的想象力创造属于自己的游戏。祝你好运
上一页第 20 章 - 使用声音和图像