pygame教程----Sprite精灵模块介绍

Sprite精灵模块介绍

来历
“精灵”这个词是从老式电脑和游戏机中流传下来的。这些较老的盒子无法快速绘制和删除普通图像,无法作为游戏使用。这些机器有特殊的硬件来处理需要快速动画的游戏类物体。这些对象被称为“精灵”,有特殊的限制,但可以快速绘制和更新。它们通常存在于视频中的特殊覆盖缓冲区中。现在的电脑速度已经足够快,无需专用硬件就可以处理像精灵一样的物体。精灵这一术语仍然用于描述2D游戏中的任何动画内容。

正式介绍
sprite模块带有两个主要类。第一个是Sprite,它应该被用作所有游戏对象的基类。这个类本身并不真正做任何事情,它只是包含了几个帮助管理游戏对象的函数。类的另一种类型是Group。Group类是不同Sprite对象的容器。实际上有几种不同类型的组类。例如,有些组可以绘制它们所包含的所有元素。
这就是全部的内容。我们将从描述每种类型的类的功能开始,然后讨论使用这两个类的正确方法。

Sprite类
就像之前提到的,Sprite类被设计成所有游戏对象的基类。您不能单独使用它,因为它只有几个方法来帮助它处理不同的Group类。精灵跟踪它属于哪个组。类构造函数(__init__方法)接受Sprite实例应该属于的Group(或Group列表)的参数。你也可以用add()和remove()方法改变Sprite的组成员。还有一个groups()方法,它返回包含精灵的当前组列表。
当使用你的Sprite类时,当它们属于一个或多个组时,最好认为它们是“有效的”或“活的”。当你从所有组中删除实例时,pygame将会清除该对象。(除非您在其他地方有自己对该实例的引用。)kill()方法将精灵从它所属的所有组中移除。这将清晰地删除精灵对象。如果你已经将一些小游戏组合在一起,你就会知道有时候干净利落地删除一个游戏对象是很棘手的。该精灵还带有一个alive()方法,如果它仍然是任何组的成员,则返回true。

Group类
Group类只是一个简单的容器。与精灵类似,它有一个add()和remove()方法,可以改变精灵属于哪个组。你也可以将一个精灵或精灵列表传递给构造函数(init()方法)来创建包含一些初始精灵的Group实例。
Group还有一些其他方法,如empty()可以从组中移除所有精灵,而copy()则会返回一个拥有所有相同成员的组副本。此外,has()方法将快速检查群组是否包含精灵或精灵列表。
你将经常使用的另一个函数是sprites()方法。这将返回一个可以循环访问该组所包含的每个精灵的对象。目前这只是一个精灵列表,但在python的后续版本中,它可能会使用迭代器来获得更好的性能。
作为一种快捷方式,Group也有一个update()方法,它将在组中的每个精灵上调用一个update()方法。将相同的参数传递给每一个。通常在游戏中,你需要一些函数来更新游戏对象的状态。使用Group.sprites()方法可以很容易地调用自己的方法,但这是一个使用足够多的快捷方式。还要注意的是,基本的Sprite类有一个“虚拟的”update()方法,它接受任何类型的参数,但什么也不做。
最后,Group还有一些其他方法允许你通过内置的len()函数来使用它,获得它所包含的精灵数量,以及“truth”操作符,它允许你执行“if mygroup:”来检查群组是否有精灵。

使他们相互结合
在这一点上,这两个类看起来相当基础。比起使用一个简单的列表和你自己的游戏对象类,你并没有做更多的事情。但是同时使用Sprite和Group有一些很大的优势。一个精灵可以属于任意多个组。记住,一旦它不属于任何组,它通常就会被清除(除非你有其他“非组”对该对象的引用)。
第一件重要的事情是快速简单地分类精灵。例如,假设我们有一款类似《吃豆人》的游戏。我们可以为游戏中不同类型的对象创建不同的组。幽灵,吃豆人,和子弹。当Pac吃下能量球时,我们可以通过影响ghost组中的所有东西来改变所有ghost对象的状态。这比遍历所有游戏对象列表并检查哪些是幽灵要更快更简单。
添加和删除组和精灵是一个非常快速的操作,比使用列表存储所有内容要快。因此,您可以非常有效地更改组成员关系。组可以像每个游戏对象的简单属性一样工作。你可以将它们添加到一个单独的组中,而不是跟踪一些属性,如“close_to_player”。然后,当你需要访问玩家附近的所有敌人时,你已经有了一个敌人列表,而不是遍查所有敌人列表,检查“close_to_player”标志。之后你的游戏可以添加多个玩家,而不是添加更多的“close_to_player2”,“close_to_player3”属性,你可以轻松地将他们添加到不同的组或每个玩家。
使用精灵和组的另一个重要好处是,组可以干净地处理游戏对象的删除(或消灭)。在游戏中,许多对象都在引用其他对象,有时候删除一个对象可能是最困难的部分,因为它不能消失,除非它没有被任何人引用。假设我们有一个物体在“追逐”另一个物体。追逐者可以保持一个简单的Group来引用它所追逐的对象(或多个对象)。如果被追逐的对象碰巧被销毁,我们不需要担心通知追逐者停止追逐。追逐者可以看到自己的团队现在是空的,也许可以找到一个新的目标。
同样,需要记住的是,从组中添加和删除精灵是一种非常廉价/快速的操作。你最好添加许多组来包含和组织你的游戏对象。有些甚至在游戏的大部分内容中是空的,这样管理你的游戏并不会受到任何惩罚。

