[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
Re: [pygame] sprite engine Top this!
Christopher Arndt schrieb:
Christopher Arndt wrote:
DR0ID wrote:
If you can make this faster, post your results here!
Yes, I can!
Pretty impressive!
I'm wondering which fps you get on your machines. Please tell me so I
can build a little statistic.
~425 Fps max here on Pentium III Mobile 1200MHz with 512MB
Fullscreen mode is maybe 3% slower.
I forgot:
Ubuntu Breezy, Python 2.4.2, pygame 1.7.1, SDL 1.2.7, Xorg 6.8.2
Hi
thank you for your results. As you suggested I have added the max, min
and average fps printed out at exiting the code. I found it strange that
there are some 0 fps counts!! (maybe at startup?)
And also sometimes it seems to drop nearly 0.0 fps in the middle of
running and I do not know why! Can it be because of the garbage
collector? Or other memory related stuff?
~DR0ID
#
# (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
__author__ = 'DR0ID'
__version__ = '0.2' # change also top of this file!!
__versionnumber__ = 0,2 # versionnumber seperatly for comparing
__license__ = 'public domain'
__copyright__ = '(c) DR0ID 05.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 = 0
while 0==self.vx:
self.vx= random.randint(-3,3)
self.vy = 0
while 0==self.vy:
self.vy = random.randint(-3,3)
self.path_factor = 0.01
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 StaticQuadrat(sprite.NewSprite):
def __init__(self, pos, size):
sprite.NewSprite.__init__(self,(), False)
self.image = pygame.Surface(size, pygame.SRCALPHA, 32).convert_alpha()
self.image.fill((random.randint(0,255),random.randint(0,255),random.randint(0,255),128) )
self.rect = pygame.Rect(pos, size)
self.vx = self.vy =0
self.dirty = 1
self._renderer.add(self)
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.image = self.font.render(str("Hit ESC to stop, ALT-ENTER to toggle fullscreen mode."), 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))
self.dirty = 1
self.rect = self.image.get_rect(topleft=self.rect.topleft)
self.ctr = 0
self.nexttime = now + 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,32)
#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(StaticQuadrat((100,300), (100,300)))
for i in range(10):
g.add(Quadrat())
g.add(StaticQuadrat((50,450), (600,100)))
g.add(Circle())
g.add(StaticQuadrat((140,400), (300,300)))
## for i in range(500):
## g.add(StaticQuadrat((random.randint(0,800),random.randint(0,600)), (32,32)))
text = Text((0,0))
text2 = Text((150,0))
sprite.Renderer.setBackgroundColor((0,0,80))
pygame.display.set_caption("press 'h' for help (in console)")
fpslist = []
stepmodus = False
fullscreen = False
clock = pygame.time.Clock()
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:
## print fpslist
max = 0
min = 1000
zero= 0
for r in fpslist:
if r>max:
max = r
if r<min:
if r == 0:
zero +=1
else:
min = r
print "max fps: ", max
print "min fpx: ", min
print "0 fps count:", zero
print "average fps: ", sum(fpslist)/len(fpslist)
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()==20:
sprite.Renderer.fpsSet(0)
else:
sprite.Renderer.fpsSet(20)
elif event.key == K_s:
stepmodus = not stepmodus
elif event.key == K_h:
print "Help:"
print "s toggles stepmode, press any key except s to step"
print "d toggles debugmode"
print "f toggles to 20 fps"
print "alt+enter toggles fullscreen"
print "esc to exit"
print "h for this help"
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.append(clock.get_fps())
clock.tick()
text.update( time.time())
g.update()
sprite.Renderer.render()
#if __name__ == '__main__': main()
if __name__=="__main__":
main()
import pygame
#==============================================================================
class NewSprite(pygame.sprite.Sprite):
#------------------------------------------------------------------------------
def __init__(self, groups=(), register=True):
pygame.sprite.Sprite.__init__(self, groups)
self.dirty = 1
self.rect = pygame.Rect(-99, -99, 0, 0)
self._renderer = Renderer
if register:
self._renderer.add(self)
#------------------------------------------------------------------------------
## def __setattr__(self, name, value):
## pygame.sprite.Sprite.__setattr__(self, name, value)
## if(name=="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._scree_rect = 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!!!"
#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)
#------------------------------------------------------------------------------
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):
self.changeSpriteLayer(sprite, 0)
self._spriteOldRect[sprite] = 0
self._spriteOldRect[sprite] = sprite.rect
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._appendDirtyRect(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:
sprite.dirty = 1
#------------------------------------------------------------------------------
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.convert_alpha()
#------------------------------------------------------------------------------
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
if self._background != 0:
self._background.fill(self._background_color)
self.flip()
#------------------------------------------------------------------------------
def fpsSet(self, n=0):
self._fps = n
#------------------------------------------------------------------------------
def fpsGet(self):
return self._fps
#------------------------------------------------------------------------------
def notifyScreenChange(self):
# check if screen has changed
# screen has changed, check if new screen is valid
self._screen = self._getScreen()
if self._screen is None:
self._initialized = False
self._init()
self._appendDirtyRect(self._screen.get_rect())
# 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).convert_alpha()
self._background.fill(self._background_color)
#self._screen.blit(self._background, (0,0))
self._appendDirtyRect(self._screen.get_rect())
self._initialized = True
self.render = self._render
self.flip = self._flip
self.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._appendDirtyRect(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):
_update = self._update
_unionR = pygame.Rect(rect)
i = _unionR.collidelist(_update)
while -1<i:
_unionR.union_ip(_update[i])
del _update[i]
i = _unionR.collidelist(_update)
_update.append(_unionR)
#------------------------------------------------------------------------------
def _render(self):
# speedups
_blit = self._screen.blit
_background = self._background
_spriteOldRect = self._spriteOldRect
_update = self._update
_update_append = self._appendDirtyRect
# 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():
if 0<spr.dirty and -1<self._sprites[spr]: # TODO perhaps better to check concrete value?
_update_append(_spriteOldRect[spr])# _oldRect)
_update_append(spr.rect)
# 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 r in _update:
_blit(_background, r, r)
for layer in self._sortedLayers:
if -1<layer: # layer smaller than 0 are not visible
for spr in self._layers[ layer ]:
if 0==spr.dirty:
_spr_rect = spr.rect
for idx in _spr_rect.collidelistall(_update):
# clip
self._screen.set_clip(_update[idx])
_blit(spr.image, _spr_rect)
self._screen.set_clip()
else:
_spriteOldRect[spr] = _blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
if 1 == spr.dirty:
spr.dirty = 0
self._display_update(_update)
self._clock_tick(self._fps)
_update[:] = []
#------------------------------------------------------------------------------
def _DEBUGrender(self):
print "DEBUGrender"
# speedups
_blit = self._screen.blit
_background = self._background
_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
_oldRect = 0
_optimized = []
_blit(self._background, (0,0)) # rebuild entire screen (slow!)
for layer in self._sortedLayers:
for spr in self._layers[layer]:
_blit(spr.image, spr)
# 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:
_update_append( _oldRect )
_update_append(spr.rect)
_debugOldRects_append(_oldRect)
_debugNewRects_append(spr.rect)
## 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 0==spr.dirty:
_spr_rect = spr.rect
for idx in _spr_rect.collidelistall(_update):
# clip
clip = _spr_rect.clip(_update[idx])
## _debugCollidingRects_append( _blit(spr.image, clip, pygame.Rect(clip[0]-_spr_rect[0], clip[1]-_spr_rect[1], clip[2], clip[3])) )
_debugCollidingRects_append(clip)
else:
_spriteOldRect[spr] = spr.rect #_blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
if 1 == spr.dirty:
spr.dirty = 0
## print "update: ", _update, "\n"
## print "\nupdate: ", _update, "\n"
#pygame.display.update(_update)
# debugging code
_oldRectsArea = 0
_collidingRectsArea = 0
_newRectsArea = 0
_updateArea = 0
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,1) # red
_updateArea += r.width*r.height
_optArea = 0
print "screen area: \t", self._screen.get_width()*self._screen.get_height()
print "\n"
print "old rects ",len(_debugOldRects)," area: \t", _oldRectsArea
print "colliding rects ",len(_debugCollidingRects)," area:\t", _collidingRectsArea
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(_debugOldRects)+ \
len(_debugCollidingRects)," area: \t", _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!)
self._clock_tick(self._fps)
#------------------------------------------------------------------------------
def _flip(self):
_blit = self._screen.blit
_blit(self._background, (0,0))
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:
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()