pygame—— piman's sprite tutorial

 

Sprites are one of the most useful, but least understood, parts of Pygame. This document will, hopefully, teach you enough about sprites to simplify your code tremendously.

This document assumes that you're familiar with Pygame's Rect and Surface objects. Concepts about Rects and Surface will be given only cursory mention here, so be sure to check the documentation if you don't understand something. I believe strongly in code examples and analysis as a way to learn things, so most of this tutorial will be Python, not English.

(This document applies to Pygame 1.5.x and Pygame 1.6. After that, the sprite library has changed in some compatible and subtle but powerful ways. This tutorial will be updated to reflect those changes when the next version of Pygame is released.)

这段大概讲的是 精灵 在pygame中是很重要的。这篇教程会教会你怎么使用。不过在此之前你得对pygame中的rectSurface有一定的了解哦。

 

What is a Sprite?

什么是精灵

A "sprite", at least in Pygame terms, is an object that contains both an image (a Surface), and a location at which to draw that image (a Rect). The term "sprite" is actually a holdover from older display systems that did such manipulations directly in hardware. Computers today are fast enough to not need this, but it is still convenient to work with this abstraction.

Sprites are especially good in object-oriented languages like Python, where you can have a standard sprite interface, and classes that extend that interface as specific sprites. Which is exactly what Pygame does.

精灵 ,简单的说就是Surface + rect。虽然红白机时代很需要精灵,而现在电脑的速度够快了,不过呢,在python中用精灵工作还是很方便的。

pygame.sprite.Sprite

pygame.sprite.Sprite is the class that Pygame uses to represent sprites. However, you will rarely instantiate this class directly. Instead, you'll extend it with your own user-created class, which in turn acts like whatever kind of object you want.

Sprites have two important instance variables, self.image and self.rect. self.image is a Surface, which is the current image that will be displayed. self.rect is the location at which this image will be displayed when the sprite is drawn to the screen.

Sprites also have one important instance method, self.update. This method can take any arguments, but (for reasons which will be explained when we get to sprite groups) the more sprite subclasses you have that use the same arguments to update, the better.

This is probably a little confusing to just read, so the following is a step by step example of making a sprite.

pygame.sprite.Sprite就是pygame里面的一个类,你不用去实例化它,只需要继承它,然后按需写出自己的类就好了。Sprite的变量也就是self.image self.rect两个最为重要,self.image管显示什么,self.rect管在哪里显示。Sprite的方法最为重要的就是self.update,此方法所需要的参数没有限制,爱来几个是几个,倒是后面要着重介绍的精灵组(sprite groups)的原因,你最好使用相同的参数。刚开始有点看不懂对不对,接下来一步一步来吧。

Making a Simple Sprite

做一个简单的精灵

This sprite is very simple; it creates a 15x15 box and fills it with a color.

下面的精灵很简单,就是搞一个15*15的方盒子,然后填充颜色

Box: boxes.py

import pygame

 

class Box(pygame.sprite.Sprite):

    def __init__(self, color, initial_position):

 

        # All sprite classes should extend pygame.sprite.Sprite. This

        # gives you several important internal methods that you probably

        # don't need or want to write yourself. Even if you do rewrite

        # the internal methods, you should extend Sprite, so things like

        # isinstance(obj, pygame.sprite.Sprite) return true on it.

        # 所有的精灵类都从pygame.sprite.Sprite继承。你从中获益的当然就是

        # pygame内置了不少方法,不用自己再去写。当然,即使想写功能一样的

        # 嘿嘿,你照样要继承精灵这个类。

        pygame.sprite.Sprite.__init__(self)

     

        # Create the image that will be displayed and fill it with the

        # right color.创建可以显示并填充了颜色的图像

        self.image = pygame.Surface([15, 15])

        self.image.fill(color)

 

        # Make our top-left corner the passed-in location.置图像于左上角

        self.rect = self.image.get_rect()

        self.rect.topleft = initial_position

