使用我们的新电子书中的分步说明，以有趣的方式了解 Python。Python 是目前最流行的程序设计语言之一。不管是为了工作还是娱乐为目的学习 Python，它都是一门功能强大且非常有用的编程语言。你可以创建应用程序来帮助你完成日常任务，创建你和朋友们喜欢玩的游戏，创建用于处理数据的脚本，创建用于生成或分析信息的应用程序等等。无论你计划使用程序设计语言做什么，我们都认为通过创建游戏来学习比通过处理数字或变换字符串来学习更为有趣。然而，如果你完全是一个编程的新手，当你能看到代码在视频游戏等熟悉的环境中工作时，你会更容易理解为什么要用代码做某事。你可能不会选择 Python 作为最好的编程语言（每个人对此都有自己的答案），但它不是一门令人恐惧的编程语言。 Python 可以使用很多关键字（例如 is 和 is not）代替符号（例如 = 和 !=）。它还能管理许多低级任务，因此你通常不必担心数据类型和垃圾收集之类的事情。通常，这意味着你马上就可以开始编写代码，而不会像在 C 或 Java 那样的复杂编程语言面前遇到挫折。为了帮助你学习 Python，我们编写了一本电子书，教你如何使用 Python 创建平台类视频游戏。在制作视频游戏的同时逐步通过课程学习 Python。另外一个好处是，你还将学习编程逻辑、语法、运算符等更多的内容。你可以在学习过程中立即看到结果，因此你学到的所有内容都会得到及时巩固。一分钟上手 PythonPython 是一种用途广泛的编程语言，这意味着它（与大多数语言一样）提供了函数来对数字和字符做处理的“简单技巧”。Linux 操作系统用户已经安装了 Python。 Mac 操作系统用户使用的是较旧版本的 Python，但是你可以从 Python.org 网站 安装最新版本。Windows 操作系统用户可以从这篇 在 Windows 上安装 Python 的文章中学习如何安装 Python。安装完成后，你可以启动交互式 Python Shell 进行算术运算：$ python3 >>> 5+6 11 >>> 11/2 5.5 >>> 11//2 5 >>> 11%2 1 从该示例可以了解，需要一些特殊的符号，但学过数学的人都最熟悉不过了。也许你不喜欢数字，而更喜欢字母：$ python3 >>> string = "hello world" >>> print(string) hello world >>> print(string.upper()) HELLO WORLD >>> print(string[0]) h >>> print(string[1]) e >>> print(string[2]) l >>> print(string[3]) l >>> print(string[4]) o 同样，相对地说基础的任务有特殊的符号表示法，但是即使没有说明，你也可能已经发现 [0] 和 [1] 符号表示法是将数据“切片”并且利用 print 函数将其中的数据显示在屏幕上。五分钟用上 Pygame如果你只想使用 Python 来创建一个视频游戏或任何超越基本计算的项目，这可能需要投入大量的学习、努力和时间。幸运的是，Python 诞生已有二十年了，开发者已经开发了代码库来帮助你（相对）轻松地完成典型的程序壮举。Pygame 是一套用于创建视频游戏的代码模块。它 不是唯一的这种类库，但是它是最古老的（不论好坏），因此在线上有很多文档和示例。首先学习 推荐的 Python 虚拟环境工作流程：$ python3 -m venv mycode/venv $ cd mycode $ source ./venv/bin/activate (venv)$ 进入虚拟环境后，可以安全地将 Pygame 安装到项目文件夹中：(venv)$ echo "pygame" >> requirements.txt (venv)$ python -m pip install -r requirements.txt [...] Installing collected packages: pygame Successfully installed pygame-x.y.z 现在你已经安装了 Pygame，就可以创建一个简单的演示应用程序。它比你想象的要容易。Python 可以进行所谓的面向对象编程（OOP），这是一个漂亮的计算机科学术语，用于描述当代码结构化时，就像你在使用代码创建物理对象一样。然而，程序员并没有受到迷惑。他们知道在编写代码时并不是真的在制造物理对象，但是这样有助于想象，因为这样你就可以了解编程世界的局限性。例如，如果你被困在一个荒岛上并想要一杯咖啡，那么你就必须收集一些黏土，做一个杯子，然后烘烤它。如果你足够聪明，先创建一个模具，以便每当需要另一个杯子时，都可以从模板中快速创建一个新杯子。即使每个杯子都来自相同的模板，它们在物理上也是独立的：如果一个杯子破裂，你还会有另一个杯子。你可以通过添加颜色或蚀刻使每个咖啡杯显得独一无二。在 Pygame 和许多编程任务中，你都会使用类似的逻辑。在定义之前，它不会出现在你的编程项目中。下面是如何在 Python 和 Pygame 程序中让咖啡杯出现。使用 Pygame 进行面向对象编程创建一个名为 main.py 的文件，并输入以下代码用以启动 Pygame 模块，并使用 Pygame 模板创建一个窗口：import pygame pygame.init() screen = pygame.display.set_mode((960,720)) 就像你可能在现实生活中使用模板来创建对象一样，你也可以使用 Pygame 提供的模板来创建一个 妖精 sprite （这是 Pygame 的视觉游戏对象术语）。在面向对象的编程中，class 表示对象的模板。在你的文档中输入以下代码：class Cup(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) # image img = pygame.image.load('coffeecup.png').convert() self.image = img # volume self.rect = self.image.get_rect() self.rect.x = 10 self.rect.y = 10 该代码块使用 Pygame 的 sprite 模板设计一个咖啡杯子妖精。由于 self.image 的存在，你的咖啡杯妖精有一个图像，而 self.rect 则赋予了它体积（宽度和高度）。这些是 Pygame 期望妖精拥有的属性，但是如果你要创建一个可玩的视频游戏，则可以为其指定任何其他所需的属性，例如健康点和得分。到目前为止，你所要做的就是创建一个窗口和一个用于咖啡杯的 模板 。你的游戏实际上还没有一个杯子。你的代码的最后一部分必须使用模板来生成杯子并将其添加到游戏世界中。如你所知，计算机运行速度非常快，因此从技术上讲，你到目前为止创建的代码只会运行一毫秒左右。编写图形计算机应用程序时，无论计算机是否认为已完成规定的任务，都必须强制其保持打开状态。程序员使用 无限循环 来执行此操作，该循环在 Python 中由 while True 语句表示（True 始终为真，因此循环永远不会结束）。无限循环可以确保你的应用程序保持打开状态足够长的时间，以使计算机用户可以查看和使用该应用程序：cup = Cup() while True: pygame.display.update() screen.blit(cup.image, cup.rect) 此代码示例从模板 Cup 创建杯子，然后使用 Pygame 函数更新显示。最后，使用 Pygame 的 blit 函数在其边框内绘制杯子的图像。获取图形在成功运行代码之前，你需要为咖啡杯准备一个图形。你可以在 FreeSVG.org 上找到许多 公用创作 咖啡杯图形。我用了 这个。将图形保存在项目目录中，并将其命名为 coffeecup.png。运行游戏启动应用程序：(venv)$ python ./main.py 下载 Python 游戏电子书Pygame 是一个功能强大的框架，除了在屏幕上绘制咖啡杯之外，你还可以做更多的事情。下载我们的免费电子书 更好地了解 Pygame 和 Python。via: https://opensource.com/article/20/10/learn-python-ebook作者：Seth Kenlon 选题：lujun9972 译者：stevenzdg988 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
通过添加声音到你的游戏中，听听当你的英雄战斗、跳跃、收集战利品时会发生什么。学习如何在这个 Pygame 系列的第十三篇文章中，创建一个声音平台类精灵。在 Python 3 中使用 Pygame 模块来创建电脑游戏的系列文章仍在进行中，这是第十三部分。先前的文章是:通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人在 Pygame 游戏中放置平台在你的 Python 游戏中模拟引力为你的 Python 平台类游戏添加跳跃功能使你的 Python 游戏玩家能够向前和向后跑在你的 Python 平台类游戏中放一些奖励添加计分到你的 Python 游戏在你的 Python 游戏中加入投掷机制Pygame 提供了一种简单的方法来集成声音到你的 Python 电脑游戏中。Pygame 的 mixer 模块 可以依据命令播放一个或多个声音，并且你也可以将这些声音混合在一起，例如，你能够在听到你的英雄收集奖励或跳过敌人声音的同时播放背景音乐。集成 mixer 模块到一个已存在的游戏中是很容易的，因此，与其给你代码示例来向你展示放置它们的位置，不如在这篇文章解释在你的应用程序中获得声音所需的四个步骤。启动 mixer首先，在你代码的设置部分，启动 mixer 进程。你的代码已经启动 Pygame 和 Pygame 字体，因此将它们归类到一起是一个很好的主意:pygame.init() pygame.font.init() pygame.mixer.init() # add this line 定义声音接下来，你必需定义你想要使用的声音。这样就要求你的计算机上有声音文件，就像使用字体就要求你有字体文件，使用图像就要求你有图像文件一样。你还必需把这些声音与你的游戏捆绑在一起，以便任何玩你游戏的人都有这些声音文件。为将一个声音与你的游戏捆绑在一起，首先在你的游戏目录中创建一个新的目录，就像你为你图像和字体创建的目录一样。称它为 sound:s = 'sound' 尽管在互联网上有很多声音文件，下载这些声音文件并将其与你的游戏一起分发并不一定是合法的。这看起来是很奇怪的，因为这么多来自著名电脑游戏的声音是流行文化的一部分，但法律就是这样运作的。如果你想与你的游戏一起分发一个声音文件，你必需找到一个开源或共创许可的声音文件，它们准许与游戏一起提供声音。这里有一些专门提供自由和合法的声音文件的网站，包括:Freesound 托管存储所有种类的音效。Incompetech 托管存储背景音乐。Open Game Art 托管存储一些音效和音乐。一些声音文件只要你给予作曲家或声音设计师一个致谢就可以自由使用。在与你的游戏捆绑前，仔细阅读使用条件！音乐家和声音设计师在声音上的工作就像你在代码上的工作一样努力，因此即使他们不要求，给予他们致谢也是极好的。给予你的声音源文件一些致谢，在一个名为 CREDIT 的文本文件中列出你使用的声音，并在你的游戏文件夹中放置该文本文件。你也可以尝试制作你自己的音乐。优秀的 LMMS 音频工作站易于使用，并带有很多有趣的声音。它在所有主要的平台上都可以使用，也可以导出为 Ogg Vorbis（OGG）音频格式。添加声音到 Pygame当你找到你喜欢的一个声音文件时，下载它。如果它是一个 ZIP 或 TAR 文件，提取它并将其移动到你游戏目录中的 sound 文件夹中。如果声音文件的名字带有空格或特殊字符，重新命名它。文件名称是完全随意的，它的名称越简单，你就越容易输入到你的代码中。大多数的电脑游戏使用 OGG 格式声音文件，因为这种格式可以占有较小空间而提供高质量的声音。当你下载一个声音文件时，它可能是一个 MP3、WAVE、FLAC 或者其它的音频格式。为保持你的文件的较高兼容性和降低下载文件大小，使用一个像 fre:ac 或 Miro 这样的工具来转换这些的文件格式为 Ogg 格式。例如，假设你已经下载一个称为 ouch.ogg 的声音文件。在你代码的设置部分中，创建一个变量，代表你想使用的声音文件：ouch = pygame.mixer.Sound(os.path.join(s, 'ouch.ogg')) 触发一个声音为使用一个声音，你所要做的就是在你想触发它的时候调用这个变量。例如，当你的玩家击中一名敌人时，触发 OUCH 声音效果:for enemy in enemy_hit_list: pygame.mixer.Sound.play(ouch) score -= 1 你可以为各种动作创建声音，例如，跳跃、收集奖励、投掷、碰撞，以及其他任何你能想象到的动作。添加背景音乐如果你有想在你的游戏的背景中播放的音乐或令人激动的音效，你可以使用 Pygame 中的 mixer 模块中的 music 函数。在你的设置部分中，加载音乐文件：music = pygame.mixer.music.load(os.path.join(s, 'music.ogg')) 然后，开始音乐:pygame.mixer.music.play(-1) -1 值告诉 Pygame 无限循环音乐文件。你可以将其设置为从 0 到更高的值之间的任意数值，以定义音乐在停止前循环多少次。享受音效音乐和声音可以为你的游戏添加很多韵味。尝试添加一些声音到你的 Pygame 工程!via: https://opensource.com/article/20/9/add-sound-python-game作者：Seth Kenlon 选题：lujun9972 译者：robsean 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
四处奔跑躲避敌人是一回事，反击敌人是另一回事。学习如何在这系列的第十二篇文章中在 Pygame 中创建平台游戏。这是仍在进行中的关于使用 Pygame 模块在 Python 3 中创建电脑游戏的第十二部分。先前的文章是：通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人在 Pygame 游戏中放置平台在你的 Python 游戏中模拟引力为你的 Python 平台类游戏添加跳跃功能使你的 Python 游戏玩家能够向前和向后跑在你的 Python 平台类游戏中放一些奖励添加计分到你的 Python 游戏我的上一篇文章本来是这一系列文章的最后一篇，它鼓励你为这个游戏编写自己的附加程序。你们很多人都这么做了！我收到了一些电子邮件，要求帮助我还没有涵盖的常用机制：战斗。毕竟，跳起来躲避坏人是一回事，但是有时候让他们走开是一件非常令人满意的事。在电脑游戏中向你的敌人投掷一些物品是很常见的，不管是一个火球、一支箭、一道闪电，还是其它适合游戏的东西。与迄今为止你在这个系列中为你的平台游戏编程的任何东西不同，可投掷物品有一个生存时间。在你投掷一个物品后，它会如期在移动一段距离后消失。如果它是一支箭或其它类似的东西，它可能会在通过屏幕的边缘时而消失。如果它是一个火球或一道闪电，它可能会在一段时间后熄灭。这意味着每次生成一个可投掷的物品时，也必须生成一个独特的衡量其生存时间的标准。为了介绍这个概念，这篇文章演示如何一次只投掷一个物品。（换句话说，每次仅存在一个投掷物品）。 一方面，这是一个游戏的限制条件，但另一方面，它却是游戏本身的运行机制。你的玩家不能每次同时投掷 50 个火球，因为每次仅允许一个投掷物品，所以当你的玩家释放一个火球来尝试击中一名敌人就成为了一项挑战。而在幕后，这也使你的代码保持简单。如果你想启用每次投掷多个项目，在完成这篇教程后，通过学习这篇教程所获取的知识来挑战你自己。创建 Throwable 类如果你跟随学习这系列的其它文章，那么你应该熟悉在屏幕上生成一个新的对象基础的 __init__ 函数。这和你用来生成你的 玩家 和 敌人 的函数是一样的。这里是生成一个 throwable 对象的 __init__ 函数来：class Throwable(pygame.sprite.Sprite): """ 生成一个 throwable 对象 """ def __init__(self, x, y, img, throw): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.firing = throw 同你的 Player 类或 Enemy 类的 __init__ 函数相比，这个函数的主要区别是，它有一个 self.firing 变量。这个变量保持跟踪一个投掷的物品是否在当前屏幕上活动，因此当一个 throwable 对象创建时，将变量设置为 1 的合乎情理的。判断存活时间接下来，就像使用 Player 和 Enemy 一样，你需要一个 update 函数，以便投掷的物品在瞄准敌人抛向空中时，它会自己移动。测定一个投掷的物品存活时间的最简单方法是侦测它何时离开屏幕。你需要监视的屏幕边缘取决于你投掷的物品的物理特性。如果你的玩家正在投掷的物品是沿着水平轴快速移动的，像一只弩箭或箭或一股非常快的魔法力量，而你想监视你游戏屏幕的水平轴极限。这可以通过 worldx 定义。如果你的玩家正在投掷的物品是沿着垂直方向或同时沿着水平方向和垂直方向移动的，那么你必须监视你游戏屏幕的垂直轴极限。这可以通过 worldy 定义。这个示例假设你投掷的物品向前移动一点并最终落到地面上。不过，投掷的物品不会从地面上反弹起来，而是继续掉落出屏幕。你可以尝试不同的设置来看看什么最适合你的游戏： def update(self,worldy): ''' 投掷物理学 ''' if self.rect.y < worldy: #垂直轴 self.rect.x += 15 #它向前移动的速度有多快 self.rect.y += 5 #它掉落的速度有多快 else: self.kill() #移除投掷对象 self.firing = 0 #解除火力发射 为使你的投掷物品移动地更快，增加 self.rect 的动量值。如果投掷物品不在屏幕上，那么该物品将被销毁，以及释放其所占用的寄存器。另外，self.firing 将被设置回 0 以允许你的玩家来进行另一次射击。设置你的投掷对象就像使用你的玩家和敌人一样，你必须在你的设置部分中创建一个精灵组来保持投掷对象。此外，你必须创建一个非活动的投掷对象来供开始的游戏使用。如果在游戏开始时却没有一个投掷对象，那么玩家在第一次尝试投掷一柄武器时，投掷将失败。这个示例假设你的玩家使用一个火球作为开始的武器，因此，每一个投掷实例都是由 fire 变量指派的。在后面的关卡中，当玩家获取新的技能时，你可以使用相同的 Throwable 类来引入一个新的变量以使用一张不同的图像。在这代码块中，前两行已经在你的代码中，因此不要重新键入它们:player_list = pygame.sprite.Group() #上下文 player_list.add(player) #上下文 fire = Throwable(player.rect.x,player.rect.y,'fire.png',0) firepower = pygame.sprite.Group() 注意，每一个投掷对象的起始位置都是和玩家所在的位置相同。这使得它看起来像是投掷对象来自玩家。在第一个火球生成时，使用 0 来显示 self.firing 是可用的。在主循环中获取投掷行为没有在主循环中出现的代码不会在游戏中使用，因此你需要在你的主循环中添加一些东西，以便能在你的游戏世界中获取投掷对象。首先，添加玩家控制。当前，你没有火力触发器。在键盘上的按键是有两种状态的：释放的按键，按下的按键。为了移动，你要使用这两种状态：按下按键来启动玩家移动，释放按键来停止玩家移动。开火仅需要一个信号。你使用哪个按键事件（按键按下或按键释放）来触发你的投掷对象取决于你的品味。在这个代码语句块中，前两行是用于上下文的： if event.key == pygame.K_UP or event.key == ord('w'): player.jump(platform_list) if event.key == pygame.K_SPACE: if not fire.firing: fire = Throwable(player.rect.x,player.rect.y,'fire.png',1) firepower.add(fire) 与你在设置部分创建的火球不同，你使用一个 1 来设置 self.firing 为不可用。最后，你必须更新和绘制你的投掷物品。这个顺序很重要，因此把这段代码放置到你现有的 enemy.move 和 player_list.draw 的代码行之间： enemy.move() # 上下文 if fire.firing: fire.update(worldy) firepower.draw(world) player_list.draw(screen) # 上下文 enemy_list.draw(screen) # 上下文 注意，这些更新仅在 self.firing 变量被设置为 1 时执行。如果它被设置为 0 ，那么 fire.firing 就不为 true，接下来就跳过更新。如果你尝试做上述这些更新，不管怎样，你的游戏都会崩溃，因为在游戏中将不会更新或绘制一个 fire 对象。启动你的游戏，尝试挑战你的武器。检测碰撞如果你玩使用了新投掷技巧的游戏，你可能会注意到，你可以投掷对象，但是它却不会对你的敌人有任何影响。原因是你的敌人没有被查到碰撞事故。一名敌人可能会被你的投掷物品所击中，但是敌人却从来不知道被击中了。你已经在你的 Player 类中完成了碰撞检测，这非常类似。在你的 Enemy 类中，添加一个新的 update 函数： def update(self,firepower, enemy_list): """ 检测火力碰撞 """ fire_hit_list = pygame.sprite.spritecollide(self,firepower,False) for fire in fire_hit_list: enemy_list.remove(self) 代码很简单。每个敌人对象都检查并看看它自己是否被 firepower 精灵组的成员所击中。如果它被击中，那么敌人就会从敌人组中移除和消失。为集成这些功能到你的游戏之中，在主循环中调用位于新触发语句块中的函数： if fire.firing: # 上下文 fire.update(worldy) # 上下文 firepower.draw(screen) # 上下文 enemy_list.update(firepower,enemy_list) # 更新敌人 你现在可以尝试一下你的游戏了，大多数的事情都如预期般的那样工作。不过，这里仍然有一个问题，那就是投掷的方向。更改投掷机制的方向当前，你英雄的火球只会向右移动。这是因为 Throwable 类的 update 函数将像素添加到火球的位置，在 Pygame 中，在 X 轴上一个较大的数字意味着向屏幕的右侧移动。当你的英雄转向另一个方向时，你可能希望它投掷的火球也抛向左侧。到目前为止，你已经知道如何实现这一点，至少在技术上是这样的。然而，最简单的解决方案却是使用一个变量，在一定程度上对你来说可能是一种新的方法。一般来说，你可以“设置一个标记”（有时也被称为“翻转一个位”）来标明你的英雄所面向的方向。在你做完后，你就可以检查这个变量来得知火球是向左移动还是向右移动。首先，在你的 Player 类中创建一个新的变量来代表你的游戏所面向的方向。因为我的游戏天然地面向右侧，由此我把面向右侧作为默认值： self.score = 0 self.facing_right = True # 添加这行 self.is_jumping = True 当这个变量是 True 时，你的英雄精灵是面向右侧的。当玩家每次更改英雄的方向时，变量也必须重新设置，因此，在你的主循环中相关的 keyup 事件中这样做： if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps, 0) player.facing_right = False # 添加这行 if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps, 0) player.facing_right = True # 添加这行 最后，更改你的 Throwable 类的 update 函数，以检测英雄是否面向右侧，并恰当地添加或减去来自火球位置的像素： if self.rect.y < worldy: if player.facing_right: self.rect.x += 15 else: self.rect.x -= 15 self.rect.y += 5 再次尝试你的游戏，清除掉你游戏世界中的一些坏人。作为一项额外的挑战，当彻底打败敌人时，尝试增加你玩家的得分。完整的代码#!/usr/bin/env python3 # 作者: Seth Kenlon # GPLv3 # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <[http://www.gnu.org/licenses/>][17]. import pygame import pygame.freetype import sys import os ''' 变量 ''' worldx = 960 worldy = 720 fps = 40 ani = 4 world = pygame.display.set_mode([worldx, worldy]) forwardx = 600 backwardx = 120 BLUE = (80, 80, 155) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ALPHA = (0, 255, 0) tx = 64 ty = 64 font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts", "amazdoom.ttf") font_size = tx pygame.freetype.init() myfont = pygame.freetype.Font(font_path, font_size) ''' 对象 ''' def stats(score, health): myfont.render_to(world, (4, 4), "Score:"+str(score), BLUE, None, size=64) myfont.render_to(world, (4, 72), "Health:"+str(health), BLUE, None, size=64) class Throwable(pygame.sprite.Sprite): """ 生成一个投掷的对象 """ def __init__(self, x, y, img, throw): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images', img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.firing = throw def update(self, worldy): ''' 投掷物理学 ''' if self.rect.y < worldy: if player.facing_right: self.rect.x += 15 else: self.rect.x -= 15 self.rect.y += 5 else: self.kill() self.firing = 0 # x 位置, y 位置, img 宽度, img 高度, img 文件 class Platform(pygame.sprite.Sprite): def __init__(self, xloc, yloc, imgw, imgh, img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images', img)).convert() self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): """ 生成一名玩家 """ def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.damage = 0 self.score = 0 self.facing_right = True self.is_jumping = True self.is_falling = True self.images = [] for i in range(1, 5): img = pygame.image.load(os.path.join('images', 'walk' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def gravity(self): if self.is_jumping: self.movey += 3.2 def control(self, x, y): """ 控制玩家移动 """ self.movex += x def jump(self): if self.is_jumping is False: self.is_falling = False self.is_jumping = True def update(self): """ 更新精灵位置 """ # 向左移动 if self.movex < 0: self.is_jumping = True self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = pygame.transform.flip(self.images[self.frame // ani], True, False) # 向右移动 if self.movex > 0: self.is_jumping = True self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] # 碰撞 enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) if self.damage == 0: for enemy in enemy_hit_list: if not self.rect.contains(enemy): self.damage = self.rect.colliderect(enemy) if self.damage == 1: idx = self.rect.collidelist(enemy_hit_list) if idx == -1: self.damage = 0 # 设置伤害回 0 self.health -= 1 # 减去 1 单位健康度 ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.bottom = g.rect.top self.is_jumping = False # 停止跳跃 # 掉落世界 if self.rect.y > worldy: self.health -=1 print(self.health) self.rect.x = tx self.rect.y = ty plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.is_jumping = False # 停止跳跃 self.movey = 0 if self.rect.bottom <= p.rect.bottom: self.rect.bottom = p.rect.top else: self.movey += 3.2 if self.is_jumping and self.is_falling is False: self.is_falling = True self.movey -= 33 # 跳跃多高 loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) self.rect.x += self.movex self.rect.y += self.movey class Enemy(pygame.sprite.Sprite): """ 生成一名敌人 """ def __init__(self, x, y, img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images', img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): """ 敌人移动 """ distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance * 2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 def update(self, firepower, enemy_list): """ 检测火力碰撞 """ fire_hit_list = pygame.sprite.spritecollide(self, firepower, False) for fire in fire_hit_list: enemy_list.remove(self) class Level: def ground(lvl, gloc, tx, ty): ground_list = pygame.sprite.Group() i = 0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i], worldy - ty, tx, ty, 'tile-ground.png') ground_list.add(ground) i = i + 1 if lvl == 2: print("Level " + str(lvl)) return ground_list def bad(lvl, eloc): if lvl == 1: enemy = Enemy(eloc[0], eloc[1], 'enemy.png') enemy_list = pygame.sprite.Group() enemy_list.add(enemy) if lvl == 2: print("Level " + str(lvl)) return enemy_list # x 位置, y 位置, img 宽度, img 高度, img 文件 def platform(lvl, tx, ty): plat_list = pygame.sprite.Group() ploc = [] i = 0 if lvl == 1: ploc.append((200, worldy - ty - 128, 3)) ploc.append((300, worldy - ty - 256, 3)) ploc.append((550, worldy - ty - 128, 4)) while i < len(ploc): j = 0 while j <= ploc[i][2]: plat = Platform((ploc[i][0] + (j * tx)), ploc[i][1], tx, ty, 'tile.png') plat_list.add(plat) j = j + 1 print('run' + str(i) + str(ploc[i])) i = i + 1 if lvl == 2: print("Level " + str(lvl)) return plat_list def loot(lvl): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_list ''' Setup 部分 ''' backdrop = pygame.image.load(os.path.join('images', 'stage.png')) clock = pygame.time.Clock() pygame.init() backdropbox = world.get_rect() main = True player = Player() # 生成玩家 player.rect.x = 0 # 转到 x player.rect.y = 30 # 转到 y player_list = pygame.sprite.Group() player_list.add(player) steps = 10 fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 0) firepower = pygame.sprite.Group() eloc = [] eloc = [300, worldy-ty-80] enemy_list = Level.bad(1, eloc) gloc = [] i = 0 while i <= (worldx / tx) + tx: gloc.append(i * tx) i = i + 1 ground_list = Level.ground(1, gloc, tx, ty) plat_list = Level.platform(1, tx, ty) enemy_list = Level.bad( 1, eloc ) loot_list = Level.loot(1) ''' 主循环 ''' while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() try: sys.exit() finally: main = False if event.type == pygame.KEYDOWN: if event.key == ord('q'): pygame.quit() try: sys.exit() finally: main = False if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(-steps, 0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(steps, 0) if event.key == pygame.K_UP or event.key == ord('w'): player.jump() if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps, 0) player.facing_right = False if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps, 0) player.facing_right = True if event.key == pygame.K_SPACE: if not fire.firing: fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 1) firepower.add(fire) # 向向滚动世界 if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll for l in loot_list: l.rect.x -= scroll # 向后滚动世界 if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll for l in loot_list: l.rect.x += scroll world.blit(backdrop, backdropbox) player.update() player.gravity() player_list.draw(world) if fire.firing: fire.update(worldy) firepower.draw(world) enemy_list.draw(world) enemy_list.update(firepower, enemy_list) loot_list.draw(world) ground_list.draw(world) plat_list.draw(world) for e in enemy_list: e.move() stats(player.score, player.health) pygame.display.flip() clock.tick(fps) via: https://opensource.com/article/20/9/add-throwing-python-game作者：Seth Kenlon 选题：lujun9972 译者：robsean 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在本系列的第十一篇有关使用 Python Pygame 模块进行编程的文章中，显示玩家获得战利品或受到伤害时的得分。这是仍在进行中的关于使用 Pygame 模块来在 Python 3 在创建电脑游戏的第十一部分。先前的文章是：通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人在 Pygame 游戏中放置平台在你的 Python 游戏中模拟引力为你的 Python 平台类游戏添加跳跃功能使你的 Python 游戏玩家能够向前和向后跑在你的 Python 平台类游戏中放一些奖励如果你已经跟随这一系列很久，那么已经学习了使用 Python 创建一个视频游戏所需的所有基本语法和模式。然而，它仍然缺少一个至关重要的组成部分。这一组成部分不仅仅对用 Python 编程游戏重要；不管你探究哪个计算机分支，你都必需精通：作为一个程序员，通过阅读一种语言的或库的文档来学习新的技巧。幸运的是，你正在阅读本文的事实表明你熟悉文档。为了使你的平台类游戏更加美观，在这篇文章中，你将在游戏屏幕上添加得分和生命值显示。不过，教你如何找到一个库的功能以及如何使用这些新的功能的这节课程并没有多神秘。在 Pygame 中显示得分现在，既然你有了可以被玩家收集的奖励，那就有充分的理由来记录分数，以便你的玩家看到他们收集了多少奖励。你也可以跟踪玩家的生命值，以便当他们被敌人击中时会有相应结果。你已经有了跟踪分数和生命值的变量，但是这一切都发生在后台。这篇文章教你在游戏期间在游戏屏幕上以你选择的一种字体来显示这些统计数字。阅读文档大多数 Python 模块都有文档，即使那些没有文档的模块，也能通过 Python 的帮助功能来进行最小的文档化。Pygame 的主页面 链接了它的文档。不过，Pygame 是一个带有很多文档的大模块，并且它的文档不像在 Opensource.com 上的文章一样，以同样易理解的（和友好的、易解释的、有用的）叙述风格来撰写的。它们是技术文档，并且列出在模块中可用的每个类和函数，各自要求的输入类型等等。如果你不适应参考代码组件描述，这可能会令人不知所措。在烦恼于库的文档前，第一件要做的事，就是来想想你正在尝试达到的目标。在这种情况下，你想在屏幕上显示玩家的得分和生命值。在你确定你需要的结果后，想想它需要什么的组件。你可以从变量和函数的方面考虑这一点，或者，如果你还没有自然地想到这一点，你可以进行一般性思考。你可能意识到需要一些文本来显示一个分数，你希望 Pygame 在屏幕上绘制这些文本。如果你仔细思考，你可能会意识到它与在屏幕上渲染一个玩家、奖励或一个平台并多么大的不同。从技术上讲，你可以使用数字图形，并让 Pygame 显示这些数字图形。它不是达到你目标的最容易的方法，但是如果它是你唯一知道的方法，那么它是一个有效的方法。不过，如果你参考 Pygame 的文档，你看到列出的模块之一是 font，这是 Pygame 使得在屏幕上来使打印文本像输入文字一样容易的方法。解密技术文档font 文档页面以 pygame.font.init() 开始，它列出了用于初始化字体模块的函数。它由 pygame.init() 自动地调用，你已经在代码中调用了它。再强调一次，从技术上讲，你已经到达一个足够好的点。虽然你尚不知道如何做，你知道你能够使用 pygame.font 函数来在屏幕上打印文本。然而，如果你阅读更多一些，你会找到这里还有一种更好的方法来打印字体。pygame.freetype 模块在文档中的描述方式如下：pygame.freetype 模块是 pygame.fontpygame 模块的一个替代品，用于加载和渲染字体。它有原函数的所有功能，外加很多新的功能。在 pygame.freetype 文档页面的下方，有一些示例代码：import pygame import pygame.freetype你的代码应该已经导入了 Pygame，不过，请修改你的 import 语句以包含 Freetype 模块：import pygame import sys import os import pygame.freetype在 Pygame 中使用字体从 font 模块的描述中可以看出，显然 Pygame 使用一种字体（不管它的你提供的或内置到 Pygame 的默认字体）在屏幕上渲染字体。滚动浏览 pygame.freetype 文档来找到 pygame.freetype.Font 函数：pygame.freetype.Font 从支持的字体文件中创建一个新的字体实例。 Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font pygame.freetype.Font.name 符合规则的字体名称。 pygame.freetype.Font.path 字体文件路径。 pygame.freetype.Font.size 在渲染中使用的默认点大小这描述了如何在 Pygame 中构建一个字体“对象”。把屏幕上的一个简单对象视为一些代码属性的组合对你来说可能不太自然，但是这与你构建英雄和敌人精灵的方式非常类似。你需要一个字体文件，而不是一个图像文件。在你有一个字体文件后，你可以在你的代码中使用 pygame.freetype.Font 函数来创建一个字体对象，然后使用该对象来在屏幕上渲染文本。因为并不是世界上的每个人的电脑上都有完全一样的字体，因此将你选择的字体与你的游戏捆绑在一起是很重要的。要捆绑字体，首先在你的游戏文件夹中创建一个新的目录，放在你为图像而创建的文件目录旁边。称其为 fonts 。即使你的计算机操作系统随附了几种字体，但是将这些字体给予其他人是非法的。这看起来很奇怪，但法律就是这样运作的。如果想与你的游戏一起随附一种字体，你必需找到一种开源或知识共享的字体，以允许你随游戏一起提供该字体。专门提供自由和合法字体的网站包括：Font LibraryFont SquirrelLeague of Moveable Type当你找到你喜欢的字体后，下载下来。解压缩 ZIP 或 TAR 文件，并移动 .ttf 或 .otf 文件到你的项目目录下的 fonts 文件夹中。你没有安装字体到你的计算机上。你只是放置字体到你游戏的 fonts 文件夹中，以便 Pygame 可以使用它。如果你想，你可以在你的计算机上安装该字体，但是没有必要。重要的是将字体放在你的游戏目录中，这样 Pygame 可以“描绘”字体到屏幕上。如果字体文件的名称复杂且带有空格或特殊字符，只需要重新命名它即可。文件名称是完全任意的，并且对你来说，文件名称越简单，越容易将其键入你的代码中。现在告诉 Pygame 你的字体。从文档中你知道，当你至少提供了字体文件路径给 pygame.freetype.Font 时（文档明确指出所有其余属性都是可选的），你将在返回中获得一个字体对象：Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font创建一个称为 myfont 的新变量来充当你在游戏中字体，并放置 Font 函数的结果到这个变量中。这个示例中使用 amazdoom.ttf 字体，但是你可以使用任何你想使用的字体。在你的设置部分放置这些代码：font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf") font_size = tx myfont = pygame.freetype.Font(font_path, font_size)在 Pygame 中显示文本现在你已经创建一个字体对象，你需要一个函数来绘制你想绘制到屏幕上的文本。这和你在你的游戏中绘制背景和平台是相同的原理。首先，创建一个函数，并使用 myfont 对象来创建一些文本，设置颜色为某些 RGB 值。这必须是一个全局函数；它不属于任何具体的类：def stats(score,health): myfont.render_to(world, (4, 4), "Score:"+str(score), WHITE, None, size=64) myfont.render_to(world, (4, 72), "Health:"+str(health), WHITE, None, size=64)当然，你此刻已经知道，如果它不在主循环中，你的游戏将不会发生任何事，所以在文件的底部添加一个对你的 stats 函数的调用： for e in enemy_list: e.move() stats(player.score,player.health) # draw text pygame.display.flip()尝试你的游戏。当玩家收集奖励品时，得分会上升。当玩家被敌人击中时，生命值下降。成功！不过，这里有一个问题。当一个玩家被敌人击中时，健康度会一路下降，这是不公平的。你刚刚发现一个非致命的错误。非致命的错误是这些在应用程序中小问题，（通常）不会阻止应用程序启动或甚至导致停止工作，但是它们要么没有意义，要么会惹恼用户。这里是如何解决这个问题的方法。修复生命值计数当前生命值系统的问题是，敌人接触玩家时，Pygame 时钟的每一次滴答，健康度都会减少。这意味着一个缓慢移动的敌人可能在一次遭遇中将一个玩家降低健康度至 -200 ，这不公平。当然，你可以给你的玩家一个 10000 的起始健康度得分，而不用担心它；这可以工作，并且可能没有人会注意。但是这里有一个更好的方法。当前，你的代码侦查出一个玩家和一个敌人发生碰撞的时候。生命值问题的修复是检测两个独立的事件：什么时候玩家和敌人碰撞，并且，在它们碰撞后，什么时候它们停止碰撞。首先，在你的玩家类中，创建一个变量来代表玩家和敌人碰撞在一起： self.frame = 0 self.health = 10 self.damage = 0在你的 Player 类的 update 函数中，移除这块代码块： for enemy in enemy_hit_list: self.health -= 1 #print(self.health)并且在它的位置，只要玩家当前没有被击中，检查碰撞： if self.damage == 0: for enemy in enemy_hit_list: if not self.rect.contains(enemy): self.damage = self.rect.colliderect(enemy)你可能会在你删除的语句块和你刚刚添加的语句块之间看到相似之处。它们都在做相同的工作，但是新的代码更复杂。最重要的是，只有当玩家当前没有被击中时，新的代码才运行。这意味着，当一个玩家和敌人碰撞时，这些代码运行一次，而不是像以前那样一直发生碰撞。新的代码使用两个新的 Pygame 函数。self.rect.contains 函数检查一个敌人当前是否在玩家的边界框内，并且当它是 true 时， self.rect.colliderect 设置你的新的 self.damage 变量为 1，而不管它多少次是 true 。现在，即使被一个敌人击中 3 秒，对 Pygame 来说仍然看作一次击中。我通过通读 Pygame 的文档而发现了这些函数。你没有必要一次阅读完全部的文档，并且你也没有必要阅读每个函数的每个单词。不过，花费时间在你正在使用的新的库或模块的文档上是很重要的；否则，你极有可能在重新发明轮子。不要花费一个下午的时间来尝试修改拼接一个解决方案到一些东西，而这些东西已经被你正在使用的框架的所解决。阅读文档，知悉函数，并从别人的工作中获益！最后，添加另一个代码语句块来侦查出什么时候玩家和敌人不再接触。然后直到那时，才从玩家减少一个生命值。 if self.damage == 1: idx = self.rect.collidelist(enemy_hit_list) if idx == -1: self.damage = 0 # set damage back to 0 self.health -= 1 # subtract 1 hp注意，只有当玩家被击中时，这个新的代码才会被触发。这意味着，在你的玩家在你的游戏世界正在探索或收集奖励时，这个代码不会运行。它仅当 self.damage 变量被激活时运行。当代码运行时，它使用 self.rect.collidelist 来查看玩家是否仍然接触在你敌人列表中的敌人（当其未侦查到碰撞时，collidelist 返回 -1）。在它没有接触敌人时，是该处理 self.damage 的时机：通过设置 self.damage 变量回到 0 来使其无效，并减少一点生命值。现在尝试你的游戏。得分反应现在，你有一个来让你的玩家知道它们分数和生命值的方法，当你的玩家达到某些里程碑时，你可以确保某些事件发生。例如，也许这里有一个特殊的恢复一些生命值的奖励项目。也许一个到达 0 生命值的玩家不得不从一个关卡的起始位置重新开始。你可以在你的代码中检查这些事件，并且相应地操纵你的游戏世界。你已经知道该怎么做，所以请浏览文档来寻找新的技巧，并且独立地尝试这些技巧。这里是到目前为止所有的代码：#!/usr/bin/env python3 # draw a world # add a player and player control # add player movement # add enemy and basic collision # add platform # add gravity # add jumping # add scrolling # add loot # add score # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import pygame import sys import os import pygame.freetype ''' Objects ''' class Platform(pygame.sprite.Sprite): # x location, y location, img width, img height, img file def __init__(self,xloc,yloc,imgw,imgh,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)).convert() self.image.convert_alpha() self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): ''' Spawn a player ''' def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.damage = 0 self.collide_delta = 0 self.jump_delta = 6 self.score = 1 self.images = [] for i in range(1,9): img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def jump(self,platform_list): self.jump_delta = 0 def gravity(self): self.movey += 3.2 # how fast player falls if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty def control(self,x,y): ''' control player movement ''' self.movex += x self.movey += y def update(self): ''' Update sprite position ''' self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[self.frame//ani] # moving right if self.movex > 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[(self.frame//ani)+4] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) if self.damage == 0: for enemy in enemy_hit_list: if not self.rect.contains(enemy): self.damage = self.rect.colliderect(enemy) if self.damage == 1: idx = self.rect.collidelist(enemy_hit_list) if idx == -1: self.damage = 0 # set damage back to 0 self.health -= 1 # subtract 1 hp loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.collide_delta = 0 # stop jumping self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.y = worldy-ty-ty self.collide_delta = 0 # stop jumping if self.rect.y > g.rect.y: self.health -=1 print(self.health) if self.collide_delta < 6 and self.jump_delta < 6: self.jump_delta = 6*2 self.movey -= 33 # how high to jump self.collide_delta += 6 self.jump_delta += 6 class Enemy(pygame.sprite.Sprite): ''' Spawn an enemy ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.movey = 0 #self.image.convert_alpha() #self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): ''' enemy movement ''' distance = 80 speed = 8 self.movey += 3.2 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 if not self.rect.y >= worldy-ty-ty: self.rect.y += self.movey plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.rect.y = worldy-ty-ty class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy enemy_list = pygame.sprite.Group() # create enemy group enemy_list.add(enemy) # add enemy to group if lvl == 2: print("Level " + str(lvl) ) return enemy_list def loot(lvl,tx,ty): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(200,ty*7,tx,ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_list def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list def platform(lvl,tx,ty): plat_list = pygame.sprite.Group() ploc = [] i=0 if lvl == 1: ploc.append((20,worldy-ty-128,3)) ploc.append((300,worldy-ty-256,3)) ploc.append((500,worldy-ty-128,4)) while i < len(ploc): j=0 while j <= ploc[i][2]: plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png') plat_list.add(plat) j=j+1 print('run' + str(i) + str(ploc[i])) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return plat_list def stats(score,health): myfont.render_to(world, (4, 4), "Score:"+str(score), SNOWGRAY, None, size=64) myfont.render_to(world, (4, 72), "Health:"+str(health), SNOWGRAY, None, size=64) ''' Setup ''' worldx = 960 worldy = 720 fps = 40 # frame rate ani = 4 # animation cycles clock = pygame.time.Clock() pygame.init() main = True BLUE = (25,25,200) BLACK = (23,23,23 ) WHITE = (254,254,254) SNOWGRAY = (137,164,166) ALPHA = (0,255,0) world = pygame.display.set_mode([worldx,worldy]) backdrop = pygame.image.load(os.path.join('images','stage.png')).convert() backdropbox = world.get_rect() player = Player() # spawn player player.rect.x = 0 player.rect.y = 0 player_list = pygame.sprite.Group() player_list.add(player) steps = 10 forwardx = 600 backwardx = 230 eloc = [] eloc = [200,20] gloc = [] tx = 64 #tile size ty = 64 #tile size font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf") font_size = tx myfont = pygame.freetype.Font(font_path, font_size) i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,gloc,tx,ty ) plat_list = Level.platform( 1,tx,ty ) loot_list = Level.loot(1,tx,ty) ''' Main loop ''' while main == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() main = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): print("LEFT") player.control(-steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): print("RIGHT") player.control(steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps,0) if event.key == pygame.K_UP or event.key == ord('w'): player.jump(plat_list) if event.key == ord('q'): pygame.quit() sys.exit() main = False # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll for l in loot_list: l.rect.x -= scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll for l in loot_list: l.rect.x += scroll world.blit(backdrop, backdropbox) player.gravity() # check gravity player.update() player_list.draw(world) #refresh player position enemy_list.draw(world) # refresh enemies ground_list.draw(world) # refresh enemies plat_list.draw(world) # refresh platforms loot_list.draw(world) # refresh loot for e in enemy_list: e.move() stats(player.score,player.health) # draw text pygame.display.flip() clock.tick(fps)via: https://opensource.com/article/20/1/add-scorekeeping-your-python-game作者：Seth Kenlon 选题：lujun9972 译者：robsean 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
这部分是关于在使用 Python 的 Pygame 模块开发的视频游戏总给你的玩家提供收集的宝物和经验值的内容。这是正在进行的关于使用 Python 3 的 Pygame 模块创建视频游戏的系列文章的第十部分。以前的文章有:通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人在 Pygame 游戏中放置平台在你的 Python 游戏中模拟引力为你的 Python 平台类游戏添加跳跃功能使你的 Python 游戏玩家能够向前和向后跑如果你已经阅读了本系列的前几篇文章，那么你已经了解了编写游戏的所有基础知识。现在你可以在这些基础上，创造一个全功能的游戏。当你第一次学习时，遵循本系列代码示例，这样的“用例”是有帮助的，但是，用例也会约束你。现在是时候运用你学到的知识，以新的方式应用它们了。如果说，说起来容易做起来难，这篇文章展示了一个如何将你已经了解的内容用于新目的的例子中。具体来说，就是它涵盖了如何使用你以前的课程中已经了解到的来实现奖励系统。在大多数电子游戏中，你有机会在游戏世界中获得“奖励”或收集到宝物和其他物品。奖励通常会增加你的分数或者你的生命值，或者为你的下一次任务提供信息。游戏中包含的奖励类似于编程平台。像平台一样，奖励没有用户控制，随着游戏世界的滚动进行，并且必须检查与玩家的碰撞。创建奖励函数奖励和平台非常相似，你甚至不需要一个奖励的类。你可以重用 Platform 类，并将结果称为“奖励”。由于奖励类型和位置可能因关卡不同而不同，如果你还没有，请在你的 Level 中创建一个名为 loot 的新函数。因为奖励物品不是平台，你也必须创建一个新的 loot_list 组，然后添加奖励物品。与平台、地面和敌人一样，该组用于检查玩家碰撞: def loot(lvl,lloc): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(300,ty*7,tx,ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_list你可以随意添加任意数量的奖励对象；记住把每一个都加到你的奖励清单上。Platform 类的参数是奖励图标的 X 位置、Y 位置、宽度和高度（通常让你的奖励精灵保持和所有其他方块一样的大小最为简单），以及你想要用作的奖励的图片。奖励的放置可以和贴图平台一样复杂，所以使用创建关卡时需要的关卡设计文档。在脚本的设置部分调用新的奖励函数。在下面的代码中，前三行是上下文，所以只需添加第四行:enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,gloc,tx,ty ) plat_list = Level.platform( 1,tx,ty ) loot_list = Level.loot(1,tx,ty)正如你现在所知道的，除非你把它包含在你的主循环中，否则奖励不会被显示到屏幕上。将下面代码示例的最后一行添加到循环中: enemy_list.draw(world) ground_list.draw(world) plat_list.draw(world) loot_list.draw(world)启动你的游戏看看会发生什么。你的奖励将会显示出来，但是当你的玩家碰到它们时，它们不会做任何事情，当你的玩家经过它们时，它们也不会滚动。接下来解决这些问题。滚动奖励像平台一样，当玩家在游戏世界中移动时，奖励必须滚动。逻辑与平台滚动相同。要向前滚动奖励物品，添加最后两行: for e in enemy_list: e.rect.x -= scroll for l in loot_list: l.rect.x -= scroll要向后滚动，请添加最后两行： for e in enemy_list: e.rect.x += scroll for l in loot_list: l.rect.x += scroll再次启动你的游戏，看看你的奖励物品现在表现得像在游戏世界里一样了，而不是仅仅画在上面。检测碰撞就像平台和敌人一样，你可以检查奖励物品和玩家之间的碰撞。逻辑与其他碰撞相同，除了撞击不会（必然）影响重力或生命值。取而代之的是，命中会导致奖励物品会消失并增加玩家的分数。当你的玩家触摸到一个奖励对象时，你可以从 loot_list 中移除该对象。这意味着当你的主循环在 loot_list 中重绘所有奖励物品时，它不会重绘那个特定的对象，所以看起来玩家已经获得了奖励物品。在 Player 类的 update 函数中的平台碰撞检测之上添加以下代码（最后一行仅用于上下文）： loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)当碰撞发生时，你不仅要把奖励从它的组中移除，还要给你的玩家一个分数提升。你还没有创建分数变量，所以请将它添加到你的玩家属性中，该属性是在 Player 类的 __init__ 函数中创建的。在下面的代码中，前两行是上下文，所以只需添加分数变量: self.frame = 0 self.health = 10 self.score = 0当在主循环中调用 update 函数时，需要包括 loot_list： player.gravity() player.update()如你所见，你已经掌握了所有的基本知识。你现在要做的就是用新的方式使用你所知道的。在下一篇文章中还有一些提示，但是与此同时，用你学到的知识来制作一些简单的单关卡游戏。限制你试图创造的东西的范围是很重要的，这样你就不会埋没自己。这也使得最终的成品看起来和感觉上更容易完成。以下是迄今为止你为这个 Python 平台编写的所有代码:#!/usr/bin/env python3 # draw a world # add a player and player control # add player movement # add enemy and basic collision # add platform # add gravity # add jumping # add scrolling # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import pygame import sys import os ''' Objects ''' class Platform(pygame.sprite.Sprite): # x location, y location, img width, img height, img file def __init__(self,xloc,yloc,imgw,imgh,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)).convert() self.image.convert_alpha() self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): ''' Spawn a player ''' def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.collide_delta = 0 self.jump_delta = 6 self.score = 1 self.images = [] for i in range(1,9): img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def jump(self,platform_list): self.jump_delta = 0 def gravity(self): self.movey += 3.2 # how fast player falls if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty def control(self,x,y): ''' control player movement ''' self.movex += x self.movey += y def update(self): ''' Update sprite position ''' self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[self.frame//ani] # moving right if self.movex > 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[(self.frame//ani)+4] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 #print(self.health) loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.collide_delta = 0 # stop jumping self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.y = worldy-ty-ty self.collide_delta = 0 # stop jumping if self.rect.y > g.rect.y: self.health -=1 print(self.health) if self.collide_delta < 6 and self.jump_delta < 6: self.jump_delta = 6*2 self.movey -= 33 # how high to jump self.collide_delta += 6 self.jump_delta += 6 class Enemy(pygame.sprite.Sprite): ''' Spawn an enemy ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.movey = 0 #self.image.convert_alpha() #self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): ''' enemy movement ''' distance = 80 speed = 8 self.movey += 3.2 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 if not self.rect.y >= worldy-ty-ty: self.rect.y += self.movey plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.rect.y = worldy-ty-ty class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy enemy_list = pygame.sprite.Group() # create enemy group enemy_list.add(enemy) # add enemy to group if lvl == 2: print("Level " + str(lvl) ) return enemy_list def loot(lvl,tx,ty): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(200,ty*7,tx,ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_list def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list def platform(lvl,tx,ty): plat_list = pygame.sprite.Group() ploc = [] i=0 if lvl == 1: ploc.append((20,worldy-ty-128,3)) ploc.append((300,worldy-ty-256,3)) ploc.append((500,worldy-ty-128,4)) while i < len(ploc): j=0 while j <= ploc[i][2]: plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png') plat_list.add(plat) j=j+1 print('run' + str(i) + str(ploc[i])) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return plat_list ''' Setup ''' worldx = 960 worldy = 720 fps = 40 # frame rate ani = 4 # animation cycles clock = pygame.time.Clock() pygame.init() main = True BLUE = (25,25,200) BLACK = (23,23,23 ) WHITE = (254,254,254) ALPHA = (0,255,0) world = pygame.display.set_mode([worldx,worldy]) backdrop = pygame.image.load(os.path.join('images','stage.png')).convert() backdropbox = world.get_rect() player = Player() # spawn player player.rect.x = 0 player.rect.y = 0 player_list = pygame.sprite.Group() player_list.add(player) steps = 10 forwardx = 600 backwardx = 230 eloc = [] eloc = [200,20] gloc = [] #gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630] tx = 64 #tile size ty = 64 #tile size i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,gloc,tx,ty ) plat_list = Level.platform( 1,tx,ty ) loot_list = Level.loot(1,tx,ty) ''' Main loop ''' while main == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() main = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): print("LEFT") player.control(-steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): print("RIGHT") player.control(steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps,0) if event.key == pygame.K_UP or event.key == ord('w'): player.jump(plat_list) if event.key == ord('q'): pygame.quit() sys.exit() main = False # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll for l in loot_list: l.rect.x -= scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll for l in loot_list: l.rect.x += scroll world.blit(backdrop, backdropbox) player.gravity() # check gravity player.update() player_list.draw(world) #refresh player position enemy_list.draw(world) # refresh enemies ground_list.draw(world) # refresh enemies plat_list.draw(world) # refresh platforms loot_list.draw(world) # refresh loot for e in enemy_list: e.move() pygame.display.flip() clock.tick(fps)via: https://opensource.com/article/20/1/loot-python-platformer-game作者：Seth Kenlon 选题：lujun9972 译者：heguangzhi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
使你的 Python 游戏玩家能够向前和向后跑
使用 Pygame 模块来使你的 Python 平台开启侧滚效果，来让你的玩家自由奔跑。这是仍在进行中的关于使用 Pygame 模块来在 Python 3 中在创建电脑游戏的第九部分。先前的文章是：通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人在 Pygame 游戏中放置平台在你的 Python 游戏中模拟引力为你的 Python 平台类游戏添加跳跃功能在这一系列关于使用 Pygame 模块来在 Python 3 中创建电脑游戏的先前文章中，你已经设计了你的关卡设计布局，但是你的关卡的一些部分可能已近超出你的屏幕的可视区域。在平台类游戏中，这个问题的普遍解决方案是，像术语“ 侧滚 side-scroller ”表明的一样，滚动。滚动的关键是当玩家精灵接近屏的幕边缘时，使在玩家精灵周围的平台移动。这样给予一种错觉，屏幕是一个在游戏世界中穿梭追拍的摄像机。这个滚动技巧需要两个在屏幕边缘的绝对区域，在绝对区域内的点处，在世界滚动期间，你的化身静止不动。在侧滚动条中放置卷轴如果你希望你的玩家能够后退，你需要一个触发点来向前和向后。这两个点仅仅是两个变量。设置它们各个距各个屏幕边缘大约 100 或 200 像素。在你的设置部分中创建变量。在下面的代码中，前两行用于上下文说明，所以仅需要添加这行后的代码：player_list.add(player) steps = 10 forwardX = 600 backwardX = 230在主循环中，查看你的玩家精灵是否在 forwardx 或 backwardx 滚动点处。如果是这样，向左或向右移动使用的平台，取决于世界是向前或向后移动。在下面的代码中，代码的最后三行仅供你参考： # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll ## scrolling code above world.blit(backdrop, backdropbox) player.gravity() # check gravity player.update()启动你的游戏，并尝试它。滚动像预期的一样工作，但是你可能注意到一个发生的小问题，当你滚动你的玩家和非玩家精灵周围的世界时：敌人精灵不随同世界滚动。除非你要你的敌人精灵要无休止地追逐你的玩家，你需要修改敌人代码，以便当你的玩家快速撤退时，敌人被留在后面。敌人卷轴在你的主循环中，你必须对卷轴平台为你的敌人的位置的应用相同的规则。因为你的游戏世界将（很可能）有不止一个敌人在其中，该规则应该被应用于你的敌人列表，而不是一个单独的敌人精灵。这是分组类似元素到列表中的优点之一。前两行用于上下文注释，所以只需添加这两行后面的代码到你的主循环中： # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll来滚向另一个方向： # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll再次启动游戏，看看发生什么。这里是到目前为止你已经为这个 Python 平台所写所有的代码：#!/usr/bin/env python3 # draw a world # add a player and player control # add player movement # add enemy and basic collision # add platform # add gravity # add jumping # add scrolling # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import pygame import sys import os ''' Objects ''' class Platform(pygame.sprite.Sprite): # x location, y location, img width, img height, img file def __init__(self,xloc,yloc,imgw,imgh,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)).convert() self.image.convert_alpha() self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): ''' Spawn a player ''' def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.collide_delta = 0 self.jump_delta = 6 self.score = 1 self.images = [] for i in range(1,9): img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def jump(self,platform_list): self.jump_delta = 0 def gravity(self): self.movey += 3.2 # how fast player falls if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty def control(self,x,y): ''' control player movement ''' self.movex += x self.movey += y def update(self): ''' Update sprite position ''' self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[self.frame//ani] # moving right if self.movex > 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[(self.frame//ani)+4] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 #print(self.health) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.collide_delta = 0 # stop jumping self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.y = worldy-ty-ty self.collide_delta = 0 # stop jumping if self.rect.y > g.rect.y: self.health -=1 print(self.health) if self.collide_delta < 6 and self.jump_delta < 6: self.jump_delta = 6*2 self.movey -= 33 # how high to jump self.collide_delta += 6 self.jump_delta += 6 class Enemy(pygame.sprite.Sprite): ''' Spawn an enemy ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.movey = 0 #self.image.convert_alpha() #self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): ''' enemy movement ''' distance = 80 speed = 8 self.movey += 3.2 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 if not self.rect.y >= worldy-ty-ty: self.rect.y += self.movey plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.rect.y = worldy-ty-ty class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy enemy_list = pygame.sprite.Group() # create enemy group enemy_list.add(enemy) # add enemy to group if lvl == 2: print("Level " + str(lvl) ) return enemy_list def loot(lvl,lloc): print(lvl) def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list def platform(lvl,tx,ty): plat_list = pygame.sprite.Group() ploc = [] i=0 if lvl == 1: ploc.append((0,worldy-ty-128,3)) ploc.append((300,worldy-ty-256,3)) ploc.append((500,worldy-ty-128,4)) while i < len(ploc): j=0 while j <= ploc[i][2]: plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png') plat_list.add(plat) j=j+1 print('run' + str(i) + str(ploc[i])) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return plat_list ''' Setup ''' worldx = 960 worldy = 720 fps = 40 # frame rate ani = 4 # animation cycles clock = pygame.time.Clock() pygame.init() main = True BLUE = (25,25,200) BLACK = (23,23,23 ) WHITE = (254,254,254) ALPHA = (0,255,0) world = pygame.display.set_mode([worldx,worldy]) backdrop = pygame.image.load(os.path.join('images','stage.png')).convert() backdropbox = world.get_rect() player = Player() # spawn player player.rect.x = 0 player.rect.y = 0 player_list = pygame.sprite.Group() player_list.add(player) steps = 10 forwardx = 600 backwardx = 230 eloc = [] eloc = [200,20] gloc = [] #gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630] tx = 64 #tile size ty = 64 #tile size i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,gloc,tx,ty ) plat_list = Level.platform( 1,tx,ty ) ''' Main loop ''' while main == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() main = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): print("LEFT") player.control(-steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): print("RIGHT") player.control(steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps,0) if event.key == pygame.K_UP or event.key == ord('w'): player.jump(plat_list) if event.key == ord('q'): pygame.quit() sys.exit() main = False # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll world.blit(backdrop, backdropbox) player.gravity() # check gravity player.update() player_list.draw(world) #refresh player position enemy_list.draw(world) # refresh enemies ground_list.draw(world) # refresh enemies plat_list.draw(world) # refresh platforms for e in enemy_list: e.move() pygame.display.flip() clock.tick(fps)via: https://opensource.com/article/19/12/python-platformer-game-run作者：Seth Kenlon 选题：lujun9972 译者：robsean 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在本期使用 Python Pygame 模块编写视频游戏中，学会如何使用跳跃来对抗重力。在本系列的 前一篇文章 中，你已经模拟了重力。但现在，你需要赋予你的角色跳跃的能力来对抗重力。跳跃是对重力作用的暂时延缓。在这一小段时间里，你是向上跳，而不是被重力拉着向下落。但你一旦到达了跳跃的最高点，重力就会重新发挥作用，将你拉回地面。在代码中，这种变化被表示为变量。首先，你需要为玩家精灵建立一个变量，使得 Python 能够跟踪该精灵是否正在跳跃中。一旦玩家精灵开始跳跃，他就会再次受到重力的作用，并被拉回最近的物体。设置跳跃状态变量你需要为你的 Player 类添加两个新变量：一个是为了跟踪你的角色是否正在跳跃中，可通过你的玩家精灵是否站在坚实的地面来确定一个是为了将玩家带回地面将如下两个变量添加到你的 Player 类中。在下方的代码中，注释前的部分用于提示上下文，因此只需要添加最后两行： self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 # 此处是重力相关变量 self.collide_delta = 0 self.jump_delta = 6第一个变量 collide_delta 被设为 0 是因为在正常状态下，玩家精灵没有处在跳跃中的状态。另一个变量 jump_delta 被设为 6，是为了防止精灵在第一次进入游戏世界时就发生反弹（实际上就是跳跃）。当你完成了本篇文章的示例，尝试把该变量设为 0 看看会发生什么。跳跃中的碰撞如果你是跳到一个蹦床上，那你的跳跃一定非常优美。但是如果你是跳向一面墙会发生什么呢？（千万不要去尝试！）不管你的起跳多么令人印象深刻，当你撞到比你更大更硬的物体时，你都会立马停下。（LCTT 译注：原理参考动量守恒定律）为了在你的视频游戏中模拟这一点，你需要在你的玩家精灵与地面等东西发生碰撞时，将 self.collide_delta 变量设为 0。如果你的 self.collide_delta 不是 0 而是其它的什么值，那么你的玩家就会发生跳跃，并且当你的玩家与墙或者地面发生碰撞时无法跳跃。在你的 Player 类的 update 方法中，将地面碰撞相关代码块修改为如下所示： ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.y = worldy-ty-ty self.collide_delta = 0 # 停止跳跃 if self.rect.y > g.rect.y: self.health -=1 print(self.health)这段代码块检查了地面精灵和玩家精灵之间发生的碰撞。当发生碰撞时，它会将玩家 Y 方向的坐标值设置为游戏窗口的高度减去一个瓷砖的高度再减去另一个瓷砖的高度。以此保证了玩家精灵是站在地面上，而不是嵌在地面里。同时它也将 self.collide_delta 设为 0，使得程序能够知道玩家未处在跳跃中。除此之外，它将 self.movey 设为 0，使得程序能够知道玩家当前未受到重力的牵引作用（这是游戏物理引擎的奇怪之处，一旦玩家落地，也就没有必要继续将玩家拉向地面）。此处 if 语句用来检测玩家是否已经落到地面之下，如果是，那就扣除一点生命值作为惩罚。此处假定了你希望当你的玩家落到地图之外时失去生命值。这个设定不是必需的，它只是平台类游戏的一种惯例。更有可能的是，你希望这个事件能够触发另一些事件，或者说是一种能够让你的现实世界玩家沉迷于让精灵掉到屏幕之外的东西。一种简单的恢复方式是在玩家精灵掉落到地图之外时，将 self.rect.y 重新设置为 0，这样它就会在地图上方重新生成，并落到坚实的地面上。撞向地面模拟的重力使你玩家的 Y 坐标不断增大（LCTT 译注：此处原文中为 0，但在 Pygame 中越靠下方 Y 坐标应越大）。要实现跳跃，完成如下代码使你的玩家精灵离开地面，飞向空中。在你的 Player 类的 update 方法中，添加如下代码来暂时延缓重力的作用： if self.collide_delta < 6 and self.jump_delta < 6: self.jump_delta = 6*2 self.movey -= 33 # 跳跃的高度 self.collide_delta += 6 self.jump_delta += 6根据此代码所示，跳跃使玩家精灵向空中移动了 33 个像素。此处是负 33 是因为在 Pygame 中，越小的数代表距离屏幕顶端越近。不过此事件视条件而定，只有当 self.collide_delta 小于 6（缺省值定义在你 Player 类的 init 方法中）并且 self.jump_delta 也于 6 的时候才会发生。此条件能够保证直到玩家碰到一个平台，才能触发另一次跳跃。换言之，它能够阻止空中二段跳。在某些特殊条件下，你可能不想阻止空中二段跳，或者说你允许玩家进行空中二段跳。举个栗子，如果玩家获得了某个战利品，那么在他被敌人攻击到之前，都能够拥有空中二段跳的能力。当你完成本篇文章中的示例，尝试将 self.collide_delta 和 self.jump_delta 设置为 0，从而获得百分之百的几率触发空中二段跳。在平台上着陆目前你已经定义了在玩家精灵摔落地面时的抵抗重力条件，但此时你的游戏代码仍保持平台与地面置于不同的列表中（就像本文中做的很多其他选择一样，这个设定并不是必需的，你可以尝试将地面作为另一种平台）。为了允许玩家精灵站在平台之上，你必须像检测地面碰撞一样，检测玩家精灵与平台精灵之间的碰撞。将如下代码放于你的 update 方法中： plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.collide_delta = 0 # 跳跃结束 self.movey = 0但此处还有一点需要考虑：平台悬在空中，也就意味着玩家可以通过从上面或者从下面接触平台来与之互动。确定平台如何与玩家互动取决于你，阻止玩家从下方到达平台也并不稀奇。将如下代码加到上方的代码块中，使得平台表现得像天花板或者说是藤架。只有在玩家精灵跳得比平台上沿更高时才能跳到平台上，但会阻止玩家从平台下方跳上来： if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty此处 if 语句代码块的第一个子句阻止玩家精灵从平台正下方跳到平台上。如果它检测到玩家精灵的坐标比平台更大（在 Pygame 中，坐标更大意味着在屏幕的更下方），那么将玩家精灵新的 Y 坐标设置为当前平台的 Y 坐标加上一个瓷砖的高度。实际效果就是保证玩家精灵距离平台一个瓷砖的高度，防止其从下方穿过平台。else 子句做了相反的事情。当程序运行到此处时，如果玩家精灵的 Y 坐标不比平台的更大，意味着玩家精灵是从空中落下（不论是由于玩家刚刚从此处生成，或者是玩家执行了跳跃）。在这种情况下，玩家精灵的 Y 坐标被设为平台的 Y 坐标减去一个瓷砖的高度（切记，在 Pygame 中更小的 Y 坐标代表在屏幕上的更高处）。这样就能保证玩家在平台上，除非他从平台上跳下来或者走下来。你也可以尝试其他的方式来处理玩家与平台之间的互动。举个栗子，也许玩家精灵被设定为处在平台的“前面”，他能够无障碍地跳跃穿过平台并站在上面。或者你可以设计一种平台会减缓而又不完全阻止玩家的跳跃过程。甚至你可以通过将不同平台分到不同列表中来混合搭配使用。触发一次跳跃目前为此，你的代码已经模拟了所有必需的跳跃条件，但仍缺少一个跳跃触发器。你的玩家精灵的 self.jump_delta 初始值被设置为 6，只有当它比 6 小的时候才会触发更新跳跃的代码。为跳跃变量设置一个新的设置方法，在你的 Player 类中创建一个 jump 方法，并将 self.jump_delta 设为小于 6 的值。通过使玩家精灵向空中移动 33 个像素，来暂时减缓重力的作用。 def jump(self,platform_list): self.jump_delta = 0不管你相信与否，这就是 jump 方法的全部。剩余的部分在 update 方法中，你已经在前面实现了相关代码。要使你游戏中的跳跃功能生效，还有最后一件事情要做。如果你想不起来是什么，运行游戏并观察跳跃是如何生效的。问题就在于你的主循环中没有调用 jump 方法。先前你已经为该方法创建了一个按键占位符，现在，跳跃键所做的就是将 jump 打印到终端。调用 jump 方法在你的主循环中，将上方向键的效果从打印一条调试语句，改为调用 jump 方法。注意此处，与 update 方法类似，jump 方法也需要检测碰撞，因此你需要告诉它使用哪个 plat_list。 if event.key == pygame.K_UP or event.key == ord('w'): player.jump(plat_list)如果你倾向于使用空格键作为跳跃键，使用 pygame.K_SPACE 替代 pygame.K_UP 作为按键。另一种选择，你可以同时使用两种方式（使用单独的 if 语句），给玩家多一种选择。现在来尝试你的游戏吧！在下一篇文章中，你将让你的游戏卷动起来。以下是目前为止的所有代码：#!/usr/bin/env python3 # draw a world # add a player and player control # add player movement # add enemy and basic collision # add platform # add gravity # add jumping # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import pygame import sys import os ''' Objects ''' class Platform(pygame.sprite.Sprite): # x 坐标，y 坐标，图像宽度，图像高度，图像文件 def __init__(self,xloc,yloc,imgw,imgh,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)).convert() self.image.convert_alpha() self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): ''' 生成一个玩家 ''' def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.collide_delta = 0 self.jump_delta = 6 self.score = 1 self.images = [] for i in range(1,9): img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def jump(self,platform_list): self.jump_delta = 0 def gravity(self): self.movey += 3.2 # how fast player falls if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty def control(self,x,y): ''' 控制玩家移动 ''' self.movex += x self.movey += y def update(self): ''' 更新精灵位置 ''' self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # 向左移动 if self.movex < 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[self.frame//ani] # 向右移动 if self.movex > 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[(self.frame//ani)+4] # 碰撞 enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 #print(self.health) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.collide_delta = 0 # stop jumping self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.y = worldy-ty-ty self.collide_delta = 0 # stop jumping if self.rect.y > g.rect.y: self.health -=1 print(self.health) if self.collide_delta < 6 and self.jump_delta < 6: self.jump_delta = 6*2 self.movey -= 33 # how high to jump self.collide_delta += 6 self.jump_delta += 6 class Enemy(pygame.sprite.Sprite): ''' 生成一个敌人 ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.movey = 0 #self.image.convert_alpha() #self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): ''' 敌人移动 ''' distance = 80 speed = 8 self.movey += 3.2 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 if not self.rect.y >= worldy-ty-ty: self.rect.y += self.movey plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.movey = 0 if self.rect.y > p.rect.y: self.rect.y = p.rect.y+ty else: self.rect.y = p.rect.y-ty ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.rect.y = worldy-ty-ty class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # 生成敌人 enemy_list = pygame.sprite.Group() # 创建敌人组 enemy_list.add(enemy) # 将敌人添加到敌人组 if lvl == 2: print("Level " + str(lvl) ) return enemy_list def loot(lvl,lloc): print(lvl) def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list def platform(lvl,tx,ty): plat_list = pygame.sprite.Group() ploc = [] i=0 if lvl == 1: ploc.append((0,worldy-ty-128,3)) ploc.append((300,worldy-ty-256,3)) ploc.append((500,worldy-ty-128,4)) while i < len(ploc): j=0 while j <= ploc[i][2]: plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png') plat_list.add(plat) j=j+1 print('run' + str(i) + str(ploc[i])) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return plat_list ''' Setup ''' worldx = 960 worldy = 720 fps = 40 # 帧率 ani = 4 # 动画循环 clock = pygame.time.Clock() pygame.init() main = True BLUE = (25,25,200) BLACK = (23,23,23 ) WHITE = (254,254,254) ALPHA = (0,255,0) world = pygame.display.set_mode([worldx,worldy]) backdrop = pygame.image.load(os.path.join('images','stage.png')).convert() backdropbox = world.get_rect() player = Player() # 生成玩家 player.rect.x = 0 player.rect.y = 0 player_list = pygame.sprite.Group() player_list.add(player) steps = 10 # how fast to move jump = -24 eloc = [] eloc = [200,20] gloc = [] #gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630] tx = 64 # 瓷砖尺寸 ty = 64 # 瓷砖尺寸 i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,gloc,tx,ty ) plat_list = Level.platform( 1,tx,ty ) ''' 主循环 ''' while main == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() main = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): print("LEFT") player.control(-steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): print("RIGHT") player.control(steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps,0) if event.key == pygame.K_UP or event.key == ord('w'): player.jump(plat_list) if event.key == ord('q'): pygame.quit() sys.exit() main = False # world.fill(BLACK) world.blit(backdrop, backdropbox) player.gravity() # 检查重力 player.update() player_list.draw(world) # 刷新玩家位置 enemy_list.draw(world) # 刷新敌人 ground_list.draw(world) # 刷新地面 plat_list.draw(world) # 刷新平台 for e in enemy_list: e.move() pygame.display.flip() clock.tick(fps)本期是使用 Pygame 模块在 Python 3 中创建视频游戏连载系列的第 7 期。往期文章为：通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人在 Pygame 游戏中放置平台在你的 Python 游戏中模拟引力via: https://opensource.com/article/19/12/jumping-python-platformer-game作者：Seth Kenlon 选题：lujun9972 译者：cycoe 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
学习如何使用 Python 的 Pygame 模块编程电脑游戏，并开始操作引力。真实的世界充满了运动和生活。物理学使得真实的生活如此忙碌和动态。物理学是物质在空间中运动的方式。既然一个电脑游戏世界没有物质，它也就没有物理学规律，使用游戏程序员不得不模拟物理学。从大多数电脑游戏来说，这里基本上仅有两个方面的物理学是重要的：引力和碰撞。当你添加一个敌人到你的游戏中时，你实现了一些碰撞检测，但是这篇文章要添加更多的东西，因为引力需要碰撞检测。想想为什么引力可能涉及碰撞。如果你不能想到任何原因，不要担心 —— 它会随着你开发示例代码工作而且显然。在真实世界中的引力是有质量的物体来相互吸引的倾向性。物体（质量）越大，它施加越大的引力作用。在电脑游戏物理学中，你不必创建质量足够大的物体来证明引力的正确；你可以在电脑游戏世界本身中仅编程一个物体落向假设的最大的对象的倾向。添加一个引力函数记住你的玩家已经有了一个决定动作的属性。使用这个属性来将玩家精灵拉向屏幕底部。在 Pygame 中，较高的数字更接近屏幕的底部边缘。在真实的世界中，引力影响一切。然而，在平台游戏中，引力是有选择性的 —— 如果你添加引力到你的整个游戏世界，你的所有平台都将掉到地上。反之，你可以仅添加引力到你的玩家和敌人精灵中。首先，在你的 Player 类中添加一个 gravity 函数： def gravity(self): self.movey += 3.2 # 玩家掉落的多快这是一个简单的函数。首先，不管你的玩家是否想运动，你设置你的玩家垂直运动。也就是说，你已经编程你的玩家总是在下降。这基本上就是引力。为使引力函数生效，你必须在你的主循环中调用它。这样，当每一个处理循环时，Python 都应用下落运动到你的玩家。在这代码中，添加第一行到你的循环中： player.gravity() # 检查引力 player.update()启动你的游戏来看看会发生什么。要注意，因为它发生的很快：你是玩家从天空上下落，马上掉出了你的游戏屏幕。你的引力模拟是工作的，但是，也许太好了。作为一次试验，尝试更改你玩家下落的速度。给引力添加一个地板你的游戏没有办法发现你的角色掉落出世界的问题。在一些游戏中，如果一个玩家掉落出世界，该精灵被删除，并在某个新的位置重生。在另一些游戏中，玩家会丢失分数或一条生命。当一个玩家掉落出世界时，不管你想发生什么，你必须能够侦测出玩家何时消失在屏幕外。在 Python 中，要检查一个条件，你可以使用一个 if 语句。你必需查看你玩家是否正在掉落，以及你的玩家掉落的程度。如果你的玩家掉落到屏幕的底部，那么你可以做一些事情。简化一下，设置玩家精灵的位置为底部边缘上方 20 像素。使你的 gravity 函数看起来像这样： def gravity(self): self.movey += 3.2 # 玩家掉落的多快 if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty然后，启动你的游戏。你的精灵仍然下落，但是它停在屏幕的底部。不过，你也许不能看到你在地面层之上的精灵。一个简单的解决方法是，在精灵碰撞游戏世界的底部后，通过添加另一个 -ty 到它的新 Y 位置，从而使你的精灵弹跳到更高处： def gravity(self): self.movey += 3.2 # 玩家掉落的多快 if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty-ty现在你的玩家在屏幕底部弹跳，恰好在你地面精灵上面。你的玩家真正需要的是反抗引力的方法。引力问题是，你不能反抗它，除非你有一些东西来推开引力作用。因此，在接下来的文章中，你将添加地面和平台碰撞以及跳跃能力。在这期间，尝试应用引力到敌人精灵。到目前为止，这里是全部的代码：#!/usr/bin/env python3 # draw a world # add a player and player control # add player movement # add enemy and basic collision # add platform # add gravity # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import pygame import sys import os ''' Objects ''' class Platform(pygame.sprite.Sprite): # x location, y location, img width, img height, img file def __init__(self,xloc,yloc,imgw,imgh,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)).convert() self.image.convert_alpha() self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): ''' Spawn a player ''' def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.score = 1 self.images = [] for i in range(1,9): img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def gravity(self): self.movey += 3.2 # how fast player falls if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty-ty def control(self,x,y): ''' control player movement ''' self.movex += x self.movey += y def update(self): ''' Update sprite position ''' self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[self.frame//ani] # moving right if self.movex > 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[(self.frame//ani)+4] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 print(self.health) ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.health -= 1 print(self.health) class Enemy(pygame.sprite.Sprite): ''' Spawn an enemy ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) #self.image.convert_alpha() #self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): ''' enemy movement ''' distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy enemy_list = pygame.sprite.Group() # create enemy group enemy_list.add(enemy) # add enemy to group if lvl == 2: print("Level " + str(lvl) ) return enemy_list def loot(lvl,lloc): print(lvl) def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list def platform(lvl,tx,ty): plat_list = pygame.sprite.Group() ploc = [] i=0 if lvl == 1: ploc.append((0,worldy-ty-128,3)) ploc.append((300,worldy-ty-256,3)) ploc.append((500,worldy-ty-128,4)) while i < len(ploc): j=0 while j <= ploc[i][2]: plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png') plat_list.add(plat) j=j+1 print('run' + str(i) + str(ploc[i])) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return plat_list ''' Setup ''' worldx = 960 worldy = 720 fps = 40 # frame rate ani = 4 # animation cycles clock = pygame.time.Clock() pygame.init() main = True BLUE = (25,25,200) BLACK = (23,23,23 ) WHITE = (254,254,254) ALPHA = (0,255,0) world = pygame.display.set_mode([worldx,worldy]) backdrop = pygame.image.load(os.path.join('images','stage.png')).convert() backdropbox = world.get_rect() player = Player() # spawn player player.rect.x = 0 player.rect.y = 0 player_list = pygame.sprite.Group() player_list.add(player) steps = 10 # how fast to move eloc = [] eloc = [200,20] gloc = [] #gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630] tx = 64 #tile size ty = 64 #tile size i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,gloc,tx,ty ) plat_list = Level.platform( 1,tx,ty ) ''' Main loop ''' while main == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() main = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): print("LEFT") player.control(-steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): print("RIGHT") player.control(steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.key == ord('q'): pygame.quit() sys.exit() main = False world.blit(backdrop, backdropbox) player.gravity() # check gravity player.update() player_list.draw(world) enemy_list.draw(world) ground_list.draw(world) plat_list.draw(world) for e in enemy_list: e.move() pygame.display.flip() clock.tick(fps)这是仍在进行中的关于使用 Pygame 模块来在 Python 3 在创建电脑游戏的第七部分。先前的文章是：通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人在 Pygame 游戏中放置平台via: https://opensource.com/article/19/11/simulate-gravity-python作者：Seth Kenlon 选题：lujun9972 译者：robsean 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在这个从零构建一个 Python 游戏系列的第六部分中，为你的角色创建一些平台来旅行。这是仍在进行中的关于使用 Pygame 模块来在 Python 3 中创建电脑游戏的系列文章的第六部分。先前的文章是：通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程使用 Python 和 Pygame 模块构建一个游戏框架如何在你的 Python 游戏中添加一个玩家用 Pygame 使你的游戏角色移动起来如何向你的 Python 游戏中添加一个敌人一个平台类游戏需要平台。在 Pygame 中，平台本身也是个妖精，正像你那个可玩的妖精。这一点是重要的，因为有个是对象的平台，可以使你的玩家妖精更容易与之互动。创建平台有两个主要步骤。首先，你必须给该对象编写代码，然后，你必须映射出你希望该对象出现的位置。编码平台对象要构建一个平台对象，你要创建一个名为 Platform 的类。它是一个妖精，正像你的 Player 妖精 一样，带有很多相同的属性。你的 Platform 类需要知道很多平台类型的信息，它应该出现在游戏世界的哪里、它应该包含的什么图片等等。这其中很多信息可能还尚不存在，这要看你为你的游戏计划了多少，但是没有关系。正如直到移动你的游戏角色那篇文章结束时，你都没有告诉你的玩家妖精移动速度有多快，你不必事先告诉 Platform 每一件事。在这系列中你所写的脚本的开头附近，创建一个新的类。在这个代码示例中前三行是用于说明上下文，因此在注释的下面添加代码：import pygame import sys import os ## 新代码如下： class Platform(pygame.sprite.Sprite): # x location, y location, img width, img height, img file def __init__(self,xloc,yloc,imgw,imgh,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)).convert() self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc当被调用时，这个类在某个 X 和 Y 位置上创建一个屏上对象，具有某种宽度和高度，并使用某种图像作为纹理。这与如何在屏上绘制出玩家或敌人非常类似。平台的类型下一步是绘制出你的平台需要出现的地方。瓷砖方式实现平台类游戏世界有几种不同的方法。在最初的横向滚轴游戏中，例如，马里奥超级兄弟和刺猬索尼克，这个技巧是使用“瓷砖”方式，也就是说有几个代表地面和各种平台的块，并且这些块被重复使用来制作一个关卡。你只能有 8 或 12 种不同的块，你可以将它们排列在屏幕上来创建地面、浮动的平台，以及你游戏中需要的一切其它的事物。有人发现这是制作游戏最容易的方法了，因为你只需要制作（或下载）一小组关卡素材就能创建很多不同的关卡。然而，这里的代码需要一点数学知识。SuperTux ，一个基于瓷砖的电脑游戏。手工绘制方式另一种方法是将每个素材作为一个整体图像。如果你喜欢为游戏世界创建素材，那你会在用图形应用程序构建游戏世界的每个部分上花费很多时间。这种方法不需要太多的数学知识，因为所有的平台都是整体的、完整的对象，你只需要告诉 Python 将它们放在屏幕上的什么位置。每种方法都有优势和劣势，并且根据于你选择使用的方式，代码稍有不同。我将覆盖这两方面，所以你可以在你的工程中使用一种或另一种，甚至两者的混合。关卡绘制总的来说，绘制你的游戏世界是关卡设计和游戏编程中的一个重要的部分。这需要数学知识，但是没有什么太难的，而且 Python 擅长数学，它会有所帮助。你也许发现先在纸张上设计是有用的。拿一张表格纸，并绘制一个方框来代表你的游戏窗体。在方框中绘制平台，并标记其每一个平台的 X 和 Y 坐标，以及它的宽度和高度。在方框中的实际位置没有必要是精确的，你只要保持数字合理即可。譬如，假设你的屏幕是 720 像素宽，那么你不能在一个屏幕上放 8 块 100 像素的平台。当然，不是你游戏中的所有平台都必须容纳在一个屏幕大小的方框里，因为你的游戏将随着你的玩家行走而滚动。所以，可以继续绘制你的游戏世界到第一屏幕的右侧，直到关卡结束。如果你更喜欢精确一点，你可以使用方格纸。当设计一个瓷砖类的游戏时，这是特别有用的，因为每个方格可以代表一个瓷砖。一个关卡地图示例。坐标系你可能已经在学校中学习过笛卡尔坐标系。你学习的东西也适用于 Pygame，除了在 Pygame 中你的游戏世界的坐标系的原点 0,0 是放置在你的屏幕的左上角而不是在中间，是你在地理课上用过的坐标是在中间的。在 Pygame 中的坐标示例。X 轴起始于最左边的 0，向右无限增加。Y 轴起始于屏幕顶部的 0，向下延伸。图片大小如果你不知道你的玩家、敌人、平台是多大的，绘制出一个游戏世界是毫无意义的。你可以在图形程序中找到你的平台或瓷砖的尺寸。例如在 Krita 中，单击“图像”菜单，并选择“属性”。你可以在“属性”窗口的最顶部处找到它的尺寸。另外，你也可以创建一个简单的 Python 脚本来告诉你的一个图像的尺寸。打开一个新的文本文件，并输入这些代码到其中：#!/usr/bin/env python3 from PIL import Image import os.path import sys if len(sys.argv) > 1: print(sys.argv[1]) else: sys.exit('Syntax: identify.py [filename]') pic = sys.argv[1] dim = Image.open(pic) X = dim.size[0] Y = dim.size[1] print(X,Y)保存该文本文件为 identify.py。要使用这个脚本，你必须安装一些额外的 Python 模块，它们包含了这个脚本中新使用的关键字：$ pip3 install Pillow --user一旦安装好，在你游戏工程目录中运行这个脚本：$ python3 ./identify.py images/ground.png (1080, 97)在这个示例中，地面平台的图形的大小是 1080 像素宽和 97 像素高。平台块如果你选择单独地绘制每个素材，你必须创建想要插入到你的游戏世界中的几个平台和其它元素，每个素材都放在它自己的文件中。换句话说，你应该让每个素材都有一个文件，像这样：每个对象一个图形文件。你可以按照你希望的次数重复使用每个平台，只要确保每个文件仅包含一个平台。你不能使用一个文件包含全部素材，像这样：你的关卡不能是一个图形文件。当你完成时，你可能希望你的游戏看起来像这样，但是如果你在一个大文件中创建你的关卡，你就没有方法从背景中区分出一个平台，因此，要么把对象绘制在它们自己的文件中，要么从一个更大的文件中裁剪出它们，并保存为单独的副本。注意： 如同你的其它素材，你可以使用 GIMP、Krita、MyPaint，或 Inkscape 来创建你的游戏素材。平台出现在每个关卡开始的屏幕上，因此你必须在你的 Level 类中添加一个 platform 函数。在这里特例是地面平台，它重要到应该拥有它自己的一个组。通过把地面看作一组特殊类型的平台，你可以选择它是否滚动，或它上面是否可以站立，而其它平台可以漂浮在它上面。这取决于你。添加这两个函数到你的 Level 类：def ground(lvl,x,y,w,h): ground_list = pygame.sprite.Group() if lvl == 1: ground = Platform(x,y,w,h,'block-ground.png') ground_list.add(ground) if lvl == 2: print("Level " + str(lvl) ) return ground_list def platform( lvl ): plat_list = pygame.sprite.Group() if lvl == 1: plat = Platform(200, worldy-97-128, 285,67,'block-big.png') plat_list.add(plat) plat = Platform(500, worldy-97-320, 197,54,'block-small.png') plat_list.add(plat) if lvl == 2: print("Level " + str(lvl) ) return plat_listground 函数需要一个 X 和 Y 位置，以便 Pygame 知道在哪里放置地面平台。它也需要知道平台的宽度和高度，这样 Pygame 知道地面延伸到每个方向有多远。该函数使用你的 Platform 类来生成一个屏上对象，然后将这个对象添加到 ground_list 组。platform 函数本质上是相同的，除了其有更多的平台。在这个示例中，仅有两个平台，但是你可以想有多少就有多少。在进入一个平台后，在列出另一个前你必须添加它到 plat_list 中。如果你不添加平台到组中，那么它将不出现在你的游戏中。提示： 很难想象你的游戏世界的 0 是在顶部，因为在真实世界中发生的情况是相反的；当估计你有多高时，你不会从上往下测量你自己，而是从脚到头顶来测量。如果对你来说从“地面”上来构建你的游戏世界更容易，将 Y 轴值表示为负数可能有帮助。例如，你知道你的游戏世界的底部是 worldy 的值。因此 worldy 减去地面的高度（在这个示例中是 97）是你的玩家正常站立的位置。如果你的角色是 64 像素高，那么地面减去 128 正好是你的玩家的两倍高。事实上，一个放置在 128 像素处平台大约是相对于你的玩家的两层楼高度。一个平台在 -320 处比三层楼更高。等等。正像你现在可能所知的，如果你不使用它们，你的类和函数是没有价值的。添加这些代码到你的设置部分（第一行只是上下文，所以添加最后两行）：enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,0,worldy-97,1080,97 ) plat_list = Level.platform( 1 )并把这些行加到你的主循环（再一次，第一行仅用于上下文）：enemy_list.draw(world) # 刷新敌人 ground_list.draw(world) # 刷新地面 plat_list.draw(world) # 刷新平台瓷砖平台瓷砖类游戏世界更容易制作，因为你只需要在前面绘制一些块，就能在游戏中一再使用它们创建每个平台。在像 OpenGameArt.org 这样的网站上甚至有一套瓷砖供你来使用。Platform 类与在前面部分中的类是相同的。ground 和 platform 在 Level 类中，然而，必须使用循环来计算使用多少块来创建每个平台。如果你打算在你的游戏世界中有一个坚固的地面，这种地面是很简单的。你只需要从整个窗口的一边到另一边“克隆”你的地面瓷砖。例如，你可以创建一个 X 和 Y 值的列表来规定每个瓷砖应该放置的位置，然后使用一个循环来获取每个值并绘制每一个瓷砖。这仅是一个示例，所以不要添加这到你的代码：# Do not add this to your code gloc = [0,656,64,656,128,656,192,656,256,656,320,656,384,656]不过，如果你仔细看，你可以看到所有的 Y 值是相同的，X 值以 64 的增量不断地增加 —— 这就是瓷砖的大小。这种重复是精确地，是计算机擅长的，因此你可以使用一点数学逻辑来让计算机为你做所有的计算：添加这些到你的脚本的设置部分：gloc = [] tx = 64 ty = 64 i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 ground_list = Level.ground( 1,gloc,tx,ty )现在，不管你的窗口的大小，Python 会通过瓷砖的宽度分割游戏世界的宽度，并创建一个数组列表列出每个 X 值。这里不计算 Y 值，因为在平的地面上这个从不会变化。为了在一个函数中使用数组，使用一个 while 循环，查看每个条目并在适当的位置添加一个地面瓷砖：def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i],worldy-ty,tx,ty,'tile-ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list除了 while 循环，这几乎与在上面一部分中提供的瓷砖类平台的 ground 函数的代码相同。对于移动的平台，原理是相似的，但是这里有一些技巧可以使它简单。你可以通过它的起始像素（它的 X 值）、距地面的高度（它的 Y 值）、绘制多少瓷砖来定义一个平台，而不是通过像素绘制每个平台。这样，你不必操心每个平台的宽度和高度。这个技巧的逻辑有一点复杂，因此请仔细复制这些代码。有一个 while 循环嵌套在另一个 while 循环的内部，因为这个函数必须考虑每个数组项的三个值来成功地建造一个完整的平台。在这个示例中，这里仅有三个平台以 ploc.append 语句定义，但是你的游戏可能需要更多，因此你需要多少就定义多少。当然，有一些不会出现，因为它们远在屏幕外，但是一旦当你进行滚动时，它们将呈现在眼前。def platform(lvl,tx,ty): plat_list = pygame.sprite.Group() ploc = [] i=0 if lvl == 1: ploc.append((200,worldy-ty-128,3)) ploc.append((300,worldy-ty-256,3)) ploc.append((500,worldy-ty-128,4)) while i < len(ploc): j=0 while j <= ploc[i][2]: plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'tile.png') plat_list.add(plat) j=j+1 print('run' + str(i) + str(ploc[i])) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return plat_list要让这些平台出现在你的游戏世界，它们必须出现在你的主循环中。如果你还没有这样做，添加这些行到你的主循环（再一次，第一行仅被用于上下文）中： enemy_list.draw(world) # 刷新敌人 ground_list.draw(world) # 刷新地面 plat_list.draw(world) # 刷新平台启动你的游戏，根据需要调整你的平台的放置位置。如果你看不见屏幕外产生的平台，不要担心；你不久后就可以修复它。到目前为止，这是游戏的图片和代码：到目前为止，我们的 Pygame 平台。#!/usr/bin/env python3 # draw a world # add a player and player control # add player movement # add enemy and basic collision # add platform # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import pygame import sys import os ''' Objects ''' class Platform(pygame.sprite.Sprite): # x location, y location, img width, img height, img file def __init__(self,xloc,yloc,imgw,imgh,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)).convert() self.image.convert_alpha() self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): ''' Spawn a player ''' def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.score = 1 self.images = [] for i in range(1,9): img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def control(self,x,y): ''' control player movement ''' self.movex += x self.movey += y def update(self): ''' Update sprite position ''' self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[self.frame//ani] # moving right if self.movex > 0: self.frame += 1 if self.frame > ani*3: self.frame = 0 self.image = self.images[(self.frame//ani)+4] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 print(self.health) ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.health -= 1 print(self.health) class Enemy(pygame.sprite.Sprite): ''' Spawn an enemy ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) #self.image.convert_alpha() #self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): ''' enemy movement ''' distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy enemy_list = pygame.sprite.Group() # create enemy group enemy_list.add(enemy) # add enemy to group if lvl == 2: print("Level " + str(lvl) ) return enemy_list def loot(lvl,lloc): print(lvl) def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): print("blockgen:" + str(i)) ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list ''' Setup ''' worldx = 960 worldy = 720 fps = 40 # frame rate ani = 4 # animation cycles clock = pygame.time.Clock() pygame.init() main = True BLUE = (25,25,200) BLACK = (23,23,23 ) WHITE = (254,254,254) ALPHA = (0,255,0) world = pygame.display.set_mode([worldx,worldy]) backdrop = pygame.image.load(os.path.join('images','stage.png')).convert() backdropbox = world.get_rect() player = Player() # spawn player player.rect.x = 0 player.rect.y = 0 player_list = pygame.sprite.Group() player_list.add(player) steps = 10 # how fast to move eloc = [] eloc = [200,20] gloc = [] #gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630] tx = 64 #tile size ty = 64 #tile size i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 print("block: " + str(i)) enemy_list = Level.bad( 1, eloc ) ground_list = Level.ground( 1,gloc,tx,ty ) ''' Main loop ''' while main == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() main = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(-steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps,0) if event.key == ord('q'): pygame.quit() sys.exit() main = False # world.fill(BLACK) world.blit(backdrop, backdropbox) player.update() player_list.draw(world) #refresh player position enemy_list.draw(world) # refresh enemies ground_list.draw(world) # refresh enemies for e in enemy_list: e.move() pygame.display.flip() clock.tick(fps)（LCTT 译注：到本文翻译完为止，该系列已经近一年没有继续更新了~）via: https://opensource.com/article/18/7/put-platforms-python-game作者：Seth Kenlon 选题：lujun9972 译者：robsean 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在本系列的第五部分，学习如何增加一个坏蛋与你的好人战斗。在本系列的前几篇文章中（参见 第一部分、第二部分、第三部分 以及 第四部分)，你已经学习了如何使用 Pygame 和 Python 在一个空白的视频游戏世界中生成一个可玩的角色。但没有恶棍，英雄又将如何？如果你没有敌人，那将会是一个非常无聊的游戏。所以在此篇文章中，你将为你的游戏添加一个敌人并构建一个用于创建关卡的框架。在对玩家妖精实现全部功能之前，就来实现一个敌人似乎就很奇怪。但你已经学到了很多东西，创造恶棍与与创造玩家妖精非常相似。所以放轻松，使用你已经掌握的知识，看看能挑起怎样一些麻烦。针对本次训练，你能够从 Open Game Art 下载一些预创建的素材。此处是我使用的一些素材：印加花砖（LCTT 译注：游戏中使用的花砖贴图）一些侵略者妖精、角色、物体以及特效创造敌方妖精是的，不管你意识到与否，你其实已经知道如何去实现敌人。这个过程与创造一个玩家妖精非常相似：创建一个类用于敌人生成创建 update 方法使得敌人能够检测碰撞创建 move 方法使得敌人能够四处游荡从类入手。从概念上看，它与你的 Player 类大体相同。你设置一张或者一组图片，然后设置妖精的初始位置。在继续下一步之前，确保你有一张你的敌人的图像，即使只是一张临时图像。将图像放在你的游戏项目的 images 目录（你放置你的玩家图像的相同目录）。如果所有的活物都拥有动画，那么游戏看起来会好得多。为敌方妖精设置动画与为玩家妖精设置动画具有相同的方式。但现在，为了保持简单，我们使用一个没有动画的妖精。在你代码 objects 节的顶部，使用以下代码创建一个叫做 Enemy 的类：class Enemy(pygame.sprite.Sprite): ''' 生成一个敌人 ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y如果你想让你的敌人动起来，使用让你的玩家拥有动画的 相同方式。生成一个敌人你能够通过告诉类，妖精应使用哪张图像，应出现在世界上的什么地方，来生成不只一个敌人。这意味着，你能够使用相同的敌人类，在游戏世界的任意地方生成任意数量的敌方妖精。你需要做的仅仅是调用这个类，并告诉它应使用哪张图像，以及你期望生成点的 X 和 Y 坐标。再次，这从原则上与生成一个玩家精灵相似。在你脚本的 setup 节添加如下代码：enemy = Enemy(20,200,'yeti.png') # 生成敌人 enemy_list = pygame.sprite.Group() # 创建敌人组 enemy_list.add(enemy) # 将敌人加入敌人组在示例代码中，X 坐标为 20，Y 坐标为 200。你可能需要根据你的敌方妖精的大小，来调整这些数字，但尽量生成在一个范围内，使得你的玩家妖精能够碰到它。Yeti.png 是用于敌人的图像。接下来，将敌人组的所有敌人绘制在屏幕上。现在，你只有一个敌人，如果你想要更多你可以稍后添加。一但你将一个敌人加入敌人组，它就会在主循环中被绘制在屏幕上。中间这一行是你需要添加的新行： player_list.draw(world) enemy_list.draw(world) # 刷新敌人 pygame.display.flip()启动你的游戏，你的敌人会出现在游戏世界中你选择的 X 和 Y 坐标处。关卡一你的游戏仍处在襁褓期，但你可能想要为它添加另一个关卡。为你的程序做好未来规划非常重要，因为随着你学会更多的编程技巧，你的程序也会随之成长。即使你现在仍没有一个完整的关卡，你也应该按照假设会有很多关卡来编程。思考一下“关卡”是什么。你如何知道你是在游戏中的一个特定关卡中呢？你可以把关卡想成一系列项目的集合。就像你刚刚创建的这个平台中，一个关卡，包含了平台、敌人放置、战利品等的一个特定排列。你可以创建一个类，用来在你的玩家附近创建关卡。最终，当你创建了一个以上的关卡，你就可以在你的玩家达到特定目标时，使用这个类生成下一个关卡。将你写的用于生成敌人及其群组的代码，移动到一个每次生成新关卡时都会被调用的新函数中。你需要做一些修改，使得每次你创建新关卡时，你都能够创建一些敌人。class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # 生成敌人 enemy_list = pygame.sprite.Group() # 生成敌人组 enemy_list.add(enemy) # 将敌人加入敌人组 if lvl == 2: print("Level " + str(lvl) ) return enemy_listreturn 语句确保了当你调用 Level.bad 方法时，你将会得到一个 enemy_list 变量包含了所有你定义的敌人。因为你现在将创造敌人作为每个关卡的一部分，你的 setup 部分也需要做些更改。不同于创造一个敌人，取而代之的是你必须去定义敌人在那里生成，以及敌人属于哪个关卡。eloc = [] eloc = [200,20] enemy_list = Level.bad( 1, eloc )再次运行游戏来确认你的关卡生成正确。与往常一样，你应该会看到你的玩家，并且能看到你在本章节中添加的敌人。痛击敌人一个敌人如果对玩家没有效果，那么它不太算得上是一个敌人。当玩家与敌人发生碰撞时，他们通常会对玩家造成伤害。因为你可能想要去跟踪玩家的生命值，因此碰撞检测发生在 Player 类，而不是 Enemy 类中。当然如果你想，你也可以跟踪敌人的生命值。它们之间的逻辑与代码大体相似，现在，我们只需要跟踪玩家的生命值。为了跟踪玩家的生命值，你必须为它确定一个变量。代码示例中的第一行是上下文提示，那么将第二行代码添加到你的 Player 类中： self.frame = 0 self.health = 10在你 Player 类的 update 方法中，添加如下代码块： hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in hit_list: self.health -= 1 print(self.health)这段代码使用 Pygame 的 sprite.spritecollide 方法，建立了一个碰撞检测器，称作 enemy_hit。每当它的父类妖精（生成检测器的玩家妖精）的碰撞区触碰到 enemy_list 中的任一妖精的碰撞区时，碰撞检测器都会发出一个信号。当这个信号被接收，for 循环就会被触发，同时扣除一点玩家生命值。一旦这段代码出现在你 Player 类的 update 方法，并且 update 方法在你的主循环中被调用，Pygame 会在每个时钟滴答中检测一次碰撞。移动敌人如果你愿意，静止不动的敌人也可以很有用，比如能够对你的玩家造成伤害的尖刺和陷阱。但如果敌人能够四处徘徊，那么游戏将更富有挑战。与玩家妖精不同，敌方妖精不是由玩家控制，因此它必须自动移动。最终，你的游戏世界将会滚动。那么，如何在游戏世界自身滚动的情况下，使游戏世界中的敌人前后移动呢？举个例子，你告诉你的敌方妖精向右移动 10 步，向左移动 10 步。但敌方妖精不会计数，因此你需要创建一个变量来跟踪你的敌人已经移动了多少步，并根据计数变量的值来向左或向右移动你的敌人。首先，在你的 Enemy 类中创建计数变量。添加以下代码示例中的最后一行代码： self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 # 计数变量然后，在你的 Enemy 类中创建一个 move 方法。使用 if-else 循环来创建一个所谓的死循环：如果计数在 0 到 100 之间，向右移动；如果计数在 100 到 200 之间，向左移动；如果计数大于 200，则将计数重置为 0。死循环没有终点，因为循环判断条件永远为真，所以它将永远循环下去。在此情况下，计数器总是介于 0 到 100 或 100 到 200 之间，因此敌人会永远地从左向右再从右向左移动。你用于敌人在每个方向上移动距离的具体值，取决于你的屏幕尺寸，更确切地说，取决于你的敌人移动的平台大小。从较小的值开始，依据习惯逐步提高数值。首先进行如下尝试： def move(self): ''' 敌人移动 ''' distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1你可以根据需要调整距离和速度。当你现在启动游戏，这段代码有效果吗？当然不，你应该也知道原因。你必须在主循环中调用 move 方法。如下示例代码中的第一行是上下文提示，那么添加最后两行代码： enemy_list.draw(world) #refresh enemy for e in enemy_list: e.move()启动你的游戏看看当你打击敌人时发生了什么。你可能需要调整妖精的生成地点，使得你的玩家和敌人能够碰撞。当他们发生碰撞时，查看 IDLE 或 Ninja-IDE 的控制台，你可以看到生命值正在被扣除。你应该已经注意到，在你的玩家和敌人接触时，生命值在时刻被扣除。这是一个问题，但你将在对 Python 进行更多练习以后解决它。现在，尝试添加更多敌人。记得将每个敌人加入 enemy_list。作为一个练习，看看你能否想到如何改变不同敌方妖精的移动距离。via: https://opensource.com/article/18/5/pygame-enemy作者：Seth Kenlon 选题：lujun9972 译者：cycoe 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出