Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/pygame/sprite.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pygame/sprite.py')
-rw-r--r--src/pygame/sprite.py1423
1 files changed, 1423 insertions, 0 deletions
diff --git a/src/pygame/sprite.py b/src/pygame/sprite.py
new file mode 100644
index 0000000..5ed2af4
--- /dev/null
+++ b/src/pygame/sprite.py
@@ -0,0 +1,1423 @@
+## pygame - Python Game Library
+## 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
+## License as published by the Free Software Foundation; either
+## version 2 of the License, or (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## Library General Public License for more details.
+##
+## You should have received a copy of the GNU Library General Public
+## License along with this library; if not, write to the Free
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+## Pete Shinners
+## pete@shinners.org
+
+"""pygame module with basic game object classes
+
+This module contains several simple classes to be used within games. There
+is the main Sprite class and several Group classes that contain Sprites.
+The use of these classes is entirely optional when using Pygame. The classes
+are fairly lightweight and only provide a starting place for the code
+that is common to most games.
+
+The Sprite class is intended to be used as a base class for the different
+types of objects in the game. There is also a base Group class that simply
+stores sprites. A game could create new types of Group classes that operate
+on specially customized Sprite instances they contain.
+
+The basic Sprite class can draw the Sprites it contains to a Surface. The
+Group.draw() method requires that each Sprite have a Surface.image attribute
+and a Surface.rect. The Group.clear() method requires these same attributes,
+and can be used to erase all the Sprites with background. There are also
+more advanced Groups: pygame.sprite.RenderUpdates() and
+pygame.sprite.OrderedUpdates().
+
+Lastly, this module contains several collision functions. These help find
+sprites inside multiple groups that have intersecting bounding rectangles.
+To find the collisions, the Sprites are required to have a Surface.rect
+attribute assigned.
+
+The groups are designed for high efficiency in removing and adding Sprites
+to them. They also allow cheap testing to see if a Sprite already exists in
+a Group. A given Sprite can exist in any number of groups. A game could use
+some groups to control object rendering, and a completely separate set of
+groups to control interaction or player movement. Instead of adding type
+attributes or bools to a derived Sprite class, consider keeping the
+Sprites inside organized Groups. This will allow for easier lookup later
+in the game.
+
+Sprites and Groups manage their relationships with the add() and remove()
+methods. These methods can accept a single or multiple targets for
+membership. The default initializers for these classes also takes a
+single or list of targets for initial membership. It is safe to repeatedly
+add and remove the same Sprite from a Group.
+
+While it is possible to design sprite and group classes that don't derive
+from the Sprite and AbstractGroup classes below, it is strongly recommended
+that you extend those when you add a Sprite or Group class.
+
+Sprites are not thread safe. So lock them yourself if using threads.
+"""
+
+##todo
+## a group that holds only the 'n' most recent elements.
+## sort of like the GroupSingle class, but holding more
+## than one sprite
+##
+## drawing groups that can 'automatically' store the area
+## underneath, so the can "clear" without needing a background
+## function. obviously a little slower than normal, but nice
+## to use in many situations. (also remember it must "clear"
+## in the reverse order that it draws :])
+##
+## the drawing groups should also be able to take a background
+## function, instead of just a background surface. the function
+## would take a surface and a rectangle on that surface to erase.
+##
+## perhaps more types of collision functions? the current two
+## should handle just about every need, but perhaps more optimized
+## 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
+
+# Don't depend on pygame.mask if it's not there...
+try:
+ from pygame.mask import from_surface
+except:
+ pass
+
+
+class Sprite(object):
+ """simple base class for visible game objects
+ pygame.sprite.Sprite(*groups): return Sprite
+
+ The base class for visible game objects. Derived classes will want to
+ override the Sprite.update() and assign a Sprite.image and
+ Sprite.rect attributes. The initializer can accept any number of
+ Group instances to be added to.
+
+ When subclassing the Sprite, be sure to call the base initializer before
+ adding the Sprite to Groups.
+ """
+
+ def __init__(self, *groups):
+ self.__g = {} # The groups the sprite is in
+ if groups: self.add(groups)
+
+ def add(self, *groups):
+ """add the sprite to groups
+ Sprite.add(*groups): return None
+
+ Any number of Group instances can be passed as arguments. The
+ Sprite will be added to the Groups it is not already a member of.
+ """
+ has = self.__g.__contains__
+ for group in groups:
+ if hasattr(group, '_spritegroup'):
+ if not has(group):
+ group.add_internal(self)
+ self.add_internal(group)
+ else: self.add(*group)
+
+ def remove(self, *groups):
+ """remove the sprite from groups
+ Sprite.remove(*groups): return None
+
+ Any number of Group instances can be passed as arguments. The Sprite will
+ be removed from the Groups it is currently a member of.
+ """
+ has = self.__g.__contains__
+ for group in groups:
+ if hasattr(group, '_spritegroup'):
+ if has(group):
+ group.remove_internal(self)
+ self.remove_internal(group)
+ else: self.remove(*group)
+
+ def add_internal(self, group):
+ self.__g[group] = 0
+
+ def remove_internal(self, group):
+ del self.__g[group]
+
+ def update(self, *args):
+ """method to control sprite behavior
+ Sprite.update(*args):
+
+ The default implementation of this method does nothing; it's just a
+ convenient "hook" that you can override. This method is called by
+ Group.update() with whatever arguments you give it.
+
+ There is no need to use this method if not using the convenience
+ method by the same name in the Group class.
+ """
+ pass
+
+ def kill(self):
+ """remove the Sprite from all Groups
+ Sprite.kill(): return None
+
+ The Sprite is removed from all the Groups that contain it. This won't
+ change anything about the state of the Sprite. It is possible to continue
+ to use the Sprite after this method has been called, including adding it
+ to Groups.
+ """
+ for c in self.__g.keys():
+ c.remove_internal(self)
+ self.__g.clear()
+
+ def groups(self):
+ """list of Groups that contain this Sprite
+ Sprite.groups(): return group_list
+
+ Return a list of all the Groups that contain this Sprite.
+ """
+ return self.__g.keys()
+
+ def alive(self):
+ """does the sprite belong to any groups
+ Sprite.alive(): return bool
+
+ Returns True when the Sprite belongs to one or more Groups.
+ """
+ return (len(self.__g) != 0)
+
+ def __repr__(self):
+ return "<%s sprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))
+
+
+class DirtySprite(Sprite):
+ """a more featureful subclass of Sprite with more attributes
+ pygame.sprite.DirtySprite(*groups): return DirtySprite
+
+ Extra DirtySprite attributes with their default values:
+
+ dirty = 1
+ 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 = 0
+ its the special_flags argument of blit, blendmodes
+
+ source_rect = None
+ source rect to use, remember that it is relative to
+ topleft (0,0) of self.image
+
+ visible = 1
+ normally 1, if set to 0 it will not be repainted
+ (you must set it dirty too to be erased from screen)
+
+ layer = 0
+ (READONLY value, it is read when adding it to the
+ LayeredUpdates, for details see doc of LayeredUpdates)
+ """
+
+ def __init__(self, *groups):
+
+ 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,\n"+
+ "values 0 for invisible and 1 for visible")
+
+ def __repr__(self):
+ return "<%s DirtySprite(in %d groups)>" % (self.__class__.__name__, len(self.groups()))
+
+
+
+class AbstractGroup(object):
+ """A base for containers for sprites. It does everything
+ needed to behave as a normal group. You can easily inherit
+ a new group class from this, or the other groups below,
+ if you want to add more features.
+
+ Any AbstractGroup-derived sprite groups act like sequences,
+ and support iteration, len, and so on."""
+
+ # dummy val to identify sprite groups, and avoid infinite recursion.
+ _spritegroup = True
+
+ def __init__(self):
+ self.spritedict = {}
+ self.lostsprites = []
+
+ def sprites(self):
+ """sprites()
+ get a list of sprites in the group
+
+ Returns an object that can be looped over with a 'for' loop.
+ (For now it is always a list, but newer version of Python
+ could return different iterators.) You can also iterate directly
+ over the sprite group."""
+ return list(self.spritedict.keys())
+
+ def add_internal(self, sprite):
+ self.spritedict[sprite] = 0
+
+ def remove_internal(self, sprite):
+ r = self.spritedict[sprite]
+ if r is not 0:
+ self.lostsprites.append(r)
+ del(self.spritedict[sprite])
+
+ def has_internal(self, sprite):
+ return sprite in self.spritedict
+
+ def copy(self):
+ """copy()
+ copy a group with all the same sprites
+
+ Returns a copy of the group that is the same class
+ type, and has the same sprites in it."""
+ return self.__class__(self.sprites())
+
+ def __iter__(self):
+ return iter(self.sprites())
+
+ def __contains__(self, sprite):
+ return self.has(sprite)
+
+ def add(self, *sprites):
+ """add(sprite, list, or group, ...)
+ add sprite to group
+
+ Add a sprite or sequence of sprites to a group."""
+ 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)
+ 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)
+ 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)
+ spr.add_internal(self)
+ elif not self.has_internal(sprite):
+ self.add_internal(sprite)
+ sprite.add_internal(self)
+
+ def remove(self, *sprites):
+ """remove(sprite, list, or group, ...)
+ remove sprite from group
+
+ Remove a sprite or sequence of sprites from a group."""
+ # This function behaves essentially the same as Group.add.
+ # Check for Spritehood, check for iterability, check for
+ # old-style sprite group, and fall back to assuming
+ # spritehood.
+ for sprite in sprites:
+ if isinstance(sprite, Sprite):
+ if self.has_internal(sprite):
+ self.remove_internal(sprite)
+ sprite.remove_internal(self)
+ else:
+ try:
+ for spr in sprite: self.remove(spr)
+ except (TypeError, AttributeError):
+ if hasattr(sprite, '_spritegroup'):
+ for spr in sprite.sprites():
+ if self.has_internal(spr):
+ self.remove_internal(spr)
+ spr.remove_internal(self)
+ elif self.has_internal(sprite):
+ self.remove_internal(sprite)
+ sprite.remove_internal(self)
+
+ def has(self, *sprites):
+ """has(sprite or group, ...)
+ ask if group has a sprite or sprites
+
+ Returns true if the given sprite or sprites are
+ contained in the group. You can also use 'sprite in group'
+ or 'subgroup in group'."""
+ # Again, this follows the basic pattern of Group.add and
+ # Group.remove.
+ for sprite in sprites:
+ if isinstance(sprite, Sprite):
+ return self.has_internal(sprite)
+
+ try:
+ for spr in sprite:
+ if not self.has(spr):
+ return False
+ return True
+ except (TypeError, AttributeError):
+ if hasattr(sprite, '_spritegroup'):
+ for spr in sprite.sprites():
+ if not self.has_internal(spr):
+ return False
+ return True
+ else:
+ return self.has_internal(sprite)
+
+ def update(self, *args):
+ """update(*args)
+ call update for all member sprites
+
+ calls the update method for all sprites in the group.
+ Passes all arguments on to the Sprite update function."""
+ for s in self.sprites(): s.update(*args)
+
+ def draw(self, surface):
+ """draw(surface)
+ draw all sprites onto the surface
+
+ Draws all the sprites onto the given surface."""
+ sprites = self.sprites()
+ surface_blit = surface.blit
+ for spr in sprites:
+ self.spritedict[spr] = surface_blit(spr.image, spr.rect)
+ self.lostsprites = []
+
+ def clear(self, surface, bgd):
+ """clear(surface, bgd)
+ erase the previous position of all sprites
+
+ Clears the area of all drawn sprites. the bgd
+ argument should be Surface which is the same
+ dimensions as the surface. The bgd can also be
+ a function which gets called with the passed
+ surface and the area to be cleared."""
+ try:
+ bgd.__call__
+ except AttributeError:
+ pass
+ else:
+ for r in self.lostsprites:
+ bgd(surface, r)
+ for r in self.spritedict.values():
+ if r is not 0: bgd(surface, r)
+ return
+ surface_blit = surface.blit
+ for r in self.lostsprites:
+ surface_blit(bgd, r, r)
+ for r in self.spritedict.values():
+ if r is not 0: surface_blit(bgd, r, r)
+
+ def empty(self):
+ """empty()
+ remove all sprites
+
+ Removes all the sprites from the group."""
+ for s in self.sprites():
+ self.remove_internal(s)
+ s.remove_internal(self)
+
+ def __nonzero__(self):
+ return (len(self.sprites()) != 0)
+
+ def __len__(self):
+ """len(group)
+ number of sprites in group
+
+ Returns the number of sprites contained in the group."""
+ return len(self.sprites())
+
+ def __repr__(self):
+ return "<%s(%d sprites)>" % (self.__class__.__name__, len(self))
+
+class Group(AbstractGroup):
+ """container class for many Sprites
+ pygame.sprite.Group(*sprites): return Group
+
+ A simple container for Sprite objects. This class can be inherited to
+ create containers with more specific behaviors. The constructor takes any
+ number of Sprite arguments to add to the Group. The group supports the
+ following standard Python operations:
+
+ in test if a Sprite is contained
+ len the number of Sprites contained
+ bool test if any Sprites are contained
+ iter iterate through all the Sprites
+
+ The Sprites in the Group are not ordered, so drawing and iterating the
+ Sprites is in no particular order.
+ """
+
+ def __init__(self, *sprites):
+ AbstractGroup.__init__(self)
+ self.add(*sprites)
+
+RenderPlain = Group
+RenderClear = Group
+
+class RenderUpdates(Group):
+ """Group class that tracks dirty updates
+ pygame.sprite.RenderUpdates(*sprites): return RenderUpdates
+
+ This class is derived from pygame.sprite.Group(). It has an extended draw()
+ method that tracks the changed areas of the screen.
+ """
+
+ def draw(self, surface):
+ spritedict = self.spritedict
+ surface_blit = surface.blit
+ dirty = self.lostsprites
+ self.lostsprites = []
+ dirty_append = dirty.append
+ for s in self.sprites():
+ 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)
+ dirty_append(r)
+ spritedict[s] = newrect
+ return dirty
+
+class OrderedUpdates(RenderUpdates):
+ """RenderUpdates class that draws Sprites in order of addition
+ pygame.sprite.OrderedUpdates(*spites): return OrderedUpdates
+
+ This class derives from pygame.sprite.RenderUpdates(). It maintains
+ the order in which the Sprites were added to the Group for rendering.
+ This makes adding and removing Sprites from the Group a little
+ slower than regular Groups.
+ """
+
+ def __init__(self, *sprites):
+ self._spritelist = []
+ RenderUpdates.__init__(self, *sprites)
+
+ def sprites(self):
+ return list(self._spritelist)
+
+ def add_internal(self, sprite):
+ RenderUpdates.add_internal(self, sprite)
+ self._spritelist.append(sprite)
+
+ def remove_internal(self, sprite):
+ RenderUpdates.remove_internal(self, sprite)
+ self._spritelist.remove(sprite)
+
+
+class LayeredUpdates(AbstractGroup):
+ """LayeredUpdates Group handles layers, that draws like OrderedUpdates.
+ pygame.sprite.LayeredUpdates(*spites, **kwargs): return LayeredUpdates
+
+ This group is fully compatible with pygame.sprite.Sprite.
+
+ New in pygame 1.8.0
+ """
+
+ 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 contains '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)
+ self._default_layer = kwargs.get('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:
+ try:
+ layer = sprite._layer
+ except AttributeError:
+ 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 a sprite or sequence of sprites to a group
+ LayeredUpdates.add(*sprites, **kwargs): return None
+
+ 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 'layer' in kwargs:
+ layer = kwargs['layer']
+ if sprites is None or not sprites:
+ 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).
+ LayeredUpdates.sprites(): return sprites
+ """
+ return list(self._spritelist)
+
+ def draw(self, surface):
+ """draw all sprites in the right order onto the passed surface.
+ LayeredUpdates.draw(surface): return Rect_list
+ """
+ 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.
+ LayeredUpdates.get_sprites_at(pos): return colliding_sprites
+
+ 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 groups sprites
+ LayeredUpdates.get_sprite(idx): return sprite
+
+ Raises IndexOutOfBounds if the idx is not within range.
+ """
+ return self._spritelist[idx]
+
+ def remove_sprites_of_layer(self, layer_nr):
+ """removes all sprites from a layer and returns them as a list
+ LayeredUpdates.remove_sprites_of_layer(layer_nr): return sprites
+ """
+ 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.
+ LayeredUpdates.layers(): return layers
+ """
+ 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
+ LayeredUpdates.change_layer(sprite, new_layer): return None
+
+ 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 top layer
+ LayeredUpdates.get_top_layer(): return layer
+ """
+ return self._spritelayers[self._spritelist[-1]]
+
+ def get_bottom_layer(self):
+ """returns the bottom layer
+ LayeredUpdates.get_bottom_layer(): return layer
+ """
+ return self._spritelayers[self._spritelist[0]]
+
+ def move_to_front(self, sprite):
+ """brings the sprite to front layer
+ LayeredUpdates.move_to_front(sprite): return None
+
+ Brings the sprite to front, changing sprite layer to 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
+ LayeredUpdates.move_to_back(sprite): return None
+
+ 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
+ LayeredUpdates.get_top_sprite(): return Sprite
+ """
+ return self._spritelist[-1]
+
+ def get_sprites_from_layer(self, layer):
+ """returns all sprites from a layer, ordered by how they where added
+ LayeredUpdates.get_sprites_from_layer(layer): return sprites
+
+ 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
+ LayeredUpdates.switch_layer(layer1_nr, layer2_nr): return None
+
+ 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):
+ """LayeredDirty Group is for DirtySprites. Subclasses LayeredUpdates.
+ pygame.sprite.LayeredDirty(*spites, **kwargs): return LayeredDirty
+
+ This group requires pygame.sprite.DirtySprite or any sprite that
+ has the following attributes:
+ image, rect, dirty, visible, blendmode (see doc of DirtySprite).
+
+ 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 drawing, so you do no have to worry what would be faster.
+
+ Same as for the pygame.sprite.Group.
+ You can specify some additional attributes through kwargs:
+ _use_update: True/False default is False
+ _default_layer: default layer where 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
+
+ New in pygame 1.8.0
+ """
+
+ def __init__(self, *sprites, **kwargs):
+ """Same as for the pygame.sprite.Group.
+ pygame.sprite.LayeredDirty(*spites, **kwargs): return LayeredDirty
+
+ 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):
+ """draw all sprites in the right order onto the passed surface.
+ LayeredDirty.draw(surface, bgd=None): return Rect_list
+
+ 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):
+ """used to set background
+ Group.clear(surface, bgd): return None
+ """
+ self._bgd = bgd
+
+ def repaint_rect(self, screen_rect):
+ """repaints the given area
+ LayeredDirty.repaint_rect(screen_rect): return None
+
+ screen_rect is 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
+ LayeredDirty.set_clip(screen_rect=None): return None
+ """
+ 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):
+ """clip the area where to draw. Just pass None (default) to reset the clip
+ LayeredDirty.get_clip(): return Rect
+ """
+ return self._clip
+
+ def change_layer(self, sprite, new_layer):
+ """changes the layer of the sprite
+ change_layer(sprite, new_layer): return None
+
+ sprite must have been added to the renderer. It is not checked.
+ """
+ LayeredUpdates.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
+ set_timing_treshold(time_ms): return None
+
+ 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
+ keeps a single sprite in the group. Whatever sprite has
+ been added to the group last, will be the only sprite in
+ the group.
+
+ You can access its one sprite as the .sprite attribute.
+ Assigning to this attribute will properly remove the old
+ sprite and then add the new one."""
+
+ def __init__(self, sprite = None):
+ AbstractGroup.__init__(self)
+ self.__sprite = None
+ if sprite is not None: self.add(sprite)
+
+ def copy(self):
+ return GroupSingle(self.__sprite)
+
+ def sprites(self):
+ if self.__sprite is not None: return [self.__sprite]
+ else: return []
+
+ def add_internal(self, sprite):
+ if self.__sprite is not None:
+ self.__sprite.remove_internal(self)
+ self.__sprite = sprite
+
+ def __nonzero__(self): return (self.__sprite is not None)
+
+ def _get_sprite(self):
+ return self.__sprite
+
+ def _set_sprite(self, sprite):
+ self.add_internal(sprite)
+ sprite.add_internal(self)
+ return sprite
+
+ sprite = property(_get_sprite, _set_sprite, None,
+ "The sprite contained in this group")
+
+ def remove_internal(self, sprite):
+ if sprite is self.__sprite: self.__sprite = None
+
+ def has_internal(self, sprite):
+ return (self.__sprite is sprite)
+
+ # Optimizations...
+ def __contains__(self, sprite): return (self.__sprite is sprite)
+
+
+
+
+
+# some different collision detection functions that could be used.
+
+def collide_rect(left, right):
+ """collision detection between two sprites, using rects.
+ pygame.sprite.collide_rect(left, right): return bool
+
+ Tests for collision between two sprites. Uses the
+ pygame rect colliderect function to calculate the
+ collision. Intended to be passed as a collided
+ callback function to the *collide functions.
+ Sprites must have a "rect" attributes.
+
+ New in pygame 1.8.0
+ """
+ return left.rect.colliderect(right.rect)
+
+class collide_rect_ratio:
+ """A callable class that checks for collisions between
+ two sprites, using a scaled version of the sprites
+ rects.
+
+ Is created with a ratio, the instance is then intended
+ to be passed as a collided callback function to the
+ *collide functions.
+
+ New in pygame 1.8.1
+ """
+
+ def __init__( self, ratio ):
+ """Creates a new collide_rect_ratio callable. ratio is
+ expected to be a floating point value used to scale
+ the underlying sprite rect before checking for
+ collisions.
+ """
+
+ self.ratio = ratio
+
+ def __call__( self, left, right ):
+ """pygame.sprite.collide_rect_ratio(ratio)(left, right): bool
+ collision detection between two sprites, using scaled rects.
+
+ Tests for collision between two sprites. Uses the
+ pygame rect colliderect function to calculate the
+ collision, after scaling the rects by the stored ratio.
+ Sprites must have a "rect" attributes.
+ """
+
+ ratio = self.ratio
+
+ leftrect = left.rect
+ width = leftrect.width
+ height = leftrect.height
+ leftrect = leftrect.inflate( width * ratio - width, height * ratio - height )
+
+ rightrect = right.rect
+ width = rightrect.width
+ height = rightrect.height
+ rightrect = rightrect.inflate( width * ratio - width, height * ratio - height )
+
+ return leftrect.colliderect( rightrect )
+
+def collide_circle( left, right ):
+ """collision detection between two sprites, using circles.
+ pygame.sprite.collide_circle(left, right): return bool
+
+ Tests for collision between two sprites, by testing to
+ see if two circles centered on the sprites overlap. If
+ the sprites have a "radius" attribute, that is used to
+ create the circle, otherwise a circle is created that
+ is big enough to completely enclose the sprites rect as
+ given by the "rect" attribute. Intended to be passed as
+ a collided callback function to the *collide functions.
+ Sprites must have a "rect" and an optional "radius"
+ attribute.
+
+ New in pygame 1.8.0
+ """
+
+ xdistance = left.rect.centerx - right.rect.centerx
+ ydistance = left.rect.centery - right.rect.centery
+ distancesquared = xdistance ** 2 + ydistance ** 2
+ try:
+ leftradiussquared = left.radius ** 2
+ except AttributeError:
+ leftrect = left.rect
+ leftradiussquared = ( leftrect.width ** 2 + leftrect.height ** 2 ) / 4
+ try:
+ rightradiussquared = right.radius ** 2
+ except AttributeError:
+ rightrect = right.rect
+ rightradiussquared = ( rightrect.width ** 2 + rightrect.height ** 2 ) / 4
+ return distancesquared < leftradiussquared + rightradiussquared
+
+class collide_circle_ratio( object ):
+ """A callable class that checks for collisions between
+ two sprites, using a scaled version of the sprites radius.
+
+ Is created with a ratio, the instance is then intended
+ to be passed as a collided callback function to the
+ *collide functions.
+
+ New in pygame 1.8.1
+ """
+
+ def __init__( self, ratio ):
+ """Creates a new collide_circle_ratio callable. ratio is
+ expected to be a floating point value used to scale
+ the underlying sprite radius before checking for
+ collisions.
+ """
+ self.ratio = ratio
+ # Constant value that folds in division for diameter to radius,
+ # when calculating from a rect.
+ self.halfratio = ratio ** 2 / 4.0
+
+ def __call__( self, left, right ):
+ """pygame.sprite.collide_circle_radio(ratio)(left, right): return bool
+ collision detection between two sprites, using scaled circles.
+
+ Tests for collision between two sprites, by testing to
+ see if two circles centered on the sprites overlap, after
+ scaling the circles radius by the stored ratio. If
+ the sprites have a "radius" attribute, that is used to
+ create the circle, otherwise a circle is created that
+ is big enough to completely enclose the sprites rect as
+ given by the "rect" attribute. Intended to be passed as
+ a collided callback function to the *collide functions.
+ Sprites must have a "rect" and an optional "radius"
+ attribute.
+ """
+
+ ratio = self.ratio
+ xdistance = left.rect.centerx - right.rect.centerx
+ ydistance = left.rect.centery - right.rect.centery
+ distancesquared = xdistance ** 2 + ydistance ** 2
+ # Optimize for not containing radius attribute, as if radius was
+ # set consistently, would probably be using collide_circle instead.
+ if hasattr( left, "radius" ):
+ leftradiussquared = (left.radius * ratio) ** 2
+
+ if hasattr( right, "radius" ):
+ rightradiussquared = (right.radius * ratio) ** 2
+ else:
+ halfratio = self.halfratio
+ rightrect = right.rect
+ rightradiussquared = (rightrect.width ** 2 + rightrect.height ** 2) * halfratio
+ else:
+ halfratio = self.halfratio
+ leftrect = left.rect
+ leftradiussquared = (leftrect.width ** 2 + leftrect.height ** 2) * halfratio
+
+ if hasattr( right, "radius" ):
+ rightradiussquared = (right.radius * ratio) ** 2
+ else:
+ rightrect = right.rect
+ rightradiussquared = (rightrect.width ** 2 + rightrect.height ** 2) * halfratio
+ return distancesquared < leftradiussquared + rightradiussquared
+
+def collide_mask(left, right):
+ """collision detection between two sprites, using masks.
+ pygame.sprite.collide_mask(SpriteLeft, SpriteRight): bool
+
+ Tests for collision between two sprites, by testing if
+ thier bitmasks overlap. If the sprites have a "mask"
+ attribute, that is used as the mask, otherwise a mask is
+ created from the sprite image. Intended to be passed as
+ a collided callback function to the *collide functions.
+ Sprites must have a "rect" and an optional "mask"
+ attribute.
+
+ New in pygame 1.8.0
+ """
+ xoffset = right.rect[0] - left.rect[0]
+ yoffset = right.rect[1] - left.rect[1]
+ try:
+ leftmask = left.mask
+ except AttributeError:
+ leftmask = from_surface(left.image)
+ try:
+ rightmask = right.mask
+ except AttributeError:
+ rightmask = from_surface(right.image)
+ return leftmask.overlap(rightmask, (xoffset, yoffset))
+
+def spritecollide(sprite, group, dokill, collided = None):
+ """find Sprites in a Group that intersect another Sprite
+ pygame.sprite.spritecollide(sprite, group, dokill, collided = None): return Sprite_list
+
+ Return a list containing all Sprites in a Group that intersect with another
+ Sprite. Intersection is determined by comparing the Sprite.rect attribute
+ of each Sprite.
+
+ The dokill argument is a bool. If set to True, all Sprites that collide
+ will be removed from the Group.
+
+ The collided argument is a callback function used to calculate if two sprites
+ are colliding. it should take two sprites as values, and return a bool
+ value indicating if they are colliding. If collided is not passed, all sprites
+ must have a "rect" value, which is a rectangle of the sprite area, which will
+ be used to calculate the collision.
+ """
+ crashed = []
+ if collided is None:
+ # Special case old behaviour for speed.
+ spritecollide = sprite.rect.colliderect
+ if dokill:
+ for s in group.sprites():
+ if spritecollide(s.rect):
+ s.kill()
+ crashed.append(s)
+ else:
+ for s in group:
+ if spritecollide(s.rect):
+ crashed.append(s)
+ else:
+ if dokill:
+ for s in group.sprites():
+ if collided(sprite, s):
+ s.kill()
+ crashed.append(s)
+ else:
+ for s in group:
+ if collided(sprite, s):
+ crashed.append(s)
+ return crashed
+
+def groupcollide(groupa, groupb, dokilla, dokillb, collided = None):
+ """pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb) -> dict
+ collision detection between group and group
+
+ given two groups, this will find the intersections
+ between all sprites in each group. it returns a
+ dictionary of all sprites in the first group that
+ collide. the value for each item in the dictionary
+ is a list of the sprites in the second group it
+ collides with. the two dokill arguments control if
+ the sprites from either group will be automatically
+ removed from all groups.
+ collided is a callback function used to calculate if
+ two sprites are colliding. it should take two sprites
+ as values, and return a bool value indicating if
+ they are colliding. if collided is not passed, all
+ sprites must have a "rect" value, which is a
+ rectangle of the sprite area, which will be used
+ to calculate the collision."""
+ crashed = {}
+ SC = spritecollide
+ if dokilla:
+ for s in groupa.sprites():
+ c = SC(s, groupb, dokillb, collided)
+ if c:
+ crashed[s] = c
+ s.kill()
+ else:
+ for s in groupa:
+ c = SC(s, groupb, dokillb, collided)
+ if c:
+ crashed[s] = c
+ return crashed
+
+def spritecollideany(sprite, group, collided = None):
+ """pygame.sprite.spritecollideany(sprite, group) -> sprite
+ finds any sprites that collide
+
+ given a sprite and a group of sprites, this will
+ return return any single sprite that collides with
+ with the given sprite. If there are no collisions
+ this returns None.
+
+ if you don't need all the features of the
+ spritecollide function, this function will be a
+ bit quicker.
+
+ collided is a callback function used to calculate if
+ two sprites are colliding. it should take two sprites
+ as values, and return a bool value indicating if
+ they are colliding. if collided is not passed, all
+ sprites must have a "rect" value, which is a
+ rectangle of the sprite area, which will be used
+ to calculate the collision."""
+ if collided is None:
+ # Special case old behaviour for speed.
+ spritecollide = sprite.rect.colliderect
+ for s in group:
+ if spritecollide(s.rect):
+ return s
+ else:
+ for s in group:
+ if collided(sprite, s):
+ return s
+ return None