多组类型
上面的例子和使用Sprites和Groups的原因只是冰山一角。sprite模块的另一个优点是包含多种不同类型的群组。这些组都像常规的老组一样工作,但是它们也添加了功能(或略有不同的功能)。以下是sprite模块中包含的Group类列表。

Group:这就是标准的“无多余”群体。大多数其他组都是从这个组派生出来的,但不是全部。
GroupSingle:它的工作方式与常规的Group类完全相同,但它只包含最近添加的精灵。因此,当你向该组添加精灵时,它会“忘记”之前拥有的精灵。因此它总是只包含1个或0个精灵。
RenderPlain:这是一个从group派生出来的标准组。它拥有一个draw()方法,能够将它所包含的所有精灵绘制到屏幕上(或任何Surface)。为此,它要求它所包含的所有精灵都具有“image”和“rect”属性。它利用这些信息来知道在什么地方销毁什么。
RenderClear:这来自RenderPlain组,并添加了一个名为clear()的方法。这将抹去所有绘制精灵之前的位置。它使用背景图像填充精灵所在的区域。当clear()方法被调用时,它能够处理被删除的精灵并正确地将它们从屏幕上清除。
RenderUpdates:这是渲染组的凯迪拉克。它继承自RenderClear,但是改变了draw()方法来返回一个pygame Rects列表,它代表屏幕上所有被改变的区域。

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

渲染组
从上面我们可以看到有三个不同的渲染组。我们可能只需要使用RenderUpdates就可以了,但它增加了像滚动游戏这样的东西并不需要的开销。我们这里有一些工具,选择合适的工具来做合适的工作。
对于滚动类型的游戏,背景会完全改变每一帧,我们显然不需要担心调用display.update()中的python更新矩形。你应该使用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类知道将每个实例添加到适当的类中,所以我们不需要将它存储在一个变量中,最后一行对python程序员来说可能有点“滑稽”。

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

这类似于spritecollide函数,但更复杂一些。它检查一个组中的所有精灵与另一个组中的精灵之间的碰撞。每个列表中的精灵都有一个dokill参数。当dokill1为真时,group1中的碰撞精灵将被kill() ’ ’ ed。当’ ’ 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类时,你必须从你自己的类__init__()方法中调用sprite .init()方法。如果你忘记调用Sprite.init()方法,你会得到一个神秘的错误,像这样

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

扩展自己的类(高级)
由于速度方面的考虑,当前的Group类试图只做它们真正需要的事情,而不是处理很多一般情况。如果您决定需要额外的特性,您可能需要创建自己的Group类。
Sprite和Group类被设计为可扩展的,所以你可以随意创建自己的Group类来做专门的事情。最好的出发点可能是sprite模块的实际python源代码。看看当前的Sprite组,你就可以知道如何创建自己的Sprite组了。
例如,下面是一个渲染组的源代码,该渲染组为每个精灵调用render()方法,而不是只从它中分割一个“image”变量。因为我们想让它也处理更新的区域,我们将从原始的RenderUpdates组的副本开始,下面是代码:

class RenderUpdatesDraw(RenderClear):
    """调用sprite.draw(屏幕)来渲染精灵"""
    def draw(self, surface):
        dirty = self.lostsprites
        self.lostsprites = []
        for s, r in self.spritedict.items():
            newrect = s.draw(screen) #这是一个很大的变化
            if r is 0:
                dirty.append(newrect)
            else:
                dirty.append(newrect.union(r))
            self.spritedict[s] = newrect
        return dirty

以下是关于如何从头创建自己的Sprite和Group对象的更多信息。
Sprite对象只“需要”两个方法。“add_internal()”和“remove_internal()”。当Group类从自己中移除精灵时,它们会调用这些。add_internal()和remove_internal()只有一个参数,它是一个组。你的精灵需要一些方法来跟踪它所属的组。你可能想要尝试将其他方法和参数匹配到真正的Sprite类,但如果你不打算使用这些方法,你肯定不需要它们。
创建自己的Group的需求几乎是相同的。事实上,如果你查看源代码,你会看到GroupSingle不是从Group类派生的,它只是实现了相同的方法,所以你不能真正分辨出区别。同样,你需要一个“add_internal()”和“remove_internal()”方法,当精灵想要属于或从组中移除自己时,它们会调用这个方法。add_internal()和remove_internal()只有一个参数,即精灵。Group类唯一的另一个要求是它们有一个名为“_spritegroup”的虚拟属性。值是什么并不重要,只要属性存在就行。Sprite类可以查找这个属性来确定“group”和任何普通python容器之间的区别。(这很重要,因为几个精灵方法可以接受一个组的参数,或组的序列。因为它们看起来很相似,这是“看到”区别的最灵活的方式。)
你应该通过sprite模块的代码。虽然代码有一点“调优”,但它有足够的注释来帮助您理解。如果你想贡献的话,源代码中甚至还有一个TODO部分。

  • 18
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值