Commits

illume  committed 31f7630

Lots of sprite module changes from DR0ID.

  • Participants
  • Parent commits cbe95d2

Comments (0)

Files changed (4)

 #Changes from Marcus for ref counting bug fixes and
 
 Aug 15, 2007
+    The sprite module has had some big changes from DR0ID.  It now has a 
+     LayeredUpdates, and LayeredDirty groups.  For using layers when
+     rendering the sprites.  LayeredDirty is an alternative to 
+     RenderUpdates that automatically finds the best display method
+     (either full screen updates, or dirty rect updates).  It's faster
+     if you have sprites that don't move.  Thanks DR0ID!
     Added pygame.mask.from_surface which can make a Mask object from
      a surface.  It's 128x faster than the python version!
+    pygame.movie bug fix. Thanks Lenard Lindstrom!
 
 Jun 25, 2007
     Removed QNX support from scrap module. Thanks Marcus!

File examples/testsprite.py

 from time import time
 import pygame.joystick
 
+##import FastRenderGroup as FRG
+import pygame.sprite as FRG
+
 if "-psyco" in sys.argv:
     try:
         import psyco
     update_rects = True
 if "-noupdate_rects" in sys.argv:
     update_rects = False
+    
+use_static = False
+if "-static" in sys.argv:
+    use_static = True
+    
+
+use_FastRenderGroup = False
+if "-FastRenderGroup" in sys.argv:
+    update_rects = True
+    use_FastRenderGroup = True
+
 
 flags = 0
 if "-flip" in sys.argv:
 print screen_dims
 
 
-class Thingy(pygame.sprite.Sprite):
+##class Thingy(pygame.sprite.Sprite):
+##    images = None
+##    def __init__(self):
+##        pygame.sprite.Sprite.__init__(self)
+##        self.image = Thingy.images[0]
+##        self.rect = self.image.get_rect()
+##        self.rect.x = randint(0, screen_dims[0])
+##        self.rect.y = randint(0, screen_dims[1])
+##        #self.vel = [randint(-10, 10), randint(-10, 10)]
+##        self.vel = [randint(-1, 1), randint(-1, 1)]
+##
+##    def move(self):
+##        for i in [0, 1]:
+##            nv = self.rect[i] + self.vel[i]
+##            if nv >= screen_dims[i] or nv < 0:
+##                self.vel[i] = -self.vel[i]
+##                nv = self.rect[i] + self.vel[i]
+##            self.rect[i] = nv
+
+class Thingy(FRG.DirtySprite):
     images = None
     def __init__(self):
-        pygame.sprite.Sprite.__init__(self)
+##        pygame.sprite.Sprite.__init__(self)
+        FRG.DirtySprite.__init__(self)
         self.image = Thingy.images[0]
         self.rect = self.image.get_rect()
         self.rect.x = randint(0, screen_dims[0])
         self.rect.y = randint(0, screen_dims[1])
         #self.vel = [randint(-10, 10), randint(-10, 10)]
         self.vel = [randint(-1, 1), randint(-1, 1)]
+        self.dirty = 2
 
-    def move(self):
+    def update(self):
         for i in [0, 1]:
             nv = self.rect[i] + self.vel[i]
             if nv >= screen_dims[i] or nv < 0:
                 nv = self.rect[i] + self.vel[i]
             self.rect[i] = nv
 
+class Static(FRG.DirtySprite):
+    images = None
+    def __init__(self):
+        FRG.DirtySprite.__init__(self)
+        self.image = Static.images[0]
+        self.rect = self.image.get_rect()
+        self.rect.x = randint(0, 3*screen_dims[0]/4)
+        self.rect.y = randint(0, 3*screen_dims[1]/4)
+
 
 
 def main():
     screen.fill([0,0,0])
     pygame.display.flip()
     sprite_surface = pygame.image.load(os.path.join("data", "asprite.bmp"))
+    sprite_surface2 = pygame.image.load(os.path.join("data", "static.png"))
 
     if use_rle:
         sprite_surface.set_colorkey([0xFF, 0xFF, 0xFF], SRCCOLORKEY|RLEACCEL)
+        sprite_surface2.set_colorkey([0xFF, 0xFF, 0xFF], SRCCOLORKEY|RLEACCEL)
     else:
         sprite_surface.set_colorkey([0xFF, 0xFF, 0xFF], SRCCOLORKEY)
+        sprite_surface2.set_colorkey([0xFF, 0xFF, 0xFF], SRCCOLORKEY)
 
     if use_alpha:
         sprite_surface = sprite_surface.convert_alpha()
+        sprite_surface2 = sprite_surface2.convert_alpha()
     else:
         sprite_surface = sprite_surface.convert()
