I have this main loop that updates an image at a point:
The loop:
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
self.spaceship.buttons()
self.spaceship.update(self.screen)
pygame.display.update()
Where self.spaceship.buttons()
is:
def buttons(self):
key = pygame.key.get_pressed()
dist = 1
if key[pygame.K_LEFT]:
self.x -= dist
elif key[pygame.K_RIGHT]:
self.x += dist
And self.spaceship.update(self.screen)
is defined:
def update(self, surface):
surface.blit(self.image, (self.x, self.y))
After the main loop updates the key presses and "blits" the image onto the screen, it is supposed to update the display (pygame.display.update()
), but the screen doesn't get cleared. This is what the display looks like, before and after moving:
This is what the image looks like before the blit at a different position
This is what the image looks like after the blit is at a different position (after moving)
Why does pygame.display.update()
not clear the previous frames of the image?
It's quite simple, pygame doesn't do this for you.
pygame.display.update()
updates the entire scene to show the user the new content you've "blipped". It doesn't erase the past.
It can also be used to only update certain areas where you know you've done some updates (to speed up stuff).
Either, you do a flip()
to update the entire graphical buffer to only contain the new content that you've blipped since the last flip (note that you'll loose everything not in this cycle):
while True:
...
pygame.display.flip()
Or, you simply "draw" a black box before calling update.
This is however, much more taxing.
while True:
...
self.screen.fill((0,0,0))
pygame.display.update()
My recommendation is that you either use draw_rect and draw a black rectangle over the previous area where the spaceship was, or simply use flip()
to swap to a new buffer page in the GPU. Doing a flip()
is extremely fast in most implementations.
I come from a Pyglet background, so the speed performance of flip()
, update()
and draw_rect()
might work differently in Pygame, but the fundamentals should be the same and my answer should be valid, if not please point this out.
Pygame will only draw what you tell it to. If you only tell it to draw your spaceship in a new position, it will do just that, and not touch any of the other things you have previously drawn. This is useful, because it allows you to only update those portions of the screen that have changed since your last update, without having to draw everything everytime.
In order to have the spaceship move, you need to draw the background (in your case just black) over the position of the old spaceship, move the spaceship to its new position and then redraw the spaceship at this new position.
If your spaceship is a pygame.sprite.Sprite
(meaning it inherts from pygame.sprite.Sprite
), you can use pygame.sprite.Group
to make this whole process easier, by using pygame.sprite.Group.clear(screen,background)
to draw the background over all Sprites and then use pygame.sprite.Group.draw(screen)
to redraw the Sprites onto the screen once they have been moved. This is especially useful once you have to keep track of more than one moving object.
Example using your code snippet:
sprites = pygame.sprite.Group(self.spaceship)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
sprites.clear(self.screen,clear_callback)
self.spaceship.buttons()
sprites.draw(self.screen)
pygame.display.update()
With this callback function (slightly modified from the documentation):
def clear_callback(surf, rect):
color = (0, 0, 0)
surf.fill(color, rect)
This will only redraw the parts of your screen that have been updated, making redrawing very efficient. In order to see this, simply change the color in the callback function to something like red. You will see the red background only appear where the display updated. Every part of the screen that remains black has not been touched.
Instead of the callback function, you could also make a surface the same size as your screen, fill it with black and use this background surface as your second parameter for sprites.clear()
.
bg = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
bg.fill((0,0,0))
...
while True:
...
sprites.clear(self.screen,bg)
...
Also note, if your game gets more complex and you have to handle more Sprites, you might want to redefine your spaceship.update()
function. Usually you would rename spaceship.buttons()
to spaceship.update()
, which would only move the position of the spaceship, and then use the Spritegroup to take care of drawing (instead of having the spaceship draw itself, as in your current spaceship.update()
function). This would allow you to use pygame.sprite.Group.update()
function to call update on all sprites in the group, making the drawing portion of the update loop a simple call of
sprites.clear(screen,clear_callback)
sprites.update()
sprites.draw(screen)