diff options
Diffstat (limited to 'lib/pygame/sprite.py')
-rwxr-xr-x | lib/pygame/sprite.py | 1423 |
1 files changed, 0 insertions, 1423 deletions
diff --git a/lib/pygame/sprite.py b/lib/pygame/sprite.py deleted file mode 100755 index 5ed2af4..0000000 --- a/lib/pygame/sprite.py +++ /dev/null @@ -1,1423 +0,0 @@ -## 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 |