+        sprite_surface2 = sprite_surface2.convert()
 
     Thingy.images = [sprite_surface]
-
+    if use_static:
+        Static.images = [sprite_surface2]
+    
     if len(sys.argv) > 1:
         try:
             numsprites = int(sys.argv[-1])
             numsprites = 100
     else:
         numsprites = 100
-    if update_rects:
-        sprites = pygame.sprite.RenderUpdates()
+    sprites = None
+    if use_FastRenderGroup:
+##        sprites = FRG.FastRenderGroup()
+        sprites = FRG.LayeredDirty()
     else:
-        sprites = pygame.sprite.Group()
+        if update_rects:
+            sprites = pygame.sprite.RenderUpdates()
+        else:
+            sprites = pygame.sprite.Group()
 
     for i in xrange(0, numsprites):
+        if use_static and i%2==0:
+            sprites.add(Static())
         sprites.add(Thingy())
 
     done = False
         if not update_rects:
             screen.fill([0,0,0])
 
-        for sprite in sprites:
-            sprite.move()
+##        for sprite in sprites:
+##            sprite.move()
 
         if update_rects:
             sprites.clear(screen, background)

File lib/sprite.py

 ##    pygame - Python Game Library
-##    Copyright (C) 2000-2003  Pete Shinners
+##    Copyright (C) 2000-2003, 2007  Pete Shinners
 ##              (C) 2004 Joe Wreschnig
 ##    This library is free software; you can redistribute it and/or
 ##    modify it under the terms of the GNU Library General Public
 ## specific ones that aren't quite so general but fit into common
 ## specialized cases.
 
+import pygame
+from pygame import Rect
+from pygame.time import get_ticks
+
+
+
+
 class Sprite(object):
     """The base class for your visible game objects.
        The sprite class is meant to be used as a base class
     def __repr__(self):
         return "<%s sprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))
 
+
+class DirtySprite(Sprite):
+    """
+    DirtySprite has new attributes:
+    
+    dirty: if set to 1, it is repainted and then set to 0 again 
+           if set to 2 then it is always dirty ( repainted each frame, 
+           flag is not reset)
+           0 means that it is not dirty and therefor not repainted again
+    blendmode: its the special_flags argument of blit, blendmodes
+    source_rect: source rect to use, remember that it relative to 
+                 topleft (0,0) of self.image
+    visible: normally 1, if set to 0 it will not be repainted 
+             (you must set it dirty too to be erased from screen)
+    """
+    
+    def __init__(self, *groups):
+        """
+        Same as pygame.sprite.Sprite but initializes the new attributes to
+        default values:
+        dirty = 1 (to be always dirty you have to set it)
+        blendmode = 0
+        layer = 0 (READONLY value, it is read when adding it to the 
+                   LayeredRenderGroup, for details see doc of 
+                   LayeredRenderGroup)
+        """
+        self.dirty = 1
+        self.blendmode = 0  # pygame 1.8, reffered as special_flags in 
+                            # the documentation of blit 
+        self._visible = 1
+        self._layer = 0    # READ ONLY by LayeredUpdates or LayeredDirty
+        self.source_rect = None
+        Sprite.__init__(self, *groups)
+        
+    def _set_visible(self, val):
+        """set the visible value (0 or 1) and makes the sprite dirty"""
+        self._visible = val
+        if self.dirty < 2:
+            self.dirty = 1
+    def _get_visible(self):
+        """returns the visible value of that sprite"""
+        return self._visible
+    visible = property(lambda self: self._get_visible(),\
+                       lambda self, value:self._set_visible(value), \
+                       doc="you can make this sprite disappear without removing it from the group, values 0 for invisible and 1 for visible")
+        
+    def __repr__(self):
+        return "<%s DirtySprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))
+
+
+
 class AbstractGroup(object):
     """A base for containers for sprites. It does everything
        needed to behave as a normal group. You can easily inherit
         RenderUpdates.remove_internal(self, sprite)
         self._spritelist.remove(sprite)
 
