[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
Re: [pygame] Dirty Rectangle Processing
Hallo again
Kamilche schrieb:
DR0ID wrote:
Thank you for that. It's astonishing fast! Congratulations!
You're welcome, I'm glad you found it useful!
I tested your code:
I have ~412 fps in windowed mode and ~610 fps in fullscreen on a
PentiumM 1.5Ghz with 1GB ram and WinXP Prof.
Here's some more timings you'll find interesting:
1.4 ghz PowerMac G4 10.3.7 512 megs ram (sandwich Mac) - 432 fps
350 mhz Compaq Presario 160 megs ram, shared video - 51 fps
2.6 gHz Compaq Presario 1 gig ram, shared video - 604 fps
Interesting!
And note - alt-enter even works on the Mac, though the Mac doesn't
have an alt key and it's the option key instead.
If you permit I would like to use your fullscreen toggle code furder. (
I borrowed it for the attachet compare.py).
The target frame range on my game engine is 12 fps - as long as the
slowest machine can stay above that, they'll be able to play the game
no problem.
(my LCD display is slow so I can see 4-6 rects lined up in the moving
direction!!)
Mine too, in fullscreen mode! In windowed mode it's slow enough it
doesn't do that.
I looked at your code and noticed, that I had forgotten about the
clipping on a surface. That can make some thing faster.
Yeah... really only DRAW the bit you need, instead of overdrawing and
expecting the clipping area to throw away the unnecessary bits. In my
opinion, the current clipping rectangle is a crutch for those who
don't want to finish the calculations.
A bit more info:
The frame rate is actually better than this on my actual engine,
because I don't have any sprites move every timer tick, as in this
example. My 'real' engine frame rate SAYS over 40,000 fps, but that's
misleading, because unless you have something being drawn every frame,
you don't really know what the FPS is. When I add some 'maximum
velocity' sprites (sprites that draw every frame), the frame rates
drop to what I posted here.
Still, while I'm developing the engine, I display the FPS at all times
even though it's misleading, so if the frame rate drops sharply, I'll
know I just implemented something stupid.
Yes, I do the same. Ok until now its only for the sprite engine. If
there are no sprites moving or animating than nothing has to be drawen
and so its very fast and missleading number, as you say.
Overall, those sprite classes are easy to implement, and don't have to
worry about the dirty rect calculations. Basically, just subclass from
sprite and override the paint method. The text class is a little more
involved because until the text is painted, I don't know how big it
is. Once it's been painted, you need to update the 'size' with the
final size, so the SetRect calculation will be correct.
Now that you have the example, these points will make more sense:
1. On __init__, paint the sprite immediately (no paint flag involvement).
2. When the sprite moves, dirty the screen by calling 'SetRect' ONLY -
its appearance hasn't changed, only its position. SetRect is the magic
function behind the dirty rectangle processing, and handles dirtying
the screen without errors.
3. When the sprite changes appearance (such as size or text), mark the
sprite as needing to be repainted (set the paint flag to 1) and dirty
its area on the screen. That's a minor bug in the code I posted - when
the text changes in the text class, add 'self.SetRect()' right after
the 'self.paint = 1' line. I extracted this from a much larger engine,
and that got overlooked. Setting the paint flag and executing
self.SetRect always go hand in hand.
4. Finally - the end of paint ALWAYS calls SetRect. ALWAYS. If you
create a new class with drawing bugs, look for that error first -
forgetting to include a 'SetRect' at the end of Paint.
--Kamilche
I must say your design is flexible and clear. I do not want to copy, but
some ideas I would like to put in my sprite engine, if you permit it.
I also borrowed also the code to calculate the framerate and I'm not
sure if it is correct that way, so please CHECK the lines 100 until 110
and the line 200!!
Until now my sprite engine is compatible with the old pygame.sprites and
that is why it is perhaps not so flexible (I actually use a
pygame.sprite.group just to make the update call but I think people will
appreciate that they can still use the pygame.sprite.groups, or not?).
This morning as I tested your code, I was astonished about the speed and
conerned that I would have to rewrite things. Finally I sat down to
write a similar program to see, how my engine would perform in a similar
situation. I was astonished! I have attached the code so you can see it
by yourself ( sorry for the messy code, it is far from finished and I
have to clean up things, for example the "lostrect" thing). But take a
look!
start compar.py and then you have these key:
s : activates the step mode, press any key except s to step one frame furder
d : activates debug modus (it's full screen redraw, so you see it's slow)
f : toggles 2 fps and fastest as it can
alt+enter: toggles fullscreen ( I hope this works, have to revision my
screen changing handling)
esc : to exit
And I did not clip anything yet and I'm sure there are still more
speedups. And it uses the dirty screen area minimizing code I posted in
the past.
I have on a PentiumM 1.5GHz w 1GB ram and WinXP prof.:
windowed: ~660 fps
fullscreen: ~640 more or less the same ( perhaps little bit slower )
I made another test: I increased the number of moving rectangles to 80.
I feel a bit sorry, but my sprite engine had still ~100 fps, but your
dropped to ~50 on my machine. To test it you have to change line 133 in
compare.py and in your code lines 210 and 211 to:
#----------------------------------
for i in range(80):
Square(backcolor = colors[i%10])
#------------------------------------
I'm wondering what other people will get on their machines?
~DR0ID
import pygame
##class Sprite(pygame.sprite.Sprite):
##
## def __init__(self, img, *groups):
## pygame.sprite.Sprite.__init__(self, *groups)
## self.image = img
## self.rect = img.get_rect()
## self._rendererRemove = Renderer()._sprites.remove # for layer its: Renderer()._sprites[layer].remove
## self._rendererAppend = Renderer()._sprites.append # Renderer()._sprites[layer].append
## self.dirty = 0 # has to be last entry because of __setattr__()!!
##
## def __setattr__(self, name, value):
## object.__setattr__(self, name, value)
## if(name=="dirty" and value>0):
## self._rendererAppend(self)
##
## def kill():
## try:
## self._rendererRemove(self)
## except:
## pass
##
###==============================================================================
##class NewRect(pygame.Rect):
##
###------------------------------------------------------------------------------
## def __setattr__(self, name, value):
## pygame.Rect.__setattr__(self, name, value)
## if name in ['size','width','height', 'w', 'h']:
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
## elif 'area' == name:
## raise TypeError, str(self.__class__.__name__) +" area attribute is read only!"
##
###------------------------------------------------------------------------------
## def __getattr__(self, name):
## if 'area' == name:
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
## return self.area
## else:
## return pygame.Rect.__getattribute__(self, name)
##
###------------------------------------------------------------------------------
## def move(self, x, y):
## return NewRect(pygame.Rect.move(self, x, y))
##
###------------------------------------------------------------------------------
## def inflate(self, x, y):
## return NewRect(pygame.Rect.inflate(self, x, y))
##
###------------------------------------------------------------------------------
## def inflate_ip(self, x, y):
## pygame.Rect.inflate_ip(self, x, y)
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##
###------------------------------------------------------------------------------
## def clamp(self,rect):
## return NewRect(pygame.Rect.clamp(self,rect))
##
###------------------------------------------------------------------------------
## def clamp_ip(self,rect):
## pygame.Rect.clamp_ip(self,rect)
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##
###------------------------------------------------------------------------------
## def clip(self,rect):
## return NewRect(pygame.Rect.clip(self,rect))
##
###------------------------------------------------------------------------------
## def clip_ip(self,rect):
## pygame.Rect.clip_ip(self,rect)
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##
###------------------------------------------------------------------------------
## def union(self,rect):
## return NewRect(pygame.Rect.union(self,rect))
##
###------------------------------------------------------------------------------
## def union_ip(self,rect):
## pygame.Rect.union_ip(self,rect)
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##
###------------------------------------------------------------------------------
## def unionall(self, *rect):
## return NewRect(pygame.Rect.unionall(self,rect))
##
###------------------------------------------------------------------------------
## def unionall_ip(self, *rect):
## pygame.Rect.unionall_ip(self,rect)
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##
###------------------------------------------------------------------------------
## def fit(self,rect):
## return NewRect(pygame.Rect.fit(self,rect))
##
###------------------------------------------------------------------------------
## def normalize(self):
## pygame.Rect.normalize(self)
## pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##
###------------------------------------------------------------------------------
## def overlapArea(self,rect):
## """
## Size of the overlaping area.
## """
## return self.clip(rect).area
##
###------------------------------------------------------------------------------
## def unionArea(self,rect):
## """
## Size of the area after union the two rects.
## """
## return self.union(rect).area
##
###------------------------------------------------------------------------------
## def nonOverlapUnionArea(self,rect):
## """
## Size of the non overlaping area of the united rects.
## """
## return self.unionArea(rect)-self.overlapArea(rect)
##
###------------------------------------------------------------------------------
## def nonOverlapArea(self,rect):
## """
## Size of non overlaping area, r1.area+r2.area-r1.clip(r2).area
## """
## return self.area+rect.area-self.clip(rect).area
##
###------------------------------------------------------------------------------
## def percentOverlapOfUnionArea(self,rect):
## return float(self.overlapArea(rect))/self.unionArea(rect)
##
###------------------------------------------------------------------------------
## def percentOverlapOfArea(self,rect):
## return float(self.overlapArea(rect))/(self.area+rect.area)
##
###==============================================================================
##
#==============================================================================
class ObjectBase(object):
pass
## def __init__(self,...):
## self.rect = .... # collision rect, holds position and size
## self.sprite = sprite, #image, rect of image has offset position, you have to move it also when moving the Object
## #needs rect for grphical processing
#==============================================================================
class TestSprite(pygame.Rect):
def __init__(self, *groups):
pygame.sprite.Sprite(*groups)
self.__g = {} # The groups the sprite is in
if groups: self.add(groups)
def add(self, *groups):
"""add(group or list of of groups, ...)
add a sprite to container
Add the sprite to a group or sequence of groups."""
has = self.__g.has_key
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(group or list of groups, ...)
remove a sprite from container
Remove the sprite from a group or sequence of groups."""
has = self.__g.has_key
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):
pass
def kill(self):
"""kill()
remove this sprite from all groups
Removes the sprite from all the groups that contain
it. The sprite still exists after calling this,
so you could use it to remove a sprite from all groups,
and then add it to some other groups."""
for c in self.__g.keys():
c.remove_internal(self)
self.__g.clear()
def groups(self):
"""groups() -> list of groups
list used sprite containers
Returns a list of all the groups that contain this
sprite. These are not returned in any meaningful order."""
return self.__g.keys()
def alive(self):
"""alive() -> bool
check to see if the sprite is in any groups
Returns true if this sprite is a member of any groups."""
return (len(self.__g) != 0)
def __repr__(self):
return "<%s sprite(in %d groups)%s>" % (self.__class__.__name__, len(self.__g), pygame.Rect.__repr__(self))
#==============================================================================
class NewSprite(pygame.sprite.Sprite):
#------------------------------------------------------------------------------
## def __init__(self, img, *groups):
def __init__(self, groups=(), register=True):
pygame.sprite.Sprite.__init__(self, groups)
## self.image = img
## self.rect = pygame.Rect(-1000,-1000,1,1)#NewRect(img.get_rect())
## self.area = self.rect.width * self.rect.height # area = widht * height
self.dirty = 1
## self._layer = 0 # read only !
self._renderer = Renderer
if register:
self._renderer.add(self)
## self._createDirtyRect = True
#------------------------------------------------------------------------------
def __setattr__(self, name, value):
pygame.sprite.Sprite.__setattr__(self, name, value)
if(name=="rect"):
## pygame.sprite.Sprite.__setattr__(self,"rect",NewRect(self.rect))
pygame.sprite.Sprite.__setattr__(self, "area", self.rect.width*self.rect.height)
#------------------------------------------------------------------------------
def kill(self):
pygame.sprite.Sprite.kill(self)
self._renderer.remove(self)
#------------------------------------------------------------------------------
def getLayer(self):
return self._renderer.getLayerOfSprite(self)
#------------------------------------------------------------------------------
def changeLayer(self, toLayer, removeEmptyLayers = True):
self._renderer.changeSpriteLayer(self, toLayer, removeEmptyLayers)
#------------------------------------------------------------------------------
#==============================================================================
#TODO: RLEACCEL flag and use convert() and conver_alpha() in resourcemanager!!
class _Renderer(object):
_instance = 0
#------------------------------------------------------------------------------
def __init__(self):
# Singleton pattern
if self._instance is not 0:
raise ReferenceError , "dont try to instanciate the Renderer, use .Renderer or _Renderer._instance"
_Renderer._instance = self
# Sprite lists
self._sprites = {} # {spr:layerNr, ...} global list of sprites
self._layers = {} # {layer:[spr,...]} global list of layers
self._sortedLayers = [] # sorted layers self._layers.keys().sort()
self._needSortLayers = True
# Rect lists
self._lostRect = [] # removed sprites to clear
self._spriteOldRect = {} # {sprite:rect} old position rects
self._update = []
# screen, background
self._screen = 0
self._background = 0
self._background_color = (255,0,255)
self._getScreen = pygame.display.get_surface # function pointer
self._display_update = pygame.display.update
# timing
self._clock = pygame.time.Clock()
self._clock_tick = self._clock.tick
self._fps = 0
# init
self._initialized = False
# function pointers: .render(self) and .flip(self)
self.render = self._init # first time it points to _init() after that to _render()
self.flip = self._flipInit # first points to _flipInit() after that to _flip()
#------------------------------------------------------------------------------
def switchDEBUG(self, fps=-1):
if(self._initialized):
if self.render == self._render:
if -1!=fps:
self._fps = fps
self.render = self._DEBUGrender
else:
self._flip()
self.render = self._render
else:
raise TypeError, "Renderer not initialized!!!"
# Basic Algorithme:
# 1. determine dirty rects on screen (optimize dirty rects by overlap checks, tricky)
# 2. blit background area
# 2. find all sprites colliding with dirty rects
# 3. blit all found sprites
#dirty == -1 -> -1 do nothing (this ones are not on screen, but still in rendererqueue)
#dirty == 0 -> 0 do nothing (this ones are on screen but dont need repaint)
#dirty == 1 -> 0 clear and draw and reset dirty = 0 (this ones are on screen and need repaint, but perhaps next time not)
#dirty == 2 -> 2 clear and draw and do not reset dirty flag (for animation, allway needs repaint)
###dirty == 3 -> -1 clear but do not draw and reset dirty = -1 (this ones are on screen, but they are erased and not repainted)
#------------------------------------------------------------------------------
def add(self, *sprites):
"""
Add a sprite to renderer. One can also pass a iterable sequence of sprites.
"""
for sprite in sprites:
if isinstance(sprite, NewSprite):
## if not hasattr(sprite, "area"):
## sprite.area = sprite.rect.width*sprite.rect.height
self.changeSpriteLayer(sprite, 0)
self._spriteOldRect[sprite] = 0
else:
self.add(*sprite)
#------------------------------------------------------------------------------
def remove(self, sprite, removeEmptyLayers=True):
"""
Revmoves a sprite from renderer.
"""
if self._sprites.has_key(sprite):
self._layers[ self._sprites[sprite] ].remove(sprite)
if len( self._layers[ self._sprites[sprite] ] )==0 and removeEmptyLayers: # if layer is empty
del self._layers[ self._sprites[sprite] ] # remove it
self._needSortLayers = True
del self._sprites[sprite]
del self._spriteOldRect[sprite]
self._lostRect.append(sprite.rect)
#------------------------------------------------------------------------------
def changeSpriteLayer(self, sprite, toLayer, removeEmptyLayers=True):
"""
Changes the layer of a sprite and removes empty layers. Set "removeEmptyLayers" to
False if you want to retain empty layers.
"""
if self._sprites.has_key(sprite): # remove sprite from old layer if exists
self._layers[ self._sprites[sprite] ].remove(sprite)
if len( self._layers[ self._sprites[sprite] ] )==0 and removeEmptyLayers: # if layer is empty
del self._layers[ self._sprites[sprite] ] # remove it
self._needSortLayers = True
self._sprites[sprite] = toLayer # assign new layer to sprite
if self._layers.has_key(toLayer): # and sprite to layer
self._layers[toLayer].append(sprite) # if layer already exists then append
else:
self._layers[toLayer] = [sprite] # else new list
self._needSortLayers = True
if 2 != sprite.dirty and toLayer>-1:
sprite.dirty = 1
if 0>toLayer:
self._lostRect.append(sprite.rect)
#------------------------------------------------------------------------------
def getLayerOfSprite(self, sprite):
if self._sprites.has_key(sprite):
return self._layers[ self._sprites[sprite] ]
else:
return None
#------------------------------------------------------------------------------
def getSpritesOfLayer(self, layerNr):
if self._layers.has_key(layerNr):
return list(self._layers[layerNr])
else:
return None
#------------------------------------------------------------------------------
def moveLayer(self, toLayer, removeEmptyLayer=True):
raise NotImplementedError
#------------------------------------------------------------------------------
def clear(self):
"""
Removes all sprites from renderer!
"""
for spr in self._sprites:
self.remove(spr)
self.render()
self._sprites.clear()
self._layers.clear()
self._needSortLayers = True
#------------------------------------------------------------------------------
def setBackground(self, bgdSurface):
"""
Set the background to use to erase the sprites.
"""
# TODO: stretch or warning if background has not same size as screen???
self._background = bgdSurface
#------------------------------------------------------------------------------
def getBackground(self):
"""
Return a copy of background surface in use.
"""
return self._background.copy()
#------------------------------------------------------------------------------
def getBackgroundReference(self):
"""
Returns a reference to background surface in use.
"""
return self._background
#------------------------------------------------------------------------------
def setBackgroundColor(self, color):
"""
Set the color of the background. color = (R,G,B)
"""
self._background_color = color
#------------------------------------------------------------------------------
def fpsSet(self, n=0):
self._fps = n
#------------------------------------------------------------------------------
def fpsGet(self):
return self._fps
#------------------------------------------------------------------------------
def notifyScreenChange(self):
# check if screen has changed
## if self._screen!=self._getScreen():
# screen has changed, check if new screen is valid
self._screen = self._getScreen()
if self._screen is None:
self._initialized = False
self._init()
self.flip()
# screen ok
#------------------------------------------------------------------------------
def _init(self):
if(pygame.display.get_init()):
if(not self._initialized):
self._screen = pygame.display.get_surface()
if self._screen is None:
raise TypeError, "Could not get a valid screen surface: pygame.display.get_surface()!!"
if 0 == self._background:
self._background = pygame.Surface(self._screen.get_size(), self._screen.get_flags(), self._screen)
self._background.fill(self._background_color)
#self._screen.blit(self._background, (0,0))
self._lostRect.append(self._screen.get_rect())
self._initialized = True
self.render = self._render
self.flip = self._flip
self.flip()
## self.render()
pygame.display.flip()
else:
raise UserWarning, "Renderer.init(): you should initialize the renderer only once!"
else:
raise ReferenceError , "Renderer.init():you must have pygame.display initialized!!"
#------------------------------------------------------------------------------
def _flipInit(self):
if(pygame.display.get_init()):
if(not self._initialized):
self._screen = pygame.display.get_surface()
if self._screen is None:
raise TypeError, "Could not get a valid screen surface: pygame.display.get_surface()!!"
if 0 == self._background:
self._background = pygame.Surface(self._screen.get_size(), self._screen.get_flags(), self._screen)
self._background.fill((255,0,255))
#self._screen.blit(self._background, (0,0))
self._lostRect.append(self._screen.get_rect())
self._initialized = True
self.flip = self._flip
self._flip()
self.render = self._render
else:
raise UserWarning, "Renderer.init(): you should initialize the renderer only once!"
else:
raise ReferenceError , "Renderer.init():you must have pygame.display initialized!!"
#------------------------------------------------------------------------------
def _appendDirtyRect(self, rect):
## self._update.append(rect) # works fine nor optimization
_update = self._update
_update_append = _update.append
_update_remove = _update.remove
toremove = []
toremove_append = toremove.append
_unionR = 0
if -1!=rect.collidelist(_update):
idxlist = rect.collidelistall(_update)
toremove[:] = []
for idx in idxlist: # check if rect is inside a dirty area
# if so nothing to do,not append, not union, etc.
r = _update[idx]
# if not inside:
_unionR = r.union(rect)
if _unionR.width*_unionR.height< rect.width*rect.height+r.width*r.height:
toremove_append(r)
rect = _unionR
_update_append(rect)
for r in toremove:
_update_remove(r)
else:
_update_append(rect)
#------------------------------------------------------------------------------
def _render(self):
# speedups
_blit = self._screen.blit
_background = self._background
_lost = self._lostRect
_spriteOldRect = self._spriteOldRect
self._update = []
_update = self._update
## _update = [] # [pygame.Rect(100,100,200,200)]
_update_append = self._appendDirtyRect
# blit lost rects with background, they are dirty rects on screen
for r in _lost:
_update_append( _blit(_background, r, r) )#### use optimized update algo
_lost[:] = []
_oldRect = 0
_unionR = 0
# find dirty rects on screen and erase dirty sprites from screen(OLD postition!)
# old and new position of dirty sprite are dirty rects on screen
for spr in self._sprites.keys():
_oldRect = _spriteOldRect[spr]
if 0<spr.dirty and 0!=_oldRect and -1<self._sprites[spr]: # TODO perhaps better to check concrete value?
if 1 == spr.dirty:
spr.dirty = 0
_update_append( _blit(_background, _oldRect, _oldRect) )
## if _oldRect != spr.rect:
## _update_append(spr.rect)
_update_append(spr.rect)
## if _oldRect.colliderect(spr):
## _unionR = _oldRect.union(spr)
## if _unionR.w*_unionR.h < 2*spr.area:#_oldRect.w*_oldRect.h:
## _update_append(_unionR)
## else:
## _update_append(spr.rect)
## _update_append(_oldRect)
## #_update_append(_oldRect.union(spr))
## else:
## _update_append(spr.rect)
## _update_append(_oldRect)
# find sprites colliding with dirty rects and blit them
if self._needSortLayers: # only make a sort if needed
self._needSortLayers = False
self._sortedLayers = self._layers.keys()
self._sortedLayers.sort()
for layer in self._sortedLayers:
if -1<layer: # layer smaller than 0 are not visible
for spr in self._layers[ layer ]:
if -1!=spr.rect.collidelist(_update): # intersects with a dirty rect on screen so blit it
_spriteOldRect[spr] = _blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
self._display_update(_update)
self._clock_tick(self._fps)
#------------------------------------------------------------------------------
def _DEBUGrender(self):
print "DEBUGrender"
# speedups
_blit = self._screen.blit
_background = self._background
_lost = self._lostRect
_spriteOldRect = self._spriteOldRect
_update = self._update
_update[:] = [] # [pygame.Rect(100,100,200,200)]
_update_append = self._appendDirtyRect
# debugging vars
_debugLostRects = [] # lost rects
_debugLostRects_append = _debugLostRects.append
_colorLostR = (255,255,0) # yellow
_debugOldRects = [] # old position
_debugOldRects_append = _debugOldRects.append
_colorOldR = (255,216,66) # orange
_debugNewRects = [] # new position
_debugNewRects_append = _debugNewRects.append
_colorNewR = (0,255,0) # green
_debugCollidingRects = [] # colliding
_debugCollidingRects_append = _debugCollidingRects.append
_colorCollidingR = (0,255,255) # blue
# debugging vars_update # update rects
_colorUpdateR = (255,0,0,128) # red
_optimizedUpdate = []
_optimizedUpdate_append = _optimizedUpdate.append
# blit lost rects with background, they are dirty rects on screen
for r in _lost:
_update_append( _blit(_background, r, r) )
_debugLostRects_append(r) #### use optimized update algo
## print "lost: ", r
## print "update: ", _update, "\n"
_lost[:] = []
_oldRect = 0
_optimized = []
# find dirty rects on screen and erase dirty sprites from screen(OLD postition!)
# old and new position of dirty sprite are dirty rects on screen
for spr in self._sprites.keys():
_oldRect = _spriteOldRect[spr]
if 0<spr.dirty and 0!=_oldRect:
if 1 == spr.dirty:
spr.dirty = 0
_blit(_background, _oldRect, _oldRect)
#### from here on use optimized algo
_update_append( _oldRect )
if _oldRect != spr.rect:
_update_append(spr.rect)
## if _oldRect.colliderect(spr):
## _unionR = _oldRect.union(spr)
## if _unionR.w*_unionR.h < 2*spr.area:
## _update_append(_unionR)
## else:
## _update_append(spr.rect)
## _update_append(_oldRect)
## else:
## _update_append(spr.rect)
## _update_append(_oldRect)
_debugOldRects_append(_oldRect)
_debugNewRects_append(spr.rect)
## print "oldRect blit: ", _oldRect
## for r in _update:
# test: screen update area optimiziation
#### optimize here
## newr = spr.rect # _update[-1]
## if -1!=newr.collidelist(_optimized):
## idxlist = newr.collidelistall(_optimized)
## toremove = []
## for idx in idxlist:
## r = _optimized[idx]
## u = r.union(newr)
## if u.width*u.height< newr.width*newr.height+r.width*r.height:
## toremove.append(r)
## newr = u
## _optimized.append(newr)
## for r in toremove:
## _optimized.remove(r)
## else:
## _optimized.append(newr)
#### optimize end
## if _oldRect!=spr.rect: # different position or size
## newr = _oldRect # _update[-1]
## if -1!=newr.collidelist(_optimized):
## idxlist = newr.collidelistall(_optimized)
## toremove = []
## for idx in idxlist:
## r = _optimized[idx]
## u = r.union(newr)
## if u.width*u.height< newr.width*newr.height+r.width*r.height:
## toremove.append(r)
## newr = newr.union(r)
## _optimized.append(newr)
## for r in toremove:
## _optimized.remove(r)
## else:
## _optimized.append(newr)
## print "update: ", _update, "\n"
# find sprites colliding with dirty rects and blit them
if self._needSortLayers: # only make a sort if needed
self._sortedLayers = self._layers.keys()
self._sortedLayers.sort()
self._needSortLayers = False
for layer in self._sortedLayers:#self._layers.keys().sort():
if -1<layer:
for spr in self._layers[ layer ]:
if -1!=spr.rect.collidelist(_update): # intersects with a dirty rect on screen so blit it
_spriteOldRect[spr] = self._screen.blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
_debugCollidingRects_append(_spriteOldRect[spr])
## print "colliding obj, rect: ", spr.__class__.__name__, spr.rect
## print "update: ", _update, "\n"
## print "\nupdate: ", _update, "\n"
#pygame.display.update(_update)
# debugging code
_lostRectsArea = 0
_oldRectsArea = 0
_collidingRectsArea = 0
_newRectsArea = 0
_updateArea = 0
for r in _debugLostRects:
pygame.draw.rect(self._screen,_colorLostR, r, 5) # yellow
_lostRectsArea += r.width*r.height
for r in _debugOldRects:
pygame.draw.rect(self._screen, _colorOldR, r, 4) # orange
_oldRectsArea += r.width*r.height
for r in _debugCollidingRects:
pygame.draw.rect(self._screen, _colorCollidingR, r,3) # blue
_collidingRectsArea += r.width*r.height
for r in _debugNewRects:
pygame.draw.rect(self._screen, _colorNewR, r, 2) # green
_newRectsArea += r.width*r.height
for r in _update:
pygame.draw.rect(self._screen, _colorUpdateR, r,3) # red
_updateArea += r.width*r.height
_optArea = 0
for r in _optimized:
pygame.draw.rect(self._screen, (255,255,255), r, 1)
_optArea += r.width*r.height
print "screen area: \t", self._screen.get_width()*self._screen.get_height()
print "\n"
print "lost rects ", len(_debugLostRects), " area: \t", _lostRectsArea
print "old rects ",len(_debugOldRects)," area: \t", _oldRectsArea
print "colliding rects ",len(_debugCollidingRects)," area:\t", _collidingRectsArea
## print "new rects ",len(_debugNewRects)," area: \t", _newRectsArea
print "\n"
print "screen update rects ",len(_update)," area: \t", _updateArea
print "optimized screen update rects ",len(_optimized)," area: \t", _optArea
print "double blittet area: ", _updateArea-_optArea, " # rects diff: ", len(_update)-len(_optimized)
print "\n"
print "total blitet rects ",len(_debugLostRects)+len(_debugOldRects)+ \
len(_debugCollidingRects)," area: \t", _lostRectsArea+_oldRectsArea+_collidingRectsArea
print "\n"
_pygame_sprite_update_area = 0
for s in self._sprites.keys():
_pygame_sprite_update_area += s.rect.w*s.rect.h*2
print "pygame.sprite would have a screen update area between max ", \
_pygame_sprite_update_area, " and min ", _pygame_sprite_update_area/2
print "and ",len(self._sprites.keys())*2," rects blitet\n\n"
pygame.display.flip() # update entire screen (slow!)
## pygame.display.update(_update)
_blit(self._background, (0,0)) # rebuild entire screen (slow!)
for layer in self._sortedLayers:
for spr in self._layers[layer]:
## if spr._createDirtyRect:
_blit(spr.image, spr)
self._clock_tick(self._fps)
#------------------------------------------------------------------------------
def _flip(self):
_blit = self._screen.blit
_blit(self._background, (0,0))
for layer in self._sortedLayers:
for spr in self._layers[layer]:
_blit(spr.image, spr)
pygame.display.flip()
self._clock_tick(self._fps)
#------------------------------------------------------------------------------
#==============================================================================
# if you import this module mor than one time only one instance can exist!
if 0==_Renderer._instance:
Renderer = _Renderer()
#
# (C) DR0ID 03.2006
#
# you can contact me at: http://www.mypage.bluewin.ch/DR0ID/pygame
#
# or on IRC: irc.freenode.net 6667 #pygame
#
#
import pygame
from pygame.locals import *
import random
import sprite as sprite
import time
# try to import psyco, its not needed to run
##psyco=None
##try:
## import psyco
## if psyco.__version__>=17105136:
## from psyco.classes import __metaclass__
## else:
## print """Note: Psycho version """, psyco.__version__, """ detected, upgrade to psyco 1.5 for better performance. """
##
##except ImportError:
## print """Note: No psyco detected. Install psycho to improve performance."""
__author__ = 'DR0ID'
__version__ = '0.1' # change also top of this file!!
__versionnumber__ = 0,1 # versionnumber seperatly for comparing
__license__ = 'public domain'
__copyright__ = '(c) DR0ID 03.2006'
class Quadrat(sprite.NewSprite):
def __init__(self, register = True):
sprite.NewSprite.__init__(self,(), register)
self.image = pygame.Surface((30,30), pygame.SRCALPHA, 32).convert_alpha()
self.image.fill( (random.randint(0,255),random.randint(0,255),random.randint(0,255),128) )
## self.image.set_alpha(128)
## self.image = self.image.convert_alpha()
self.rect = self.image.get_rect()
self.dirty = 2
self.vx= random.randint(5,20)
self.vy = random.randint(5,20)
self.path_factor = 0.1
self.maxX = (1-self.path_factor)*pygame.display.get_surface().get_width()
self.minX = self.path_factor*pygame.display.get_surface().get_width()
self.maxY = (1-self.path_factor)*pygame.display.get_surface().get_height()
self.minY = self.path_factor*pygame.display.get_surface().get_height()
self.rect.center = (random.randint(300,600),random.randint(300,400))
def update(self):
if self.rect.centerx > self.maxX or self.rect.centerx< self.minX:
self.vx = -self.vx
if self.rect.centery > self.maxY or self.rect.centery< self.minY:
self.vy = -self.vy
self.rect.move_ip(self.vx, self.vy)
class Circle(Quadrat):
def __init__(self):
Quadrat.__init__(self, False)
self.image = pygame.Surface((100,100), pygame.SRCALPHA, 32).convert_alpha()
self.image.fill((0,0,0,0))
pygame.draw.circle(self.image,(random.randint(0,255),random.randint(0,255),random.randint(0,255),128),(50,50) , 50)
## self.image.set_alpha(128)
## self.image.convert_alpha()
self._renderer.add(self)
self.rect = self.image.get_rect()
self.rect.center = (random.randint(300,600),random.randint(300,400))
class Text(sprite.NewSprite):
def __init__(self, pos):
sprite.NewSprite.__init__(self)
self.dirty = 1
self.font = pygame.font.Font(pygame.font.get_default_font(), 24)
self.image = self.font.render(str(0.0), 0, (255,255,255))
self.rect = self.image.get_rect()
self.rect.move_ip(pos)
self.ctr = 0
self.nexttime = 0
def update(self, now, s=''):
self.ctr += 1
if now > self.nexttime:
if s=='':
self.image = self.font.render(("%d FPS" % self.ctr), 0, (255,255,255))
else:
self.image = self.font.render(str(s), 0, (255,255,255))
self.rect = self.image.get_rect(topleft=self.rect.topleft)
self.ctr = 0
self.nexttime = now + 1
self.dirty = 1
#example
#keys:
# s : stepmodus, press any key except s to step one frame furder
# d: change to debug modus
# f: change to slow 2 fps
# alt+enter: toggle fullscreen
def main():
# setup pygame
pygame.init()
screen = pygame.display.set_mode((800,600),1)
#prepare stuff, load images
# convert them it will nearly double your fps (frames per second)
# group for easier handling and add ouer objects
g = pygame.sprite.OrderedUpdates()
## g.add(mObj)
## g.add(mObj2)
for i in range(10):
g.add(Quadrat())
g.add(Circle())
sprite.Renderer.setBackgroundColor((0,0,0))
## fpslist = []
## for n in range(500):
## fpslist.append(100)
background = pygame.Surface(screen.get_size()).convert()
clock = pygame.time.Clock()
text = Text((0,0))
text2 = Text((150,0))
sprite.Renderer.setBackgroundColor((0,0,80))
stepmodus = False
fullscreen = False
while 1:
# eventhandling keys
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
return
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit() # if you run it from console....
return
elif event.key == K_d:
sprite.Renderer.switchDEBUG()
elif event.key == K_f:
if sprite.Renderer.fpsGet()==2:
sprite.Renderer.fpsSet(0)
else:
sprite.Renderer.fpsSet(2)
elif event.key == K_s:
stepmodus = not stepmodus
## pygame.time.wait(1000)
elif event.key == pygame.K_RETURN:
if pygame.key.get_mods() & pygame.KMOD_ALT:
print "toggle fullscreen"
fullscreen = not fullscreen
if fullscreen:
flags = pygame.FULLSCREEN
else:
flags = 0
minbitdepth = 16
bitdepth = 32
size = screen.get_size()
bitdepth = pygame.display.mode_ok(size, flags, bitdepth)
if bitdepth < minbitdepth:
raise Exception("Your system is unable to display %d bit color in an %d x %d window!" % (minbitdepth, size[0], size[1]))
screen = pygame.display.set_mode(size, flags, bitdepth)
sprite.Renderer.notifyScreenChange()
if stepmodus:
pygame.event.clear()
event = pygame.event.wait()
while event.type is not KEYUP:
if event.type == (pygame.constants.KEYDOWN or pygame.constants.KEYUP):
if event.key == pygame.constants.K_s:
stepmodus = not stepmodus
event = pygame.event.wait()
## fpslist.pop(0)
## fpslist.append(clock.get_fps())
text.update( time.time())
text2.update(time.time(),"Hit ESC to stop, ALT-ENTER to toggle fullscreen mode.")
g.update()
sprite.Renderer.render()
clock.tick()
#if __name__ == '__main__': main()
if __name__=="__main__":
main()