So now we've got a sprite. The following code creates an instance of that sprite, and displays it on the screen.

上面那个得到的就是精灵。接下来就是把它显示出来。

import pygame

from pygame.locals import *

from boxes import Box

 

pygame.init()

screen = pygame.display.set_mode([150, 150])

b = Box([255, 0, 0], [0, 0]) # Make the box red in the top left

screen.blit(b.image, b.rect)

pygame.display.update()

while pygame.event.poll().type != KEYDOWN: pygame.time.delay(10)

At the moment sprites probably aren't looking too great; it's a lot more code than just using surfaces directly. That's true. However, sprites start to shine when you plan to use a lot of them:

虽然看起来不怎么样,但也只是代码少的问题。下面的代码才开始真正闪耀起来。

import pygame

from pygame.locals import *

from boxes import Box

 

pygame.init()

boxes = []

for color, location in [([255, 0, 0], [0, 0]),

                        ([0, 255, 0], [0, 60]),

                        ([0, 0, 255], [0, 120])]:

    boxes.append(Box(color, location))

 

screen = pygame.display.set_mode([150, 150])

for b in boxes: screen.blit(b.image, b.rect)

pygame.display.update()

while pygame.event.poll().type != KEYDOWN: pygame.time.delay(10)

We've gotten a good deal of code reuse out of our Box object now, and our screen update is a one-line loop, despite it updating three things. In fact, it can be made even more efficient with:

现在Box代码可以重复使用,屏幕更新只用了一行的循环却做了三个矩形的显示。下面的两句话更有效率哦:

rectlist = [screen.blit(b.image, b.rect) for b in boxes]

pygame.display.update(rectlist)

The loop itself won't be any slower than the for loop, and we only update the parts of the screen that were changed. Since updating the screen surface can take a long time, this is a big win.

While this can be done with surfaces, the advantages of sprites are beginning to emerge even in this simple example.

这个循环本身并不会比for循环慢,并且我们只更新了三块矩形的地方的图形,这样是不是更有效率?精灵的优点就在这个简单的例子里面啦。

Animation

动画

Now for something that would be a complete pain for surfaces to do — moving the boxes up and down. This will introduce the purpose of the update method.

现在实现上下移动盒子,对Surface可是有点幸苦哦。仔细研究下面的更新功能~~~

UpDownBox: boxes.py

import pygame

 

class UpDownBox(pygame.sprite.Sprite):

    def __init__(self, color, initial_position):

        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface([15, 15])

        self.image.fill(color)

        self.rect = self.image.get_rect()

        self.rect.topleft = initial_position

        self.going_down = True # Start going downwards

        self.next_update_time = 0 # update() hasn't been called yet.还没有调用update

 

    def update(self, current_time, bottom):

        # Update every 10 milliseconds = 1/100th of a second.10毫秒更新一次

        if self.next_update_time < current_time:

 

            # If we're at the top or bottom of the screen, switch directions.

           # 如果到顶或者到底了改变方向

            if self.rect.bottom == bottom - 1: self.going_down = False

            elif self.rect.top == 0: self.going_down = True

    

            # Move our position up or down by one pixel

            # 一个一个像素地移动位置

            if self.going_down: self.rect.top += 1

            else: self.rect.top -= 1

 

            self.next_update_time = current_time + 10

This is the new sprite object. Now, rather than just sitting there, we call the update method every cycle, with the current time and the bottom of the screen (which is its vertical size).

这是一个新的精灵类,每次循环都会调用更新update()一次。

import pygame

from pygame.locals import *

from boxes import UpDownBox

 

pygame.init()

boxes = []

for color, location in [([255, 0, 0], [0, 0]),

                        ([0, 255, 0], [60, 60]),

                        ([0, 0, 255], [120, 120])]:

    boxes.append(UpDownBox(color, location))

 

screen = pygame.display.set_mode([150, 150])