+
+class LayeredUpdates(AbstractGroup):
+    """
+    LayeredRenderGroup
+    
+    A group that handles layers. For drawing it uses the same metod as the 
+    pygame.sprite.OrderedUpdates.
+    
+    This group is fully compatible with pygame.sprite.Sprite.
+    """
+    
+    def __init__(self, *sprites, **kwargs):
+        """
+        You can set the default layer through kwargs using 'default_layer'
+        and an integer for the layer. The default layer is 0.
+        
+        If the sprite you add has an attribute layer then that layer will
+        be used.
+        If the kwarg contain 'layer' then the sprites passed will be 
+        added to that layer (overriding the sprite.layer attribute).
+        If neither sprite has attribute layer nor kwarg then the default
+        layer is used to add the sprites.
+        """
+        self._spritelayers = {}
+        self._spritelist = []
+        AbstractGroup.__init__(self)
+        if kwargs.has_key('default_layer'):
+            self._default_layer = kwargs['default_layer']
+        else:
+            self._default_layer = 0
+            
+        self.add(*sprites, **kwargs)
+    
+    def add_internal(self, sprite, layer=None):
+        """
+        Do not use this method directly. It is used by the group to add a
+        sprite internally.
+        """
+        self.spritedict[sprite] = Rect(0, 0, 0, 0) # add a old rect
+        
+        if layer is None:
+            if hasattr(sprite, '_layer'):
+                layer = sprite._layer
+            else:
+                layer = self._default_layer
+                
+                
+        self._spritelayers[sprite] = layer
+        if hasattr(sprite, '_layer'):
+            sprite._layer = layer
+    
+        # add the sprite at the right position
+        # bisect algorithmus
+        sprites = self._spritelist # speedup
+        sprites_layers = self._spritelayers
+        leng = len(sprites)
+        low = 0
+        high = leng-1
+        mid = low
+        while(low<=high):
+            mid = low + (high-low)/2
+            if(sprites_layers[sprites[mid]]<=layer):
+                low = mid+1
+            else:
+                high = mid-1
+        # linear search to find final position
+        while(mid<leng and sprites_layers[sprites[mid]]<=layer):
+            mid += 1
+        sprites.insert(mid, sprite)
+        
+    def add(self, *sprites, **kwargs):
+        """add(sprite, list, or group, ...)
+           add sprite to group
+
+           Add a sprite or sequence of sprites to a group.
+        
+        If the sprite(s) have an attribute layer then that is used 
+        for the layer. If kwargs contains 'layer' then the sprite(s) 
+        will be added to that argument (overriding the sprite layer 
+        attribute). If neither is passed then the sprite(s) will be
+        added to the default layer.
+        """
+        layer = None
+        if kwargs.has_key('layer'):
+            layer = kwargs['layer']
+        if sprites is None or len(sprites)==0:
+            return
+        for sprite in sprites:
+            # It's possible that some sprite is also an iterator.
+            # If this is the case, we should add the sprite itself,
+            # and not the objects it iterates over.
+            if isinstance(sprite, Sprite):
+                if not self.has_internal(sprite):
+                    self.add_internal(sprite, layer)
+                    sprite.add_internal(self)
+            else:
+                try:
+                    # See if sprite is an iterator, like a list or sprite
+                    # group.
+                    for spr in sprite:
+                        self.add(spr, **kwargs)
+                except (TypeError, AttributeError):
+                    # Not iterable, this is probably a sprite that happens
+                    # to not subclass Sprite. Alternately, it could be an
+                    # old-style sprite group.
+                    if hasattr(sprite, '_spritegroup'):
+                        for spr in sprite.sprites():
+                            if not self.has_internal(spr):
+                                self.add_internal(spr, layer)
+                                spr.add_internal(self)
+                    elif not self.has_internal(sprite):
+                        self.add_internal(sprite, layer)
+                        sprite.add_internal(self)
+    
+    def remove_internal(self, sprite):
+        """
+        Do not use this method directly. It is used by the group to 
+        add a sprite.
+        """
+        self._spritelist.remove(sprite)
+        # these dirty rects are suboptimal for one frame
+        self.lostsprites.append(self.spritedict[sprite]) # dirty rect
+        if hasattr(sprite, 'rect'):
+            self.lostsprites.append(sprite.rect) # dirty rect
+        
+        self.spritedict.pop(sprite, 0)
+        self._spritelayers.pop(sprite)
+    
+    def sprites(self):
+        """
+        Returns a ordered list of sprites (first back, last top).
+        """
+        return list(self._spritelist)
+    
+    def draw(self, surface):
+        """
+        Draw all sprites in the right order onto the passed surface.
+        """
+        spritedict = self.spritedict
+        surface_blit = surface.blit
+        dirty = self.lostsprites
+        self.lostsprites = []
+        dirty_append = dirty.append
+        for spr in self.sprites():
+            rec = spritedict[spr]
+            newrect = surface_blit(spr.image, spr.rect)
+            if rec is 0:
+                dirty_append(newrect)
+            else:
+                if newrect.colliderect(rec):
+                    dirty_append(newrect.union(rec))
+                else:
+                    dirty_append(newrect)
+                    dirty_append(rec)
+            spritedict[spr] = newrect
+        return dirty
+
+    def get_sprites_at(self, pos):
+        """
+        Returns a list with all sprites at that position.
+        Bottom sprites first, top last.
+        """
+        _sprites = self._spritelist
+        rect = Rect(pos, (0, 0))
+        colliding_idx = rect.collidelistall(_sprites)
+        colliding = []
+        colliding_append = colliding.append
+        for i in colliding_idx:
+            colliding_append(_sprites[i])
+        return colliding
+
+    def get_sprite(self, idx):
+        """
+        Returns the sprite at the index idx from the sprites().
+        Raises IndexOutOfBounds.
+        """
+        return self._spritelist[idx]
+    
+    def remove_sprites_of_layer(self, layer_nr):
+        """
+        Removes all sprites from a layer and returns them as a list.
+        """
+        sprites = self.get_sprites_from_layer(layer_nr)
+        self.remove(sprites)
+        return sprites
+        
+
+    #---# layer methods
+    def layers(self):
+        """
+        Returns a list of layers defined (unique), sorted from botton up.
+        """
+        layers = set()
+        for layer in self._spritelayers.values():
+            layers.add(layer)
+        return list(layers)
+
+    def change_layer(self, sprite, new_layer):
+        """
+        Changes the layer of the sprite.
+        sprite must have been added to the renderer. It is not checked.
+        """
+        sprites = self._spritelist # speedup
+        sprites_layers = self._spritelayers # speedup
+        
+        sprites.remove(sprite) 
+        sprites_layers.pop(sprite)
+        
+        # add the sprite at the right position
+        # bisect algorithmus
+        leng = len(sprites)
+        low = 0
+        high = leng-1
+        mid = low
+        while(low<=high):
+            mid = low + (high-low)/2
+            if(sprites_layers[sprites[mid]]<=new_layer):
+                low = mid+1
+            else:
+                high = mid-1
+        # linear search to find final position
+        while(mid<leng and sprites_layers[sprites[mid]]<=new_layer):
+            mid += 1
+        sprites.insert(mid, sprite)
+        if hasattr(sprite, 'layer'):
+            sprite.layer = new_layer
+        
+        # add layer info
+        sprites_layers[sprite] = new_layer
+            
+    def get_layer_of_sprite(self, sprite):
+        """
+        Returns the layer that sprite is currently in. If the sprite is not 
+        found then it will return the default layer.
+        """
+        return self._spritelayers.get(sprite, self._default_layer)
+    
+    def get_top_layer(self):
+        """
+        Returns the number of the top layer.
+        """
+        return self._spritelayers[self._spritelist[-1]]
+####        return max(self._spritelayers.values())
+    
+    def get_bottom_layer(self):
+        """
+        Returns the number of the bottom layer.
+        """
+        return self._spritelayers[self._spritelist[0]]
+####        return min(self._spritelayers.values())
+    
+    def move_to_front(self, sprite):
+        """
+        Brings the sprite to front, changing the layer o the sprite
+        to be in the topmost layer (added at the end of that layer).
+        """
+        self.change_layer(sprite, self.get_top_layer())
+        
+    def move_to_back(self, sprite):
+        """
+        Moves the sprite to the bottom layer, moving it behind
+        all other layers and adding one additional layer.
+        """
+        self.change_layer(sprite, self.get_bottom_layer()-1)
+        
+    def get_top_sprite(self):
+        """
+        Returns the topmost sprite.
+        """
+        return self._spritelist[-1]
+    
+    def get_sprites_from_layer(self, layer):
+        """
+        Returns all sprites from a layer, ordered by how they where added.
+        It uses linear search and the sprites are not removed from layer.
+        """
+        sprites = []
+        sprites_append = sprites.append
+        sprite_layers = self._spritelayers
+        for spr in self._spritelist:
+            if sprite_layers[spr] == layer: 
+                sprites_append(spr)
+            elif sprite_layers[spr]>layer:# break after because no other will 
+                                          # follow with same layer
+                break
+        return sprites
+        
+    def switch_layer(self, layer1_nr, layer2_nr):
+        """
+        Switches the sprites from layer1 to layer2.
+        The layers number must exist, it is not checked.
+        """
+        sprites1 = self.remove_sprites_of_layer(layer1_nr)
+        for spr in self.get_sprites_from_layer(layer2_nr):
+            self.change_layer(spr, layer1_nr)
+        self.add(sprites1, layer=layer2_nr)
+
+
+class LayeredDirty(LayeredUpdates):
+    """
+    Yet another group. It uses the dirty flag technique and is therefore 
+    faster than the pygame.sprite.RenderUpdates if you have many static 
+    sprites. It also switches automatically between dirty rect update and 
+    full screen rawing, so you do no have to worry what would be faster. It 
+    only works with the DirtySprite or any sprite that has the following 
+    attributes: image, rect, dirty, visible, blendmode (see doc of 
+    DirtySprite).
+    """
+    
+    def __init__(self, *sprites, **kwargs):
+        """
+        Same as for the pygame.sprite.Group.
+        You can specify some additional attributes through kwargs:
+        _use_update: True/False   default is False
+        _default_layer: the default layer where the sprites without a layer are
+                        added.
+        _time_threshold: treshold time for switching between dirty rect mode and
+                        fullscreen mode, defaults to 1000./80  == 1000./fps
+        """
+        LayeredUpdates.__init__(self, *sprites, **kwargs)
+        self._clip = None
+        
+        self._use_update = False
+        
+        self._time_threshold = 1000./80. # 1000./ fps
+        
+        
+        self._bgd = None
+        for key, val in kwargs.items():
+            if key in ['_use_update', '_time_threshold', '_default_layer']:
+                if hasattr(self, key):
+                    setattr(self, key, val)
+
+    def add_internal(self, sprite, layer=None):
+        """
+        Do not use this method directly. It is used by the group to add a
+        sprite internally.
+        """
+        # check if all attributes needed are set
+        if not hasattr(sprite, 'dirty'):
+            raise AttributeError()
+        if not hasattr(sprite, "visible"):
+            raise AttributeError()
+        if not hasattr(sprite, "blendmode"):
+            raise AttributeError()
+        
+        if not isinstance(sprite, DirtySprite):
+            raise TypeError()
+        
+        if sprite.dirty == 0: # set it dirty if it is not
+            sprite.dirty = 1
+        
+        LayeredUpdates.add_internal(self, sprite, layer)
+        
+    def draw(self, surface, bgd=None):
+        """
+        Draws all sprites on the surface you pass in.
+        You can pass the background too. If a background is already set, 
+        then the bgd argument has no effect.
+        """
+        # speedups
+        _orig_clip = surface.get_clip()
+        _clip = self._clip
+        if _clip is None:
+            _clip = _orig_clip
+        
+        
+        _surf = surface
+        _sprites = self._spritelist
+        _old_rect = self.spritedict
+        _update = self.lostsprites
+        _update_append = _update.append
+        _ret = None
+        _surf_blit = _surf.blit
+        _rect = Rect
+        if bgd is not None:
+            self._bgd = bgd
+        _bgd = self._bgd
+        
+        _surf.set_clip(_clip)
+        # -------
+        # 0. deside if normal render of flip
+        start_time = get_ticks()
+        if self._use_update: # dirty rects mode
+            # 1. find dirty area on screen and put the rects into _update
+            # still not happy with that part
+            for spr in _sprites:
+                if 0 < spr.dirty:
+                    # chose the right rect
+                    if spr.source_rect:
+                        _union_rect = _rect(spr.rect.topleft, spr.source_rect.size)
+                    else:
+                        _union_rect = _rect(spr.rect)
+                        
+                    _union_rect_collidelist = _union_rect.collidelist
+                    _union_rect_union_ip = _union_rect.union_ip
+                    i = _union_rect_collidelist(_update)
+                    while -1 < i:
+                        _union_rect_union_ip(_update[i])
+                        del _update[i]
+                        i = _union_rect_collidelist(_update)
+                    _update_append(_union_rect.clip(_clip))
+                    
+                    _union_rect = _rect(_old_rect[spr])
+                    _union_rect_collidelist = _union_rect.collidelist
+                    _union_rect_union_ip = _union_rect.union_ip
+                    i = _union_rect_collidelist(_update)
+                    while -1 < i:
+                        _union_rect_union_ip(_update[i])
+                        del _update[i]
+                        i = _union_rect_collidelist(_update)
+                    _update_append(_union_rect.clip(_clip))
+            # can it be done better? because that is an O(n**2) algorithm in
+            # worst case
+                    
+            # clear using background
+            if _bgd is not None:
+                for rec in _update:
+                    _surf_blit(_bgd, rec, rec)
+                
+            # 2. draw
+            for spr in _sprites:
+                if 1 > spr.dirty:
+                    if spr._visible:
+                        # sprite not dirty, blit only the intersecting part
+                        _spr_rect = spr.rect
+                        if spr.source_rect is not None:
+                            _spr_rect = Rect(spr.rect.topleft, spr.source_rect.size)
+                        _spr_rect_clip = _spr_rect.clip
+                        for idx in _spr_rect.collidelistall(_update):
+                            # clip
+                            clip = _spr_rect_clip(_update[idx])
+                            _surf_blit(spr.image, clip, \
+                                       (clip[0]-_spr_rect[0], \
+                                            clip[1]-_spr_rect[1], \
+                                            clip[2], \
+                                            clip[3]), spr.blendmode)
+                else: # dirty sprite
+                    if spr._visible:
+                        _old_rect[spr] = _surf_blit(spr.image, spr.rect, \
+                                               spr.source_rect, spr.blendmode)
+                    if spr.dirty == 1:
+                        spr.dirty = 0
+            _ret = list(_update)
+        else: # flip, full screen mode
+            if _bgd is not None:
+                _surf_blit(_bgd, (0, 0))
+            for spr in _sprites:
+                if spr._visible:
+                    _old_rect[spr] = _surf_blit(spr.image, spr.rect, spr.source_rect,spr.blendmode)
+            _ret = [_rect(_clip)] # return only the part of the screen changed
+            
+        
+        # timing for switching modes
+        # how to find a good treshold? it depends on the hardware it runs on
+        end_time = get_ticks()
+        if end_time-start_time > self._time_threshold:
+            self._use_update = False
+        else:
+            self._use_update = True
+            
+##        # debug
+##        print "               check: using dirty rects:", self._use_update
+            
+        # emtpy dirty reas list
+        _update[:] = []
+        
+        # -------
+        # restore original clip
+        _surf.set_clip(_orig_clip)
+        return _ret
+
+    def clear(self, surface, bgd):
+        """
+        Only used to set background.
+        """
+        self._bgd = bgd
+
+    def repaint_rect(self, screen_rect): 
+        """
+        Repaints the given area.
+        screen_rect in screencoordinates.
+        """
+        self.lostsprites.append(screen_rect.clip(self._clip))
+        
+    def set_clip(self, screen_rect=None):
+        """
+        clip the area where to draw. Just pass None (default) to 
+        reset the clip.
+        """
+        if screen_rect is None:
+            self._clip = pygame.display.get_surface().get_rect()
+        else:
+            self._clip = screen_rect
+        self._use_update = False
+        
+    def get_clip(self):
+        """
+        Returns the current clip.
+        """
+        return self._clip
+    
+    def change_layer(self, sprite, new_layer):
+        """
+        Changes the layer of the sprite.
+        sprite must have been added to the renderer. It is not checked.
+        """
+        LayeredRenderGroup.change_layer(self, sprite, new_layer)
+        if sprite.dirty == 0:
+            sprite.dirty = 1
+            
+    def set_timing_treshold(self, time_ms):
+        """
+        Sets the treshold in milliseconds. Default is 1000./80 where 80 is the
+        fps I want to switch to full screen mode.
+        """
+        self._time_threshold = time_ms
+    
+
+
+
+
+
+
 class GroupSingle(AbstractGroup):
     """A group container that holds a single most recent item.
        This class works just like a regular group, but it only

File test/sprite_test.py

 
 
 
+import pygame
+import unittest
+import pygame.sprite as FastRenderGroup
+from pygame.sprite import LayeredUpdates as LayeredRenderGroup
+
+
+class Unit_test_LRG(unittest.TestCase):
+    
+    
+    def setUp(self):
+        self.LRG = LayeredRenderGroup()
+        
+    def test_get_layer_of_sprite(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        spr = pygame.sprite.Sprite()
+        self.LRG.add(spr, layer=666)
+        self.assert_(len(self.LRG._spritelist)==1)
+        self.assert_(self.LRG.get_layer_of_sprite(spr)==666)
+        self.assert_(self.LRG.get_layer_of_sprite(spr)==self.LRG._spritelayers[spr])
+        
+        
+    def test_add_sprite(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        spr = pygame.sprite.Sprite()
+        self.LRG.add(spr)
+        self.assert_(len(self.LRG._spritelist)==1)
+        self.assert_(self.LRG.get_layer_of_sprite(spr)==self.LRG._default_layer)
+        
+    def test_add_sprite_with_layer_attribute(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        spr = pygame.sprite.Sprite()
+        spr._layer = 100
+        self.LRG.add(spr)
+        self.assert_(len(self.LRG._spritelist)==1)
+        self.assert_(self.LRG.get_layer_of_sprite(spr)==100)
+        
+    def test_add_sprite_passing_layer(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        spr = pygame.sprite.Sprite()
+        self.LRG.add(spr, layer=100)
+        self.assert_(len(self.LRG._spritelist)==1)
+        self.assert_(self.LRG.get_layer_of_sprite(spr)==100)
+        
+    def test_add_sprite_overriding_layer_attr(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        spr = pygame.sprite.Sprite()
+        spr._layer = 100
+        self.LRG.add(spr, layer=200)
+        self.assert_(len(self.LRG._spritelist)==1)
+        self.assert_(self.LRG.get_layer_of_sprite(spr)==200)
+        
+    def test_add_sprite_init(self):
+        spr = pygame.sprite.Sprite()
+        lrg2 = LayeredRenderGroup(spr)
+        self.assert_(len(lrg2._spritelist)==1)
+        self.assert_(lrg2._spritelayers[spr]==lrg2._default_layer)
+        
+    def test_add_sprite_init_layer_attr(self):
+        spr = pygame.sprite.Sprite()
+        spr._layer = 20
+        lrg2 = LayeredRenderGroup(spr)
+        self.assert_(len(lrg2._spritelist)==1)
+        self.assert_(lrg2._spritelayers[spr]==20)
+        
+    def test_add_sprite_init_passing_layer(self):
+        spr = pygame.sprite.Sprite()
+        lrg2 = LayeredRenderGroup(spr, layer=33)
+        self.assert_(len(lrg2._spritelist)==1)
+        self.assert_(lrg2._spritelayers[spr]==33)
+        
+    def test_add_sprite_init_overiding_layer(self):
+        spr = pygame.sprite.Sprite()
+        spr._layer = 55
+        lrg2 = LayeredRenderGroup(spr, layer=33)
+        self.assert_(len(lrg2._spritelist)==1)
+        self.assert_(lrg2._spritelayers[spr]==33)
+        
+    def test_add_spritelist(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            sprites.append(pygame.sprite.Sprite())
+        self.LRG.add(sprites)
+        self.assert_(len(self.LRG._spritelist)==10)
+        for i in range(10):
+            self.assert_(self.LRG.get_layer_of_sprite(sprites[i])==self.LRG._default_layer)
+        
+    def test_add_spritelist_with_layer_attr(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            sprites.append(pygame.sprite.Sprite())
+            sprites[-1]._layer = i
+        self.LRG.add(sprites)
+        self.assert_(len(self.LRG._spritelist)==10)
+        for i in range(10):
+            self.assert_(self.LRG.get_layer_of_sprite(sprites[i])==i)
+        
+    def test_add_spritelist_passing_layer(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            sprites.append(pygame.sprite.Sprite())
+        self.LRG.add(sprites, layer=33)
+        self.assert_(len(self.LRG._spritelist)==10)
+        for i in range(10):
+            self.assert_(self.LRG.get_layer_of_sprite(sprites[i])==33)
+        
+    def test_add_spritelist_overriding_layer(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            sprites.append(pygame.sprite.Sprite())
+            sprites[-1].layer = i
+        self.LRG.add(sprites, layer=33)
+        self.assert_(len(self.LRG._spritelist)==10)
+        for i in range(10):
+            self.assert_(self.LRG.get_layer_of_sprite(sprites[i])==33)
+            
+    def test_add_spritelist_init(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            sprites.append(pygame.sprite.Sprite())
+        lrg2 = LayeredRenderGroup(sprites)
+        self.assert_(len(lrg2._spritelist)==10)
+        for i in range(10):
+            self.assert_(lrg2.get_layer_of_sprite(sprites[i])==self.LRG._default_layer)
+        
+    def test_remove_sprite(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            sprites.append(pygame.sprite.Sprite())
+            sprites[-1].rect = 0
+        self.LRG.add(sprites)
+        self.assert_(len(self.LRG._spritelist)==10)
+        for i in range(10):
+            self.LRG.remove(sprites[i])
+        self.assert_(len(self.LRG._spritelist)==0)
+        
+    def test_sprites(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            sprites.append(pygame.sprite.Sprite())
+            sprites[-1]._layer = 10-i
+        self.LRG.add(sprites)
+        self.assert_(len(self.LRG._spritelist)==10)
+        for idx,spr in enumerate(self.LRG.sprites()):
+            self.assert_(spr == sprites[9-idx])
+        
+    def test_layers(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        sprites = []
+        for i in range(10):
+            for j in range(5):
+                sprites.append(pygame.sprite.Sprite())
+                sprites[-1]._layer = i
+        self.LRG.add(sprites)
+        lays = self.LRG.layers()
+        for i in range(10):
+            self.assert_(lays[i] == i)
+            
+    def test_layers2(self):
+        self.assert_(len(self.LRG)==0)
+        layers = [1,4,6,8,3,6,2,6,4,5,6,1,0,9,7,6,54,8,2,43,6,1]
+        for lay in layers:
+            self.LRG.add(pygame.sprite.Sprite(), layer=lay)
+        layers.sort()
+        for idx, spr in enumerate(self.LRG.sprites()):
+            self.assert_(self.LRG.get_layer_of_sprite(spr)==layers[idx])
+            
+    def test_change_layer(self):
+        self.assert_(len(self.LRG._spritelist)==0)
+        spr = pygame.sprite.Sprite()
+        self.LRG.add(spr, layer=99)
+        self.assert_(self.LRG._spritelayers[spr] == 99)
+        self.LRG.change_layer(spr, 44)
+        self.assert_(self.LRG._spritelayers[spr] == 44)
+        
+        spr2 = pygame.sprite.Sprite()
+        spr2.layer = 55
+        self.LRG.add(spr2)
+        self.LRG.change_layer(spr2, 77)
+        self.assert_(spr2.layer == 77)
+        
+    def test_get_top_layer(self):
+        layers = [1,5,2,8,4,5,3,88,23,0]
+        for i in layers:
+            self.LRG.add(pygame.sprite.Sprite(), layer=i)
+        self.assert_(self.LRG.get_top_layer()==max(layers))
+        self.assert_(self.LRG.get_top_layer()==max(self.LRG._spritelayers.values()))
+        self.assert_(self.LRG.get_top_layer()==self.LRG._spritelayers[self.LRG._spritelist[-1]])
+            
+    def test_get_bottom_layer(self):
+        layers = [1,5,2,8,4,5,3,88,23,0]
+        for i in layers:
+            self.LRG.add(pygame.sprite.Sprite(), layer=i)
+        self.assert_(self.LRG.get_bottom_layer()==min(layers))
+        self.assert_(self.LRG.get_bottom_layer()==min(self.LRG._spritelayers.values()))
+        self.assert_(self.LRG.get_bottom_layer()==self.LRG._spritelayers[self.LRG._spritelist[0]])
+            
+    def test_move_to_front(self):
+        layers = [1,5,2,8,4,5,3,88,23,0]
+        for i in layers:
+            self.LRG.add(pygame.sprite.Sprite(), layer=i)
+        spr = pygame.sprite.Sprite()
+        self.LRG.add(spr, layer=3)
+        self.assert_(spr != self.LRG._spritelist[-1]) 
+        self.LRG.move_to_front(spr)
+        self.assert_(spr == self.LRG._spritelist[-1]) 
+        
+    def test_move_to_back(self):
+        layers = [1,5,2,8,4,5,3,88,23,0]
+        for i in layers:
+            self.LRG.add(pygame.sprite.Sprite(), layer=i)
+        spr = pygame.sprite.Sprite()
+        self.LRG.add(spr, layer=55)
+        self.assert_(spr != self.LRG._spritelist[0]) 
+        self.LRG.move_to_back(spr)
+        self.assert_(spr == self.LRG._spritelist[0]) 
+        
+    def test_get_top_sprite(self):
+        layers = [1,5,2,8,4,5,3,88,23,0]
+        for i in layers:
+            self.LRG.add(pygame.sprite.Sprite(), layer=i)
+        self.assert_(self.LRG.get_layer_of_sprite(self.LRG.get_top_sprite())== self.LRG.get_top_layer())
+        
+    def test_get_sprites_from_layer(self):
+        self.assert_(len(self.LRG)==0)
+        sprites = {}
+        layers = [1,4,5,6,3,7,8,2,1,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,0,1,6,5,4,3,2]
+        for lay in layers:
+            spr = pygame.sprite.Sprite()
+            spr._layer = lay
+            self.LRG.add(spr)
+            if not sprites.has_key(lay):
+                sprites[lay] = []
+            sprites[lay].append(spr)
+            
+        for lay in self.LRG.layers():
+            for spr in self.LRG.get_sprites_from_layer(lay):
+                self.assert_(spr in sprites[lay])
+                sprites[lay].remove(spr)
+                if len(sprites[lay]) == 0:
+                    del sprites[lay]
+        self.assert_(len(sprites.values())==0)
+        
+    def test_switch_layer(self):
+        self.assert_(len(self.LRG)==0)
+        sprites1 = []
+        sprites2 = []
+        layers = [3,2,3,2,3,3,2,2,3,2,3,2,3,2,3,2,3,3,2,2,3,2,3]
+        for lay in layers:
+            spr = pygame.sprite.Sprite()
+            spr._layer = lay
+            self.LRG.add(spr)
+            if lay==2:
+                sprites1.append(spr)
+            else:
+                sprites2.append(spr)
+                
+        for spr in sprites1:
+            self.assert_(spr in self.LRG.get_sprites_from_layer(2))
+        for spr in sprites2:
+            self.assert_(spr in self.LRG.get_sprites_from_layer(3))
+        self.assert_(len(self.LRG)==len(sprites1)+len(sprites2))
+        
+        self.LRG.switch_layer(2,3)
+        
+        for spr in sprites1:
+            self.assert_(spr in self.LRG.get_sprites_from_layer(3))
+        for spr in sprites2:
+            self.assert_(spr in self.LRG.get_sprites_from_layer(2))
+        self.assert_(len(self.LRG)==len(sprites1)+len(sprites2))
+        
+#TODO: test FRG and DirtySprite (visible, layer, blendmode and dirty)
+
+if __name__ == "__main__":
+    unittest.main()
+        
+    
+    
+    
+
+
+
+
 if __name__ == '__main__':
     unittest.main()