版本7和8间的区别
于2006-10-19 19:16:48修订的的版本7
大小: 18612
编辑: czk
备注:
于2006-10-19 20:37:27修订的的版本8
大小: 18431
编辑: czk
备注:
删除的内容标记成这样。 加入的内容标记成这样。
行号 69: 行号 69:
That is the list of different groups available We'll discuss more about these rendering groups in the next section. There's nothing stopping you from creating your own Group classes as well. They are just python code, so you can inherit from one of these and add/change whatever you want. In the future I hope we can add a couple more Groups to this list. A GroupMulti which is like the GroupSingle, but can hold up to a given number of sprites (in some sort of circular buffer?). Also a super-render group that can clear the position of the old sprites without needing a background image to do it (by grabbing a copy of the screen before blitting). Who knows really, but in the future we can add more useful classes to this list.
这是各种可用的Group的列表。我们会在下一节详细讨论这些绘图group。pygame并没有阻止你创建自己的group类。它们只是Python代码,因此你可以从任何一个group继承,添加或者删除任何你想要的东西。将来我希望我们可以内置更多的Group类。GroupMulti和GroupSingle类似,但是可以容纳指定数量的sprite(使用某种形式的循环缓存?)。还可以有一个超级绘图group,可以不用背景图片就可以清除老的sprite(通过在blit前先备份老的屏幕实现)。谁知道实际会怎样呢,但是将来我们肯定会内置更多有用的类。
行号 75: 行号 74:
For a scrolling type game, where the background completely changes every frame. We obviously don't need to worry about python's update rectangles in the call to display.update(). You should definitely go with the RenderPlain group here to manage your rendering. 对于卷轴游戏,背景时刻都在变换,我们不需要关心调用display.update时的更新区域。你应该使用RenderPlain group来进行绘图。

TableOfContents

Sprite模块介绍

Pygame 1.3版本开始有了一个新的模块pygame.sprite。这个模块由python写成,包括一些高级的管理游戏对象的类。充分利用这个模块,可以简化游戏对象的管理和绘制。sprite类是高度优化的,你的游戏如果用sprite模块很可能比不用还要快。

sprite模块也是非常通用的。你可以用它进行几乎任何类型的游戏开发。灵活性总是要有代价的,要正确使用它需要对它有一些了解。sprite模块的参考文档可以让你把它用起来,但是对于怎么在你的游戏里面使用pygame.sprite还需要更多的解释。

一些pygame的例子(像"chimp"和"aliens")已经开始使用sprite模块了。你可能要先看一下那些例子,来了解sprite模块是做什么的。chimp模块甚至有他自己每一行代码的解释,这样可以帮助你更好地了解用Python和pygame来编程。

注意,本文假设你已经了解python编程,并且对于创建一个简单游戏的各种部分已经有一点了解。在本文里面,引用这个词很少使用。这代表一个Python的变量。变量在python里面就是引用,因此你可以有好几个变量都指向同一个对象。

1. 历史知识

sprite这个词,是从很老的计算机和游戏机上遗留下来的。这些老的机器不能够像游戏里需要的那样很快的画图形和删除图形。对于像游戏里那些变化很快的对象,这些机器需要一个特别的硬件去处理。这些对象叫做sprites,并且对它们有一些特殊的限制,但是可以把它们很快的画出来以及很快的更新它们。它们通常被存在视频里面一个特殊的层的缓存上。现在计算机已经够快,从而不需要特别的硬件去处理sprite了。而sprite这个词仍然被用来表示2D游戏里面任何会动的东西。

2. 类

sprite模块有两个主要的类。一个是Sprite,用作所有游戏对象的基类。这个类本身实际上什么事情也不作,它只是包含了一些可以帮助对象管理的函数。另一个类是Group,它是各种不同的Sprite对象的容器。实际上有几种不同类型的group类,比如说有些Group类可以把所有它包含的Sprite画出来。

这就是它所有的东西了。我们首先描述这些类每一个都是做什么的,然后再讨论如何正确的使用它们。

3. Sprite类