while pygame.event.poll().type != KEYDOWN:

    screen.fill([0, 0, 0]) # blank the screen.

 

    # Save time by only calling this once

    time = pygame.time.get_ticks()

    for b in boxes:

      b.update(time, 150)

      screen.blit(b.image, b.rect)

 

    pygame.display.update()

 

Running this, you'll see the boxes start moving up and down the screen. Trying to do this directly with surfaces and rectangles would be much more confusing --- the most convenient way would be a list of (image, rectangle) tuples and a function to update the data in the rectangle based on the time. But at that point, you've just reimplemented sprites in a less object-oriented, harder to read, and probably slower manner.

这个程序运行起来就是盒子上下上下咯。直接通过面和矩形实现有点容易混淆,更好的是用一个列表(image,rectangle)元组和基于时间和方框位置数据的函数来实现。只是这样程序就不好看了。

Sprite Groups

As shown above, the benefits of sprites appear when you've got a lot of them. In the examples, the sprites are stored in a list, which is convenient, but not really optimal. In addition to sprites, Pygame offers a set of classes known as sprite groups, which are easy to use lists of sprites. Sprite groups have 5 important methods --- add, remove, draw, update, and sprites.

就像上面的例子,要处理的东西一多精灵的好处就显现出来了。上面精灵是存在一个列表中,很方便,就是有点不太好用。除了精灵,pygame还提供了精灵组,它很适合处理精灵列表,有添加,移除,绘制,更新和 精灵 5种方法。

add and remove add or remove a sprite, list of sprites, or all the sprites in another sprite group, to the group. So, you can do add(sprite1), add([sprite1, sprite2]), or add(group_with_1and2).

添加和移除。可以添加或移除精灵,精灵列表,或者任何精灵组里所有的精灵。就像这样:add(sprite1), add([sprite1, sprite2]), or add(group_with_1and2)

update takes any arguments, and passes them to the update method of each sprite in it. This is why it's a good idea to have your sprites take the same arguments to update, because then you can take advantage of the group's update rather than call each individual sprite's method.

更新。这个方法需要提供的参数由你来定,更新的效果就靠这些数据。之所以说精灵更新要一些参数,是因为你可以很好地利用精灵组而不要单独调用精灵的方法。

sprites returns a list of sprites in the sprite group. In Pygame 1.7 and greater, sprite groups are iterators themselves, so you can use for sprite in group directly, rather than for sprite in group.sprites().

精灵。返回的是精灵组里的精灵列表。要用的话直接这样用:for sprite in group

In addition, there's a new function of the Sprite object that's important to mention here — kill (which takes no arguments) will remove a sprite from all the sprite groups it's in.

再者,精灵对象还有一个很重要的方法哦:kill(),不用参数,直接从精灵组中除去精灵。

Some of the more advanced sprite groups have a draw function that takes a single surface as an argument, and draws all the sprites in it to that surface, at the location specified by their rectangles.

Rewriting the moving box example in terms of sprite groups:

绘制。有些精灵组需要用单一的Surface做参数,然后绘制所有精灵到这个Surface上面去,画的地方嘛,就看你的框框设在哪里咯。下面就用精灵组重写下移动盒子:

import pygame

from pygame.locals import *

from boxes import UpDownBox

 

pygame.init()

boxes = pygame.sprite.Group()

for color, location in [([255, 0, 0], [0, 0]),

                        ([0, 255, 0], [60, 60]),

                        ([0, 0, 255], [120, 120])]:

    boxes.add(UpDownBox(color, location))

 

screen = pygame.display.set_mode([150, 150])

while pygame.event.poll().type != KEYDOWN:

    screen.fill([0, 0, 0]) # blank the screen.

    boxes.update(pygame.time.get_ticks(), 150)

    for b in boxes.sprites(): screen.blit(b.image, b.rect)

    pygame.display.update()

Note how:

·   We don't have to manage the update loop ourselves anymore; the sprite group handles it for us.

·   不用再去自己管那更新循环了:精灵组自己搞掂。

·   We don't have to cache the value of get_ticks, since it only gets evaluated once anyway now.

