雪碧模块简介

作者

皮特·辛纳斯

联系方式

pete@shinners.org

PYGAME 1.3版附带了一个新模块, pygame.sprite 。该模块是用Python语言编写的,包含一些用于管理游戏对象的高级类。通过充分利用此模块的潜力,您可以轻松管理和绘制您的游戏对象。精灵类是非常优化的,所以你的游戏在使用精灵模块时可能会比没有精灵模块时运行得更快。

Sprite模块也应该是非常通用的。事实证明,你几乎可以在任何类型的游戏中使用它。所有这些灵活性都伴随着轻微的惩罚,需要一点理解才能正确使用它。这个 reference documentation 因为Sprite模块可以让您保持运行,但您可能需要更多地解释如何使用 pygame.sprite 在你自己的游戏里。

一些电子游戏的例子(如“黑猩猩”和“外星人”)已经被更新,使用了精灵模块。你可能想先看看这些,看看这个精灵模块是怎么回事。黑猩猩模块甚至有自己的逐行教程,这可能有助于更多地理解使用python和pyGame编程。

请注意,本简介将假设您有一些使用Python编程的经验,并且对创建一个简单游戏的不同部分有一定的了解。在本教程中,偶尔会用到“参考”一词。这表示一个python变量。在Python中,变量是引用,因此您可以将多个变量都指向同一个对象。

历史课

“精灵”一词是老式电脑和游戏机遗留下来的。这些较老的盒子无法以足够快的速度绘制和擦除正常图形,从而无法像游戏一样工作。这些机器有特殊的硬件来处理游戏,比如需要非常快地设置动画的物体。这些对象被称为“精灵”,有特殊的限制,但可以非常快地绘制和更新。它们通常存在于视频中的特殊覆盖缓冲区中。如今,计算机已经普遍变得足够快,可以在没有专用硬件的情况下处理像精灵一样的物体。术语精灵仍然被用来表示2D游戏中任何动画的东西。

这些班级

Sprite模块附带两个主要类。第一个是 Sprite ,它应该用作所有游戏对象的基类。这个类实际上本身并不做任何事情,它只包括几个帮助管理游戏对象的函数。另一类是 Group 。这个 Group 类是不同的 Sprite 物体。实际上,有几种不同类型的小组课程。其中一些 Groups 例如,可以绘制它们包含的所有元素。

这就是问题的全部。我们将从描述每种类型的类做什么开始,然后讨论使用这两个类的正确方法。

雪碧课

如前所述,Sprite类被设计为所有游戏对象的基类。您不能单独使用它,因为它只有几种方法可以帮助它处理不同的 Group 上课。精灵会跟踪它所属的组。类构造函数 (__init__ 方法)接受 Group (或列表 Groups ) Sprite 实例应该属于。您还可以更改 Group 协会的会员资格 Sprite 使用 add()remove() 方法:研究方法。也有一个 groups() 方法,该方法返回包含子画面的当前组的列表。

使用Your Sprite类时,如果它们属于一个或多个,则最好将其视为“有效”或“活动” Groups 。当你从所有组中删除该实例时,pyGame将清理该对象。(除非您有自己对其他地方的实例的引用。)这个 kill() 方法将精灵从其所属的所有组中移除。这将干净利落地删除精灵对象。如果你已经把一些小游戏放在一起,你就会知道有时干净利落地删除一个游戏对象可能是很棘手的。这款精灵还配有一个 alive() 方法,如果它仍然是任何组的成员,则该方法返回TRUE。

小组课

这个 Group 类只是一个简单的容器。与精灵类似,它有一个 add()remove() 可以更改哪些精灵属于该组的方法。您还可以将子画面或子画面列表传递给构造函数 (__init__() 方法)来创建一个 Group 实例,该实例包含一些初始子画面。

这个 Group 有一些其他的方法,比如 empty() 从组中删除所有精灵并 copy() 它将返回具有所有相同成员的组的副本。也就是 has() 方法将快速检查 Group 包含精灵或精灵列表。

您将经常使用的另一个函数是 sprites() 方法。这将返回一个对象,可以循环该对象以访问该组包含的每个精灵。目前,这只是一个精灵列表,但在以后的版本中,为了获得更好的性能,这可能会使用迭代器。