刚才提过,Sprite类是用作所有游戏对象的基类的。它本身几乎没有什么用处,因为它只包含几个函数来和各种Group类一起工作。Sprite保留了它自己属于哪个Group的信息。类的构造函数__init__可以用一个Group(或者多个Group的列表)作为参数,指明这个Sprite属于哪些Group。你还可以通过Sprite的add和remove方法来修改它属于哪些Group。还有一个groups方法,可以返回它现在属于的Group的列表。

当使用你的Sprite类的时候,如果它属于某一个或者多个Group,则它被称为是"有效的"或者说是"活的"。当你把这个Sprite从所有的组里面删除时,pygame会清除这个对象(除非你其他地方还有引用这个对象)。kill方法把这个sprite从所有组里面删掉。这会完全删除这个Sprite对象(译注:这不完全正确)。如果你一些小游戏组合在一起,你有时候很难完全清除一个游戏对象。Sprite对象还有一个alive方法,如果Sprite对象还属于其他某个Group则返回True。

4. Group类

Group类只是一个简单的容器。和sprite类似,它也有add和remove方法,用来改变它所包含的Sprites。你可以给它的构造函数传一个Sprite或者一组Sprite作为一开始它所包含的sprite对象。

Group还有其他一些函数,比如empty用来删除group里面的所有对象,copy用来创建一个新的group,包含的Sprite成员和原来的一样。还有一个has方法可以快速判断一个sprite或者一组sprite是否在一个group里面。

其他用的很多的函数还有sprites函数。它返回一个对象,遍历它可以访问Group所包含的所有Sprite对象。现在它返回的这个对象是Sprite的列表,以后可能会变成迭代子以提高性能。

作为一种便捷途径,Group还有一个update方法,它会调用它所包含的所有Sprite的update方法,并且Group的update的参数会传给所有的Sprite的update方法。通常游戏里需要一个函数来更新游戏对象的状态。虽然通过Group.sprites方法调用每一个sprite的方法也是很容易的,但是它经常被用到,所以就被加入进来作为一种便捷方式。还要注意,Sprite基类有一个"dummy"(愚蠢的什么都不会作的)的update方法,可以带任何参数,却什么也不作。

最后,Group类有几个其他方法允许你使用python内置的len函数来获得它包含的Sprite的个数,还有一个bool运算符允许你像"if mygroup:"这样来检查group是否包含sprite。

5. 把它们结合起来

到此时,这两个类看来还很简单。没有比一个简单的列表和一个自己的游戏对象类好多少。但是把Sprite和Group用在一起就能有很大的好处了。一个Sprite可以属于你想要的任意多的group。记住一旦一个Sprite不属于任何一个Group,它通常会被自动清除掉(除非你在Group外存在引用引用了那个Sprite对象)

第一个大好处就是可以很简单快速的组织sprites。比如说,我们做一个类似pacman(吃豆)的游戏。我们可以把游戏里面不同类型的对象分开成不同的group:Ghost、Pac、Pellets。当pac吃到一个强力的pellet,我们可以通过改变Ghost Group里面的所有东西来改变所有ghost对象的状态。这样比循环迭代所有游戏对象,并检查每一个对象是不是Ghost要简单和快速得多。

从Group里添加和删除Sprite以及从Sprite里面添加和删除Group都是非常快的操作,比使用list来存储它们还要快。因此,你可以非常高效的改变Group的成员。Group可以作为每一个游戏对象的一个简单属性一样工作。你可以把Sprite添加到各种不同的Group,而不是为对象添加属性(比如为一堆敌人对象添加"close_to_player"属性)。当你要访问靠近玩家的所有敌人对象,你已经有它们的一个列表了,而不是访问所有敌人的列表并检查"close_to_player"标志。如果以后你要添加多个玩家,你不用为敌人对象添加"close_to_player2"、"close_to_player3"属性,而只要简单的把它们加到每个玩家对应的Group里面去。

使用Sprite和Group另一个重要的好处是,Group可以非常清晰的控制游戏对象的清除(或者称作杀死)。在一个游戏里面,很多对象都引用另外很多对象,有时删除对象会成为最困难的事情,因为除非它不再被引用否则它就不会被清除。假设我们有一个对象,它"追逐"另一个对象。追逐者可以保留一个Group,包含它追逐的对象。追逐者可以看到自己的Group是空了,可能要找一个新的目标。