·   也不用存get_ticks这个值了,只会被用到一次

·   The only change we had to make in the initialization was append to add; sprite groups aren't harder to set up than lists.

·   就是初始的时候加个add有点变化,精灵组其实不会太难啦~~~

The sprite group code is also highly optimized, so it is often faster than a list. Insertions and deletions are both constant-time operations, and the code avoids memory dereferences as much as possible in the inner loops. This comes at the expensive of some readability, but you don't need to read sprite.py to use it.

精灵组的代码也是高度优化过了,所有经常比列表还快。插入和删除都是常见的操作,代码还可以避免内存在循环中反复消耗。虽然可读性有点变味,不过这些事你不用看sprite.py就会使用的。

I mentioned before that it's a good idea to have as many sprites as possible take the same arguments to their update method, and now you can see why. If sprites share the same update method, they can share the same sprite group, and use a single update call. While this doesn't make the code anything faster, it makes it a lot shorter.

上面不是说了吗,更新的参数多一点,这就是为什么,如果精灵分享这些相同的更新方法,他们就可以分享相同的精灵组,只用一个更新就可以了事。

RenderUpdates

If you're clever, you're starting to consider the potential of sprite groups right now. There are two key inefficiencies in the above example — first, we clear the whole screen, and second we update the whole screen. But since we have information about rectangles and surfaces, we should be able to only calculate the changed areas, right?

精灵组好像还有潜力的样子。上面的例子有两个比较抵消的地方,第一每次擦除的都是整个画面,第二每次更新的都是整个画面。但自从有了框框+画面,其实我们可以只要计算改变的区域对不对?

Pygame offers many different sprite groups. Group, shown above, is the simplest one. We'll skip all the ones in the middle (you should check them out later, though!) and head straight for RenderUpdates, the most featureful. RenderUpdates does exactly what is described above: It lets you update just the areas on the screen that are changed. It's also capable of clearing the screen (via blitting a background image in place of the sprites) in a similar manner.

Pygame提供了很多不同的精灵组。组,就像上面那个,是最简单的。先跳过中间的那些精灵组,我们直接看Renderupdate。这家伙就是要弥补上述缺点:更新画面上有改变的地方,也可以清屏哦。

import pygame

from pygame.locals import *

from boxes import UpDownBox

 

pygame.init()

boxes = pygame.sprite.RenderUpdates()

for color, location in [([255, 0, 0], [0, 0]),

                ([0, 255, 0], [60, 60]),

                        ([0, 0, 255], [120, 120])]:

    boxes.add(UpDownBox(color, location))

 

screen = pygame.display.set_mode([150, 150])

background = pygame.Surface([150, 150])

background.fill([0, 0, 0])

screen.blit(background, [0, 0])

while pygame.event.poll().type != KEYDOWN:

    boxes.update(pygame.time.get_ticks(), 150)

    rectlist = boxes.draw(screen)

    pygame.display.update(rectlist)

    pygame.time.delay(10)

    boxes.clear(screen, background)

Now, the screen will be cleared automatically every cycle, and only the necessary areas of the screen will be updated. Although in this example we clear to a plain black background, the clear method can take any surface as its second argument, and so can clear to any image you want.

现在每次循环画面都会自动擦除,只会在必要的地方更新。虽然在例子我们清楚的是黑色的平面区域,但清除这个方法可以用任何surface做他的参数,所以也可以清除任何想要的图片

Remember to call clear, otherwise your sprites will leave droppings behind.

记得调用clear,否则你的精灵会留下(dropping额 粪便吗?大概是残渣之类的啦)

Like the rest of the sprite groups, RenderUpdates is highly optimized. It will automatically calculate overlapping rectangles in draw and clear and only update them once.

Renderupdates也是被优化过的,在绘制和清除中,他会自动计算重叠的框框,然后按需更新。

Ordering Sprites