作为一种快捷方式, Group 也有一个 update() 方法,该方法将调用 update() 方法对组中的每一个精灵。将相同的参数传递给每一个。通常在游戏中,你需要一些函数来更新游戏对象的状态。方法调用您自己的方法非常容易。 Group.sprites() 方法,但这是一个快捷方式,用得够多了,应该包括在内。还要注意的是,基地 Sprite 类有一个“哑巴” update() 方法,该方法接受任何类型的参数,并且不执行任何操作。

最后,Group还有其他几种方法,使您可以将其用于构建 len() 函数,获取它包含的精灵的数量,以及“true”运算符,它允许您执行“if mygroup:”来检查组中是否有精灵。

把它们混合在一起

在这一点上,这两个类似乎非常基础。除了使用简单的列表和您自己的游戏对象类之外,不会做更多的事情。但是,使用 SpriteGroup 在一起。精灵可以属于任意多个组。请记住,一旦它不属于任何组,它通常会被清除(除非您有对该对象的其他“非组”引用)。

第一件大事是一种快速简单的精灵分类方法。例如,假设我们有一场类似吃豆人的游戏。我们可以为游戏中不同类型的物体设置不同的组。幽灵、Pac和子弹。当PAC吃了灵能小球时,我们可以通过影响幽灵组中的一切来改变所有幽灵物体的状态。这比循环遍历所有游戏对象的列表并检查哪些是幽灵更快、更简单。

在彼此之间添加和删除组和精灵是一个非常快的操作,比使用列表存储一切都要快。因此,您可以非常高效地更改组成员身份。组可用于像处理每个游戏对象的简单属性一样工作。你可以把它们添加到一个单独的组中,而不是跟踪一堆敌人物体的某个属性,比如“接近玩家”。然后,当你需要访问玩家附近的所有敌人时,你已经有了他们的列表,而不是检查所有敌人的列表,检查“Close_to_Player”标志。稍后,您的游戏可以添加多个玩家,而不是添加更多的“Close_to_player2”、“Close_to_Player3”属性,您可以轻松地将他们添加到不同的组或每个玩家。

另一个重要的好处是使用 SpritesGroups 是这些团体干净利落地处理删除(或杀死)游戏对象的问题。在许多对象都在引用其他对象的游戏中,有时删除一个对象可能是最困难的部分,因为它不会消失,除非它没有被任何人引用。假设我们有一个物体在“追赶”另一个物体。追逐者可以保留一个引用它正在追赶的对象的简单组。如果被追逐的物体恰好被摧毁,我们不需要担心通知追逐者停止追逐。追逐者可以自己看到它的团队现在是空的,也许会找到一个新的目标。

同样,要记住的是,在组中添加和删除精灵是一个非常廉价/快速的操作。添加多个组来包含和组织您的游戏对象可能是最佳选择。有些甚至在比赛的大部分时间都是空的,这样管理你的比赛不会有任何惩罚。

多种群组类型

上面的例子和使用理由 SpritesGroups 只是冰山一角。另一个优点是Sprite模块附带了几种不同类型的 Groups 。这些小组都像一个普通的老人一样工作 Group ,但它们也增加了功能(或略有不同的功能)。这是一份名单, Group 随Sprite模块一起提供的类。

Group

这是上面主要解释的标准的“朴实无华”的一组。其他大多数人 Groups 都是从这个派生出来的,但不是全部。

GroupSingle

这个的工作原理和普通的完全一样 Group 类,但它只包含最近添加的Sprite。因此,当您将精灵添加到此组中时,它会“忘记”它以前拥有的任何精灵。因此,它始终只包含一个精灵或零个精灵。

RenderPlain

这是派生自 Group 。它有一个Draw()方法,可以将它包含的所有精灵绘制到屏幕上(或任何 Surface )。为此,它要求它包含的所有精灵都具有“image”和“rect”属性。它使用这些信息来知道要对什么进行blit,以及在哪里进行blit。

RenderClear

这是从 RenderPlain 组,并添加一个名为 clear() 。这将擦除所有绘制的精灵的先前位置。它使用背景图像来填充精灵所在的区域。它足够智能,可以处理已删除的精灵,并在 clear() 方法被调用。

RenderUpdates