再重申一次,要记住从Group里面删除和添加Sprite是非常快速廉价的操作。你可以添加很多Group来包含和管理你的游戏对象。有一些Group甚至可以是空的,这样没有任何坏处。

6. 众多的Group类型

前面使用Sprite和Group的例子和原因还只是冰山一角。它们的另一个好处是sprite模块里面还有好几个不同的Group类型。这些Group都能像普通的Group一样工作,但它们还增加了其他一些功能(或者略微有点不同)。这里列举了sprite模块里面所有的Group类。

  • Group 这个是前面说明的最普通的Group。其他大部分Group都是从这个类继承的,但不是全部。
  • GroupSingle 这个Group和普通的Group一样工作,但是它只能包含最近添加的一个sprite。因此当你把一个sprite添加到这个组里面,它会自动忘记前一个sprite。因此它只能包含一个或者零个sprite。

  • RenderPlain 这是个从Group继承的标准group。它有一个draw方法可以把它包含的所有sprites画到屏幕(或者其他任何Surface)上。为了达到这个目的,它需要所有的sprite包含image和rect属性,由它们来决定画什么以及画在哪里。

  • RenderClear 这是个继承自RenderPlain的group,并添加了clear方法。它会把原来画的所有sprite清除。它使用一个背景图片来覆盖sprite所在的区域。它可以非常聪明的处理删除的sprite,当clear被调用时能把它们正确的清除。

  • RenderUpdates 这是绘图Group里面的凯迪拉克。它从RenderClear继承,并修改了draw方法,使它返回一个Rect的列表,表示屏幕上被修改过的所有区域。

这是各种可用的Group的列表。我们会在下一节详细讨论这些绘图group。pygame并没有阻止你创建自己的group类。它们只是Python代码,因此你可以从任何一个group继承,添加或者删除任何你想要的东西。将来我希望我们可以内置更多的Group类。GroupMultiGroupSingle类似,但是可以容纳指定数量的sprite(使用某种形式的循环缓存?)。还可以有一个超级绘图group,可以不用背景图片就可以清除老的sprite(通过在blit前先备份老的屏幕实现)。谁知道实际会怎样呢,但是将来我们肯定会内置更多有用的类。

7. 绘图Groups

从前面我们知道,有三种不同类型的绘图group。我们通常可以忽略RenderUpdates,它增加了额外的负担却在卷轴游戏中并没有用处。因此我们有很多工具,选择合适的工具作合适的工作。

对于卷轴游戏,背景时刻都在变换,我们不需要关心调用display.update时的更新区域。你应该使用RenderPlain group来进行绘图。

For games where the background is more stationary, you definitely don't want pygame updating the entire screen (since it doesn't need to). This type of game usually involves erasing the old position of each object, then drawing it in a new place for each frame. This way we are only changing what is necessary. Most of the time you will just want to use the RenderUpdates class here. Since you will also want to pass this list of changes to the display.update() function.

The RenderUpdates class also does a good job an minimizing overlapping areas in the list of updated rectangles. If the previous position and current position of an object overlap, it will merge them into a single rectangle. Combine this with the fact that is properly handles deleted objects and this is one powerful Group class. If you've written a game that manages the changed rectangles for the objects in a game, you know this the cause for a lot of messy code in your game. Especially once you start to throw in objects that can be deleted at anytime. All this work is reduced down to a clear() and draw() method with this monster class. Plus with the overlap checking, it is likely faster than if you did it yourself.

Also note that there's nothing stopping you from mixing and matching these render groups in your game. You should definitely use multiple rendering groups when you want to do layering with your sprites. Also if the screen is split into multiple sections, perhaps each section of the screen should use an appropriate render group?