One important thing to remember about sprite groups is that they are not ordered. Sprites will be drawn to the screen in no particular order. If you need ordering, you have two choices. First, if you only need partial ordering (e.g. sprites A and B must always be under C and D, but the order of A and B doesn't matter), you can use multiple sprite groups, and then draw them in order from lowest to highest (e.g. group_ab, then group_cd). Secondly, you can implement your own sprite group that has ordering. An example of how to do this is presented later in this tutorial.

Thinking in Sprites

The hardest part of sprites, like the hardest part of any program, isn't just the API and syntax. It's designing your program around them, in a manner that makes it easy to write, debug, and extend later. Unfortunately it's hard to show exactly how to do this in a tutorial. But some general advice that I've found useful is below.

Use Sprites..

·   You have many instances of an object on the screen at a time.

·   You have some objects that you need to track closely (e.g. collision detection). Since Sprites have a self.rect attribute, then are considered rectangles for functions like colliderect, so you can just pass in your list of sprites directly.

·   You have objects that need to "update themselves" rather than waiting for events to happen, passively. Sprite's update() method, with a time argument, makes it easy to deal with a dynamic environment.

Don't Use Sprites..

·   Your objects don't share much (if any) code, and you rarely have more than one copy of each instantiated at a time.

·   Your loop is event-based, and things never move "by themselves". Simple user interfaces are often easier to do with surfaces than with sprites.

·   You absolutely, positively need speed, and know exactly what you're doing. Sprites and sprite groups are fast, but if you're careful, you can be faster. Don't worry about this until after you've profiled your code (always profile your code) and removed the more obvious time sucks, though.

Sprite Tricks

Singleton Images

Often you'll want to have a lot of sprites of the same class on the screen at the same time, using an image that comes from a file. What you don't want to do is this:

class MySprite(pygame.sprite.Sprite):

    def __init__(self):

        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.image.load("image.png")

If you do this, every copy of your sprite will load the image from the file, and you'll have a copy of the image for each sprite, eating up memory and loading time. Instead, you'll be better off with this:

class MySprite(pygame.sprite.Sprite):

    image = None

 

    def __init__(self):

        pygame.sprite.Sprite.__init__(self)

 

        if MySprite.image is None:

            # This is the first time this class has been instantiated.

            # So, load the image for this and all subsequence instances.

            MySprite.image = pygame.image.load("image.png")

 

        self.image = MySprite.image

This loads the image once the first time the class is instantiated, and every other instance uses the same image as the first.

Even if all your sprites use the same base image, but change it later, you can use this trick to avoid loading the image from disk and decompressing it each time you make a new sprite.

class MySprite(pygame.sprite.Sprite):

    image = None

 

    def __init__(self):

        pygame.sprite.Sprite.__init__(self)

        if MySprite.image is None:

            MySprite.image = pygame.image.load("image.png")

 

        # Converting an image to its own depth and masks gives us the

        # same image. Some preliminary benchmarks show that using

        # convert is actually faster than making a new surface and

        # blitting to it in separate steps. You might want to verify

        # this yourself.

        self.image = MySprite.image.convert(MySprite.image)

Internals

Sprites

As mentioned before, Sprites are meant to be extended. As they stand, they're pretty useless, so you'll need to wrap them in something a bit more useful. However, thanks to good design, you don't need to know much about the internals of Sprites to extend the class. Instead, you just need to override __init__ and update. However, if you do need to do low-level work with sprites, there are a few more details.

The two important functions are add_internal and remove_internal. These are called when the sprite is added or removed from a sprite group, with one argument, being the sprite group. Your sprites will need to track what groups they are in, so when self.kill() is called it can tell the groups to remove them.

You shouldn't find yourself rewriting the sprite class internals. Sprite is simple enough that you can build everything you need off of it, rather than parallel to it.

Groups

Sprite groups are more complicated. For speed reasons (and some lack of foresight), groups have some internal abstraction violations that make them a little harder to extend. But it's possible, and it's not too terrible.

Sprite groups (with the exception of GroupSingle) use a dictionary. self._spritedict, to keep track of all the sprites in them. This means that addition and deletion of sprites are fast (constant-time), but the tradeoff is that there's no way to order the sprites. Like sprites, groups need add_internal and remove_internal functions, that take a single arguing, being a sprite. The key difference between add and remove and add_internal and remove_internal is that the former can take lists or sprite groups, while the latter will only take a single sprite.

Real Examples

All of the above is probably best explained by examples. We're going to construct a sprite and a sprite group as an example, from the ground up.

AnimatedSprite

The Idea

The code above shows examples of a sprite's rect attribute changing, but just as often you might want to change the picture. The following sprite subclass cycles through a list of frames, at a given rate.

The Code

Pretty straightfoward. Dividing 1000 by the fps gives us milliseconds per frame, which we can then use to figure out how long to wait between frame switches. Once we hit the end of the frames, go back to the first one.

import pygame

 

class AnimatedSprite(pygame.sprite.Sprite):

    def __init__(self, images, fps = 10):

        pygame.sprite.Sprite.__init__(self)

        self._images = images

 

        # Track the time we started, and the time between updates.

        # Then we can figure out when we have to switch the image.

        self._start = pygame.time.get_ticks()

        self._delay = 1000 / fps

        self._last_update = 0

        self._frame = 0

 

        # Call update to set our first image.

        self.update(pygame.time.get_ticks())

 

    def update(self, t):

        # Note that this doesn't work if it's been more that self._delay

        # time between calls to update(); we only update the image once

        # then, but it really should be updated twice.

 

        if t - self._last_update > self._delay:

            self._frame += 1

            if self._frame >= len(self._images): self._frame = 0

            self.image = self._images[self._frame]

            self._last_update = t

OrderedRenderUpdates

The Idea

Sprite groups normally aren't ordered (although using multiple groups you can order them a little). This can be really annoying in some cases. The following sprite group will draw sprites in the order they were added to the group (so more recently added sprites are on top).

The code is basically the same as RenderUpdates, but keeps a list as well as a dictionary of sprites. Ostensibly, insertions are constant-time, deletions are linear time, and drawing is linear time. In reality, sometimes insertions will be linear time too, as Python has to extend the array's memory buffer.

The Code

This code is close to the RenderUpdates code in Pygame, but unoptimized for readability. Because it's from RenderClear, there's some stuff that might not make sense at first, like the lostsprites list. If you're curious about that, check out the code for RenderClear. The main point of this example is show how to use the add and remove functions.

from pygame.sprite import RenderClear

 

# This class keeps an ordered list of sprites in addition to the dict,

# so we can draw in the order the sprites were added.

class OrderedRenderUpdates(RenderClear):

  def __init__(self, group = ()):

    self.spritelist = []

    RenderClear.__init__(self, group)

 

  # Some quick benchmarks show that [:] is the fastest way to get a

  # shallow copy of a list.

  def sprites(self):

    return self.spritelist[:]

 

  # This is kind of a wart -- the actual RenderUpdates class doesn't

  # use add_internal in its add method, so just overriding

  # add_internal won't work.

  def add(self, sprite):

    if hasattr(sprite, '_spritegroup'):

      for sprite in sprite.sprites():

        if sprite not in self.spritedict:

          self.add_internal(sprite)

          sprite.add_internal(self)

    else:

      try: len(sprite)

      except (TypeError, AttributeError):

        if sprite not in self.spritedict:

          self.add_internal(sprite)

          sprite.add_internal(self)

      else:

        for sprite in sprite:

          if sprite not in self.spritedict:

            self.add_internal(sprite)

            sprite.add_internal(self)

 

  def add_internal(self, sprite):

    RenderClear.add_internal(self, sprite)

    self.spritelist.append(sprite)

 

  def remove_internal(self, sprite):

    RenderClear.remove_internal(self, sprite)

    self.spritelist.remove(sprite)

 

  def draw(self, surface):

    spritelist = self.spritelist

    spritedict = self.spritedict

    surface_blit = surface.blit

    dirty = self.lostsprites

    self.lostsprites = []

    dirty_append = dirty.append

    for s in spritelist:

      r = spritedict[s]

      newrect = surface_blit(s.image, s.rect)

      if r is 0:

        dirty_append(newrect)

      else:

        if newrect.colliderect(r):

          dirty_append(newrect.union(r))

        else:

          dirty_append(newrect)

      spritedict[s] = newrect

    return dirty

DirtySprite and DirtyUpdates

脏精灵和脏矩形

The Idea

RenderUpdates is great, but not perfect. It doesn't know anything about how each sprite works, so although it can optimize the drawing areas, it doesn't know if a sprite has changed or not. If it knew this, it could avoid drawing the sprites that haven't changed.

Renderupdates还不错,不过还不够完美,它尚且还不懂得每个精灵如果工作,所以即使可以优化绘制的区域,它还是不懂得一个精灵是否变了。如果可以做到,那么就可以避免没有变得精灵也被绘制。

The bad news is, we can't do this with Sprite as it stands. The good news is, the changes needed to make Sprite allow this are small.

可惜了呀,精灵还在的时候做不到这样,不过要让精灵允许做到这样所需要的改变还是很小滴~~~

The Code

I split the code up into two sections, DirtySprite and RenderDirty. The first is a new sprite class, with a new attribute, self.dirty. Whenever you change the image or rectangle, you should set self.dirty to True. The draw method of RenderDirty then only needs to draw the sprites that have changed.

我(啊 这个 不是我,是“我” = =)分割程序成两个部分,脏精灵和renderdirty。首先是新的精灵类,有新的属性self.dirty。不论何时你要改变图或者框,你都应该设置self.dirty成真。绘制RenderDirty的方法你需要绘制改变的精灵。

This particular implemention of RenderDirty is from Pete Shinners, the author of pygame. I undid some of the optimization, to make it more readable.

这个特殊的可执行的RenderDirtypygame的作者搞的。俺解开一些优化代码,使得看起来更有可读性

This code doesn't totally work. If two sprites are overlapping, and one has updated but the other hasn't, this can give in strange results.

这样的代码尚且不能用。若是此时精灵重叠了只有一个更新,这样结果就不对了。

from pygame.sprite import Sprite, RenderUpdates

 

class DirtySprite(Sprite):

  def __init__(self):

    Sprite.__init__(self)

    self.dirty = True

 

class RenderDirty(RenderUpdates):

  def draw(self, surface):

    dirty = self.lostsprites

    self.lostsprites = []

    for s, r in self.spritedict.items():

      if not s.dirty:

        continue

 

      s.dirty = False

      newrect = surface.blit(s.image, s.rect)

      if r is 0:

        dirty.append(newrect)

      else:

        if newrect.colliderect(r):

          dirty.append(newrect.union(r))

        else:

          dirty.append(newrect)

          dirty.append(r)

      spritedict[s] = newrect

    return dirty

TODO

·   A better indepth explanation of what makes a Sprite.

·   Collision detection.

Further Reading

·   If you're having trouble with some of the base concepts of surfaces and how they interact with the screen, the MoveIt tutorial is a good introduction.

·   SpriteIntro is a broader, but less in-depth, look at sprites and sprite groups.

·   The sprite section of the Pygame reference is the definitive documentation for sprites.

Feedback

If you have advice for this tutorial, or didn't understand something, contact me at piman (at) sacredchao.net.

Thanks

·   Martin Kulas for finding a typo.

·   Darryl Mlinar, for pointing out a bug in my animation code.

·   Shandy Brown, for going over my grammar with a fine-toothed comb.

·   All the guys on irc.freenode.net/#pygame, for suggestions and criticism.

License

Copyright ©2004 Joe Wreschnig. This document is released under the GNU GPL version 2, as published by the Free Software Foundation. The code examples provided are released into the public domain.

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值