这就是渲染的凯迪拉克 Groups 。它继承自 RenderClear ,但会更改 draw() 方法还返回一个pyGame的列表。 Rects ,表示屏幕上已更改的所有区域。

这是可用的不同组的列表我们将在下一节中更多地讨论这些渲染组。也没有什么能阻止您创建自己的Group类。它们只是Python代码,所以您可以继承其中之一,并添加/更改您想要的任何内容。我希望以后我们能再增加几个 Groups 加到这张单子上。一个 GroupMulti 这就像是 GroupSingle ,但最多可以容纳给定数量的精灵(在某种循环缓冲区中?)。也是一个超级渲染组,可以清除旧精灵的位置,而不需要背景图像(通过在blit之前抓取屏幕的副本)。谁知道呢,但在未来,我们可以在这个列表中添加更多有用的类。

渲染组

从上面我们可以看到有三个不同的渲染组。我们也许可以就这样逃脱惩罚 RenderUpdates 一个,但它增加了滚动游戏之类的东西并不真正需要的开销。所以我们这里有几个工具,为合适的工作挑选合适的工具。

对于滚动类型的游戏,其中背景每一帧都完全改变,我们显然不需要担心在调用 display.update() 。你绝对应该选择 RenderPlain 在此处分组以管理您的渲染。

对于背景更固定的游戏,你肯定不想让PYGAME更新整个屏幕(因为它不需要更新)。这种类型的游戏通常需要擦除每个对象的旧位置,然后为每一帧将其绘制到一个新位置。这样,我们只是在改变必要的东西。大多数情况下,您只需要使用 RenderUpdates 在这里上课。因为您还需要将此更改列表传递给 display.update() 功能。

这个 RenderUpdates 类在最小化已更新矩形列表中的重叠区域方面也做得很好。如果对象的前一个位置和当前位置重叠,它会将它们合并成一个矩形。再加上它能正确处理已删除的对象,这是一个强大的功能 Group 班级。如果您编写了一个管理游戏中对象的更改矩形的游戏,您就知道这是游戏中大量混乱代码的原因。尤其是当你开始添加可以随时删除的对象时。所有这些工作都简化为 clear()draw() 方法使用这个怪物类。此外,有了重叠检查,它可能会比手动检查更快。

还请注意,没有什么可以阻止您在游戏中混合和匹配这些渲染组。当你想要对精灵进行分层时,你绝对应该使用多个渲染组。另外,如果屏幕被分成多个部分,也许屏幕的每个部分都应该使用适当的渲染组?

碰撞检测

Sprite模块还带有两个非常通用的碰撞检测功能。对于更复杂的游戏,这些确实不适合您,但您可以很容易地获取它们的源代码,并根据需要进行修改。

以下是对它们是什么以及它们的作用的总结。

spritecollide(sprite, group, dokill) -> list

这将检查单个精灵和组中的精灵之间的碰撞。对于所有使用的精灵,它需要一个“rect”属性。它返回与第一个精灵重叠的所有精灵的列表。“dokill”参数是一个布尔参数。如果为真,则该函数将调用 kill() 方法在所有的精灵上。这意味着对每个精灵的最后一个引用可能在返回的列表中。一旦名单消失,精灵也会消失。以下是在循环中使用此命令的快速示例:

>>> for bomb in sprite.spritecollide(player, bombs, 1):
...     boom_sound.play()
...     Explosion(bomb, 0)

这会找到“炸弹”组中与玩家发生碰撞的所有精灵。由于“dokill”的论据,它删除了所有坠毁的炸弹。对于每一枚确实相撞的炸弹,它都会播放一种“砰”的音效,并创建一个新的 Explosion 炸弹在哪里。(注: Explosion 这里的类知道将每个实例添加到适当的类中,所以我们不需要将其存储在变量中,最后一行可能会让您的Python程序员感到有点“有趣”。)

groupcollide(group1, group2, dokill1, dokill2) -> dictionary

这类似于 spritecollide 函数,但稍微复杂一些。它检查一个组中的所有精灵与另一个组中的精灵的冲突。有一个 dokill 每个列表中的精灵的参数。什么时候 dokill1 是真的,碰撞中的精灵 group1 将会是 kill() 艾德什么时候 dokill2 是真的,我们得到了相同的结果 group2 。它返回的字典是这样工作的;字典中的每个键都是来自 group1 发生了碰撞。该关键点的值是它与之碰撞的精灵的列表。也许另一个快速代码示例最好地解释了这一点:

>>> for alien in sprite.groupcollide(aliens, shots, 1, 1).keys()
...     boom_sound.play()
...     Explosion(alien, 0)
...     kills += 1

这段代码检查玩家子弹和他们可能相交的所有外星人之间的碰撞。在本例中,我们只循环访问字典键,但我们可以循环访问 values()items() 如果我们想对与外星人相撞的特定镜头做些什么。如果我们确实在 values() 我们将遍历包含精灵的列表。同一个精灵甚至可能在这些不同的循环中出现不止一次,因为相同的“射击”可能会与多个“外星人”相撞。

这些都是PYGAME附带的基本碰撞功能。如果您自己使用的属性与“rect”属性不同,那么您应该可以很容易地使用它们。或者尝试通过直接影响碰撞对象来对代码进行更多微调,而不是构建碰撞列表?Sprite碰撞函数中的代码非常优化,但您可以通过删除一些不需要的功能来略微加快它的速度。

常见问题

目前,吸引新用户的主要问题有一个。当您使用Sprite基础派生新的Sprite类时, must 调用 Sprite.__init__() 方法来自您自己的类。 __init__() 方法。如果您忘记调用 Sprite.__init__() 方法时,您会得到一个隐晦的错误,如下所示::

AttributeError: 'mysprite' instance has no attribute '_Sprite__g'

扩展您自己的类 (高级)

由于速度方面的考虑,目前的 Group 类尝试只做它们需要做的事情,而不处理许多一般情况。如果您决定需要额外的功能,则可能需要创建自己的功能 Group 班级。

这个 SpriteGroup 类是为扩展而设计的,因此您可以随意创建自己的类 Group 班级来做一些特殊的事情。最好的起点可能是Sprite模块的实际Python源代码。看着海流 Sprite 如何创建自己的群组应该是足够的榜样。

例如,下面是渲染的源代码 Group 这就叫 render() 方法,而不是仅仅从其中删除一个“图像”变量。因为我们希望它也处理更新的区域,所以我们将从原始的 RenderUpdates 组,以下是代码::

class RenderUpdatesDraw(RenderClear):
    """call sprite.draw(screen) to render sprites"""
    def draw(self, surface):
        dirty = self.lostsprites
        self.lostsprites = []
        for s, r in self.spritedict.items():
            newrect = s.draw(screen) #Here's the big change
            if r is 0:
                dirty.append(newrect)
            else:
                dirty.append(newrect.union(r))
            self.spritedict[s] = newrect
        return dirty

以下是有关如何创建您自己的 SpriteGroup 对象从头开始。

这个 Sprite 对象只“需要”两个方法。“ADD_INTERNAL()”和“REMOVE_INTERNAL()”。这些调用由 Group 类在从自己身上移除一个精灵时。这个 add_internal()remove_internal() 有一个单独的论点,那就是一个小组。你的 Sprite 还需要一些方法来跟踪 Groups 它属于。您可能希望尝试将其他方法和参数与实际匹配 Sprite 类,但是如果您不打算使用这些方法,那么您肯定不需要它们。

这几乎与创建您自己的要求相同 Group 。事实上,如果你看一下源代码,你会发现 GroupSingle 不是派生自 Group 类,它只是实现了相同的方法,因此您无法真正区分它们。同样,当精灵想要属于组或将自己从组中移除时,您需要一个“Add_Internal()”和“Remove_Internal()”方法来调用它们。这个 add_internal()remove_internal() 只有一个论点,那就是精灵。唯一的另一个要求是 Group 类有一个名为“_spritegroup”的伪属性。值是什么并不重要,只要属性存在即可。Sprite类可以通过查找该属性来确定“group”与任何普通的python容器之间的区别。(这一点很重要,因为多个精灵方法可以接受单个组的参数,也可以接受一系列组的参数。由于它们看起来很相似,这是“看到”不同之处的最灵活的方式。)

您应该通读一下Sprite模块的代码。虽然代码有些“调优”,但它有足够的注释来帮助您理解。如果你想做贡献,甚至在源代码中有一个TODO部分。




Edit on GitHub