image = pygame.image.load("01_image.png")
(if you have your image in a subdirectory then you should use
os.path.join() to join your path
together because if you do otherwise it could cause problems on different platforms)
screen.blit(image, (50,50))What it does is copy the pixels of the image surface to the screen surface. The position (50,50) is the top left corner of the image. If you try that now you will get a black screen. It is because we have forgot to update the screen. The full screen update is done using pygame.display.flip().
pygame.display.flip()
screen.fill((r,g,b))After that you blit your image over it as shown in the first section.
screen.blit(bgd_image, (0,0))We blit it at the top left corner of the screen that is (0,0). The background image bgd_image should have the same size as the screen (or bigger but the clipped parts will not be seen).
image.set_colorkey((255,0,255))You get the source , but try it first by your own.
image.set_alpha(128)This will make the image half transparent using the per surface alpha (not per pixel alpha). Here I use a colorkey too to make the pink border transparent. Well to avoid some problems using alpha and colorkey I have looked it up in the sdl documentation what combinations will work. Its because not any combination is possible and you might then wonder why it does not do what you want. In the documentation is said:
The per-surface alpha value of 128 is considered a special case and is optimized, so it's much faster than other per-surface values.
| RGBA->RGB with SDL_SRCALPHA | The source is alpha-blended with the destination, using the alpha channel. SDL_SRCCOLORKEY and the per-surface alpha are ignored. |
| RGBA->RGB without SDL_SRCALPHA | The RGB data is copied from the source. The source alpha channel and the per-surface alpha value are ignored. If SDL_SRCCOLORKEY is set, only the pixels not matching the colorkey value are copied. |
| RGB->RGBA with SDL_SRCALPHA | The source is alpha-blended with the destination using the per-surface alpha value. If SDL_SRCCOLORKEY is set, only the pixels not matching the colorkey value are copied. The alpha channel of the copied pixels is set to opaque. |
| RGB->RGBA without SDL_SRCALPHA | The RGB data is copied from the source and the alpha value of the copied pixels is set to opaque. If SDL_SRCCOLORKEY is set, only the pixels not matching the colorkey value are copied. |
| RGBA->RGBA with SDL_SRCALPHA | The source is alpha-blended with the destination using the source alpha channel. The alpha channel in the destination surface is left untouched. SDL_SRCCOLORKEY is ignored. |
| RGBA->RGBA without SDL_SRCALPHA | The RGBA data is copied to the destination surface. If SDL_SRCCOLORKEY is set, only the pixels not matching the colorkey value are copied. |
| RGB->RGB with SDL_SRCALPHA | The source is alpha-blended with the destination using the per-surface alpha value. If SDL_SRCCOLORKEY is set, only the pixels not matching the colorkey value are copied. |
| RGB->RGB without SDL_SRCALPHA | The RGB data is copied from the source. If SDL_SRCCOLORKEY is set, only the pixels not matching the colorkey value are copied. |
# define the position of the smiley
xpos = 50
ypos = 50
# how many pixels we move our smiley each frame
step_x = 10
step_y = 10
What we have to do next is to change the position by the step size each time it loops through the
main loop. So we write in the main loop something like this:
# check if the smiley is still on screen, if not change direction
if xpos>screen_width-64 or xpos<0:
step_x = -step_x
if ypos>screen_height-64 or ypos<0:
step_y = -step_y
# update the position of the smiley
xpos += step_x # move it to the right
ypos += step_y # move it down
The two if-statements are there to keep the smiley in the screen. Perhaps you wonder why there is an
-64 in xpos>screen_width-64. Well, remember the blit position is always the top left corner of the image and what we have
actually saved is the position of the top left corner into xpos/ypos. If we would just check xpos>screen_width
then the smiley would slide completely out of the screen before it will change direction and come back.
If you have coded that and try to run it (try it!)
you will see nothing happened on screen! Why? Well, we forgot to blit the smiley at the new position!
So let's do it, add the following lines in the main loop, after the position update:
# now blit the smiley on screen
screen.blit(image, (xpos, ypos))
# and update the screen (don't forget that!)
pygame.display.flip()
But what is that? What have we done wrong? Well I think we forgot to erase the current screen. Because
now there is an image of the smiley at each position it once was. So let us fix that. Before blitting
the smiley we have to erase the screen. How to do it? Just blit the background over anything
on the screen.
# first erase the screen
#(just blit the background over anything on screen)
screen.blit(bgd_image, (0,0))
# now blit the smiley on screen
screen.blit(image, (xpos, ypos))
# and update the screen (don't forget that!)
pygame.display.flip()
So what have we learned? For a moving or somehow changing image, the basic algorithm is as follows:
First do it right, then optimize!or in other words:
First you must understand the problem exactly before you can optimize the code.
# draw method from pygame.sprites.RenderUpdates
def draw(self, surface):
spritedict = self.spritedict # {sprite:old_rect}
surface_blit = surface.blit # speed up
dirty = self.lostsprites # dirty rects (from removed sprites)
self.lostsprites = []
dirty_append = dirty.append # speed up
for s in self.sprites():
r = spritedict[s] # get the old_rect
newrect = surface_blit(s.image, s.rect)# draw it
if r is 0: # first time the old_rect is 0
dirty_append(newrect) # add the rect from the blit, nothing else to do
else:
if newrect.colliderect(r): # if the old_rect and the newrect overlap, case 3
dirty_append(newrect.union(r)) # append the union of these two rects
else:
dirty_append(newrect) # not overlapping so append both, newrect and
dirty_append(r) # old_rect to the dirty list, case 5
spritedict[s] = newrect # replace the old one with the new one
return dirty # return the dirty rects list
Is it the best we can do? I think not. If you have many moving sprites which overlap
in their movement, this approach will still update many areas twice. And it has one more major disadvantage:
it has to clear and redraw every sprite (otherwise is would not work, see
dirty flags
). But that leads us to the next
section: dirty areas union.
_update = [] #list that contains the (or already added) dirty rects
_union_rect = _rect(spr.rect) # copy the rect because it will be modified
_union_rect_collidelist = _union_rect.collidelist # speed up
_union_rect_union_ip = _union_rect.union_ip # speed up
i = _union_rect_collidelist(_update) # check for overlapping areas
while -1 < i: # as long a overlapping area is found
_union_rect_union_ip(_update[i]) # union the two rects
del _update[i] # remove the one from the list
i = _union_rect_collidelist(_update) # check again for overlapping ares
_update.append(_union_rect.clip(_clip)) # at the end add the new found rect to the list
# do the same for the old rect (old position of the sprite)
for spr in sprites:
if spr.dirty:
# do the drawing, only for dirty sprites and reset dirty flag
So only the sprites that are marked with "dirty == 1" are drawn and the flag gets reset (important).
But wait, what if a sprite
intersects with another one? Even worse, what if that sprite is transparent, dirty and intersects
with other sprites? Yes you guessed right, these intersecting sprites need to be redrawn too!! That is
the problem I was referring in the other sections before. Any sprite in a dirty area has to be redrawn,
independent how you have found the dirty area.
It is because you erase the dirty area by filling it using a background and then you will
redraw anything in the cleaned area.
So now the algorithm changes to:
# find the dirty areas first
dirty_areas = []
for spr in sprites:
if spr.dirty:
# add this sprite area to the dirty areas list
dirty_areas.append(spr.rect)
# draw the sprites
for dirty_area in dirty_areas:
# do the drawing of the intersecting sprites
for spr in sprites:
if dirty_area.collide_rect(spr.rect):
if spr.dirty:
# just draw the sprite, because the entire sprite is in the dirty area
# reset the flag
else:
# find intersecting part and draw only this part of the sprite
Well this code can be optimized using the colliding function from the pygame.Rect. I will put a snippet here
of how it is done in the FastRenderGroup (only the drawing part, for how it finds the dirty areas see
dirty areas union):
for spr in _sprites:
if 1 > spr.dirty: # sprite not dirty, blit only the
_spr_rect = spr.rect # intersecting part
_spr_rect_clip = _spr_rect.clip
for idx in _spr_rect.collidelistall(_update): # find all intersecting dirty areas
# clip
clip = _spr_rect_clip(_update[idx]) # find the intersecting part
_surf_blit(spr.image, clip, \ # and draw only that part
(clip[0]-_spr_rect[0], \
clip[1]-_spr_rect[1], \
clip[2], \
clip[3]), spr.blendmode)
else: # dirty sprite # if dirty draw the entire sprite
_old_rect[spr] = _surf_blit(spr.image, spr.rect, \
None, spr.blendmode)
if spr.dirty == 1: # and reset the flag (well here it is
spr.dirty = 0 # special because only if dirty has
# value 1 it will be reset (2 not)
As you have seen, optimization is sometimes good, sometimes bad. Since I want to write my things
in pure python, that is all you can do. If you need even more speed, you always can consider to write
a C extension for python. Before you do that try psyco. If you decide to write an extension, then there
are some tools that might help (I have not tried one yet): swig, pyrex, boost.