Collision Detection The sprite module also comes with two very generic collision detection functions. For more complex games, these really won't work for you, but you can easily grab the sourcecode for them, and modify them as needed. Here's a summary of what they are, and what they do.

  • spritecollide(sprite, group, dokill) -> list This checks for collisions between a single sprite and the sprites in a group. It requires a "rect" attribute for all the sprites used. It returns a list of all the sprites that overlap with the first sprite. The "dokill" argument is a boolean argument. If it is true, the function will call the kill() method on all the sprites. This means the last reference to each sprite is probably in the returned list. Once the list goes away so do the sprites. A quick example of using this in a loop,

        >>> for bomb in sprite.spritecollide(player, bombs, 1):
        ...     boom_sound.play()
        ...     Explosion(bomb, 0)
  • This finds all the sprites in the "bomb" group that collide with the player. Because of the "dokill" argument it deletes all the crashed bombs. For each bomb that did collide, it plays a "boom" sound effect, and creates a new Explosion where the bomb was. (Note, the Explosion class here knows to add each instance to the appropriate class, so we don't need to store it in a variable, that last line might feel a little "funny" to you python programmers.

    groupcollide(group1, group2, dokill1, dokill2) -> dictionary This is similar to the spritecollide function, but a little more complex. It checks for collisions for all the sprites in one group, to the sprites in another. There is a dokill argument for the sprites in each list. When dokill1 is true, the colliding sprites in group1 will be kill()ed. When dokill2 is true, we get the same results for group2. The dictionary it returns works like this; each key in the dictionary is a sprite from group1 that had a collision. The value for that key is a list of the sprites that it collided with. Perhaps another quick code sample explains it best

        >>> for alien in sprite.groupcollide(aliens, shots, 1, 1).keys()
        ...     boom_sound.play()
        ...     Explosion(alien, 0)
        ...     kills += 1
  • This code checks for the collisions between player bullets and all the aliens they might intersect. In this case we only loop over the dictionary keys, but we could loop over the values() or items() if we wanted to do something to the specific shots that collided with aliens. If we did loop over the values() we would be looping through lists that contain sprites. The same sprite may even appear more than once in these different loops, since the same "shot" could have collided against multiple "aliens".

Those are the basic collision functions that come with pygame. It should be easy to roll your own that perhaps use something differen than the "rect" attribute. Or maybe try to fine-tweak your code a little more by directly effecting the collision object, instead of building a list of the collision? The code in the sprite collision functions is very optimized, but you could speed it up slightly by taking out some functionality you don't need.

8. 常见问题

Currently there is one main problem that catches new users. When you derive your new sprite class with the Sprite base, you must call the Sprite.init() method from your own class init() method. If you forget to call the Sprite.init() method, you get a cryptic error, like this; AttributeError: 'mysprite' instance has no attribute '_Spriteg'.

9. Extending Your Own Classes (Advanced)

Because of speed concerns, the current Group classes try to only do exactly what they need, and not handle a lot of general situations. If you decide you need extra features, you may want to create your own Group class.

The Sprite and Group classes were designed to be extended, so feel free to create your own Group classes to do specialized things. The best place to start is probably the actual python source code for the sprite module. Looking at the current Sprite groups should be enough example on how to create your own.

For example, here is the source code for a rendering Group that calls a render() method for each sprite, instead of just blitting an "image" variable from it. Since we want it to also handle updated areas, we will start with a copy of the original RenderUpdates group, here is the code:

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

Following is more information on how you could create your own Sprite and Group objects from scratch.

The Sprite objects only "require" two methods. "add_internal()" and "remove_internal()". These are called by the Group classes when they are removing a sprite from themselves. The add_internal() and remove_internal() have a single argument which is a group. Your Sprite will need some way to also keep track of the Groups it belongs to. You will likely want to try to match the other methods and arguments to the real Sprite class, but if you're not going to use those methods, you sure don't need them.

It is almost the same requirements for creating your own Group. In fact, if you look at the source you'll see the GroupSingle isn't derived from the Group class, it just implements the same methods so you can't really tell the difference. Again you need an "add_internal()" and "remove_internal()" method that the sprites call when they want to belong or remove themselves from the group. The add_internal() and remove_internal() have a single argument which is a sprite. The only other requirement for the Group classes is they have a dummy attribute named "_spritegroup". It doesn't matter what the value is, as long as the attribute is present. The Sprite classes can look for this attribute to determine the difference between a "group" and any ordinary python container. (This is important, because several sprite methods can take an argument of a single group, or a sequence of groups. Since they both look similar, this is the most flexible way to "see" the difference.)

You should through the code for the sprite module. While the code is a bit "tuned", it's got enough comments to help you follow along. There's even a todo section in the source if you feel like contributing.

The end

Pygame的Sprite动画 (2008-02-23 15:35:16由localhost编辑)

ch3n2k.com | Copyright (c) 2004-2020 czk.