Sprite 模块介绍(未完成)
kode4u
|
1#
kode4u 发表于 2007-08-26 10:21
Sprite 模块介绍(未完成)Pygame 教程 Sprite 模块介绍 作者:Pete Shinners pete@shinners.org 修订版 1.1, 2002年4月12日 Pygame 1.3版包含了一个新模块,pygame.sprite。这个由python写成的模块包含了一些高级类用于管理游戏对象。通过有效地使用这个模块,可以轻松地管理与绘制游戏对象。Sprite类经过了特别的优化,因此它会使你的游戏运行得更快。 Sprite模块也具有很好的通用性,这意味着你几乎可以在任何游戏角色上使用它。但所有灵活性都会带来一些轻微的损失,因此你还要学习如何正确地使用它。Sprite 模块 [url=../ref/pygame_sprite.html]参考文档[/url] 已经提供了足够多的信息,但也许还需要再向你说明一下如何在游戏中使用 pygame.sprite。 一些pygame示例(如:chimp、aliens)使用 sprite 模块进行了重写。你可以先研究一下它们,以了解 sprite 模块究竟是个什么。chimp 模块还有一个对应的详细行解教程,也许可以帮助你更加深入了理解如何使用 python 和 pygame 进行编程。 注意,这篇介绍文章假设你已经有了一定的 python 编程经验,并且熟悉创建一个小游戏所需的内容。在这篇教程中,不时会使用“引用”一词,它是指的是一个 python 变量。变量在 python 中就是引用,因此你可能同时有好几个变量指向同一个对象。 历史课 术语“sprite”是由旧电脑和游戏机时代留下来的。当初那些破盒子还不能为运行游戏而快速地绘制和擦除图像,只能使用特殊的硬件来处理像快速移动游戏对象这类的游戏需要。这些有着特殊限制的对象就被称为 sprite,它们可以被非常快速地绘制和更新。这类对象通常存在于特殊的视频覆盖缓冲区(overlay buffers)中。现在计算机已经不再需要专门的硬件就能够很快地处理 sprite 这类对象了,但术语“sprite”仍旧被用于描述2D游戏中的动画物体。 类 Sprite 模块包含两个主要类。一个是 Sprite,它被用作所有游戏对象的基类。这个类本身不做任何事,它只是包含了几个函数用以帮助管理游戏对象;另一个类是 Group,它是不同 Sprite 对象的容器。 Sprite 模块中存在几种不同类型的 group 类,例如一些 Group 类可以绘制它所包含的所有元素。 所有的内容都在这儿了,我们逐个解释这些类都是做什么的,并讨论正确使用它们的方法。 Sprite 类 如前所述,Sprite 类被设计为所有游戏对象的基类。但它只有几个与 Group 类协同工作的方法,因此不能被单独使用。Sprite 会跟踪它所从属的 Group。Sprite 的类构造器(__init__ 方法)使用它的实例所从属的 Group 对象(或 Group 对象队列)作为参数。你也可以使用 Group 对象的 add() 和 remove() 方法来改变 Group 与 Sprite 的从属关系。Group 对象还有一个 groups() 方法,它返回当前 Group 对象所包含的 Sprite 对象列表。 When using the your Sprite classes it's best to think of them as "valid" or "alive" when they are belonging to one or more Groups. When you remove the instance from all groups pygame will clean up the object. (Unless you have your own references to the instance somewhere else.) The kill() method removes the sprite from all groups it belongs to. This will cleanly delete the sprite object. If you've put some little games together, you'll know sometimescleanly deleting a game object can be tricky. The sprite also comes withan alive() method, which returns true if it is still a member of any groups. The Group Class The Group class is just a simple container. Similar to the sprite, it has an add() and remove() method which can change which sprites belong to the group. You also can pass a sprite or list of sprites to the contructor (__init__ method) to create a Group instance that contains some initial sprites. The Group has a few other methods like empty() to remove all sprites from the group and copy() which will return a copy of the group with all the same members. Also the has() method will quickly check if the Group contains a sprite or list of sprites. The other function you will use frequently is the sprites() method. This returns an object that can be looped on to access every sprite the group contains.Currently this is just a list of the sprites, but in later version of pythonthis will likely use iterators for better performance. As a shortcut, the Group also has an update() method, which will call an update() method on every sprite in the group. Passing the same arguments toeach one. Usually in a game you need some function that updates the state of a game object. It's very easy to call your own methods using the Group.sprites() method, but this is a shortcut that's used enough to be included. Also note that the base Sprite class has a "dummy" update() method that takes any sort of arguments and does nothing. Lastly, the Group has a couple other methods that allow you to use it with the builtlin len() method, getting the number of sprites it contains. and the "truth" operator, which allows you to do "if mygroup:" to check if the group has any sprites. Mixing Them Together At this point the two classes seem pretty basic. Not doing a lot more than you can do with a simple list and your own class of game objects. But there are some big advantages to using the Sprite and Group together. A sprite canbelong to as many groups as you want. Remember as soon as it belongs to nogroups, it will usually be cleared up (unless you have other "non-group" referencesto that object) The first big thing is a fast simple way to categorize sprites. For example, say we had a pacman-like game. We could make separate groups for the different types of objects in the game. Ghosts, Pac, and Pellets. When pac eats a power pellet, we can change the state for all ghost objects by effecting everything in the Ghost group. This is quicker and simpler than looping through a list of all the game objects and checking which ones are ghosts. Adding and removing groups and sprites from each other is a very fast operation, quicker than using lists to store everything. Therefore you can very efficiently change group memberships. Groups can be used to work like simple attributes for each game object. Instead of tracking some attribute like "close_to_player" for a bunch of enemy objects, you could add them to a separate group. Then when you need to access all the enemies that are near the player, you already have a list of them, instead of going through a list of all the enemies, checking for the "close_to_player" flag. Later on your game could add multiple players, and instead of adding more "close_to_player2", "close_to_player3" attributes, you can easily add them to different groups for each player. Another important benefit of using the Sprites and Groups, the groups cleanly handle the deleting (or killing) of game objects. In a game where many objects are referencing other objects, sometimes deleting an object can be the hardest part, since it can't go away until it is not referenced by anyone. Say we have an object that is "chasing" another object. The chaser can keep a simple Group that references the object (or objects) it is chasing. If the object being chased happens to be destroyed, we don't need to worry about notifying the chaser to stop chasing. The chaser can see for itself that its group isnow empty, and perhaps find a new target. Again, the thing to remember is that adding and removing sprites from groups is a very cheap/fast operation. You may be best off by adding many groups to contain and organize your game objects. Some could even be empty for large portions of the game, there isn't any penalties for managing your game like this. The Many Group Types The above examples and reasons to use Sprites and Groups are only a tipof the iceberg. Another advantage is that the sprite module comes with several different types of Groups. These groups all work just like a regular old Group, but they also have added functionality (or slightly different functionality).Here's a list of the Group classes included with the sprite module. Group This is the standard "no frills" group mainly explained above. Most of the other Groups are derived from this one, but not all. GroupSingle This works exactly like the regular Group class, but it only contains the most recently added sprite. Therefore when you add a sprite to this group, it "forgets" about any previous sprites it had. Therefore it always contains only one or zero sprites. RenderPlain This is a standard group derived from Group. It has a draw() method that draws all the sprites it contains to the screen (or any Surface). For this to work, it requires all sprites it contains to have a "image" and "rect" attributes. It uses these to know what to blit, and where to blit it. RenderClear This is derived from the RenderPlain group, and adds a method named clear(). This will erase the previous position of all drawn sprites. It uses a background image to fill in the areas where the sprite were. It is smart enough to handle deleted sprites and properly clear them from the screen whenthe clear() method is called. RenderUpdates This is the cadillac of rendering Groups. It is inherited from RenderClear, but changes the draw() method to also return a list of pygame Rects, which represent all the areas onscreen that have been changed. That is the list of different groups available We'll discuss more aboutthese rendering groups in the next section. There's nothing stopping youfrom 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. Inthe future I hope we can add a couple more Groups to this list. A GroupMultiwhich 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 clearthe position of the old sprites without needing a background image to doit (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. The Rendering Groups From above we can see there are three different rendering groups. We could probably just get away with the RenderUpdates one, but it adds overhead not really needed for something like a scrolling game. So we have a couple tools here, pick the right one for the right job. For a scrolling type game, where the background completely changes every frame. We obviously don't need to worry about python's update rectangles inthe call to display.update(). You should definitely go with the RenderPlain group here to manage your rendering. For games where the background is more stationary, you definitely don'twant pygame updating the entire screen (since it doesn't need to). This typeof 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. Sinceyou 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 inobjects that can be deleted at anytime. All this work is reduced down toa 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 theserender groups in your game. You should definitely use multiple rendering groupswhen you want to do layering with your sprites. Also if the screen is split intomultiple sections, perhaps each section of the screen should use an appropriaterender 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 Explosionwhere the bomb was. (Note, the Explosion class here knows to add each instanceto the appropriate class, so we don't need to store it in a variable, thatlast 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. Perhapsanother 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 mayeven 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. Common Problems Currently there is one main problem that catches new users. When youderive your new sprite class with the Sprite base, you must call theSprite.__init__() method from your own class __init__() method. If you forgetto call the Sprite.__init__() method, you get a cryptic error, like this;AttributeError: 'mysprite' instance has no attribute '_Sprite__g'. Extending Your Own Classes (Advanced) Because of speed concerns, the current Group classes try toonly do exactly what they need, and not handle a lot of general situations. If youdecide 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 freeto create your own Group classes to do specialized things. The best place to startis probably the actual python source code for the sprite module. Looking at thecurrent 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 arender() 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 originalRenderUpdates 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 Spriteand 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 fromthemselves. The add_internal() and remove_internal() have a single argumentwhich is a group. Your Sprite will need some way to also keeptrack of the Groups it belongs to. You will likely want to try to match theother methods and arguments to the real Sprite class, but if you're not goingto 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 fromthe Group class, it just implements the same methods so you can't really tellthe difference. Again you need an "add_internal()" and "remove_internal()" methodthat the sprites call when they want to belong or remove themselves fromthe 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 thisattribute to determine the difference between a "group" and any ordinary pythoncontainer. (This is important, because several sprite methods can take anargument of a single group, or a sequence of groups. Since they both looksimilar, this is the most flexible way to "see" the difference.) You should through the code for the sprite module. Whilethe 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. |