Python pygame – Deleting offscreen sprites

Question:

I created a simple 2D game with python 2 and pygame where you have to avoid squares that are moving down. I created this class for the ‘enemy’:

class Enemy(pygame.sprite.Sprite):

  def __init__(self, game):
      self.groups = game.all_sprites
      pygame.sprite.Sprite.__init__(self, self.groups)
      self.game = game
      self.image = pygame.Surface((TILESIZE + 10, TILESIZE + 10))
      self.image.fill(ENEMY_COLOR)
      self.rect = self.image.get_rect()
      self.x = random.uniform(0, WIDTH - TILESIZE)
      self.rect.x = self.x
      self.y = 0

  def update(self):
      if self.rect.colliderect(self.game.player.rect):
          self.game.deaths += 1
          self.game.score = 0
          self.game.run()
      self.rect.y += (self.game.score + 500) / 50

Then, I have a thread that creates an instance of the enemy:

class Create_Enemy(Thread):

  def __init__(self):
      Thread.__init__(self)

  def run(self):
      while True:
          while not game.game_paused:
              time.sleep((game.score + 1) / game.speed)
              Enemy(game)

Then in the draw method i just write self.all_sprites.draw()
The game is an infinite runner, and every frame the enemies move a few pixels down. The problem is that after a bit the game gets laggy since, when the blocks go offscreen, the sprites don’t get deleted.
Is there a way to automatically delete the offscreen instances?

I tried the following but it just deletes all the enemies onscreen.

if self.rect.y >= WIDTH:
    self.rect.kill()

I was thinking I could do something like game.all_sprites.pop(1) (the position 0 is the player) but I couldn’t find a way to archive something similar …

Asked By: anon

||

Answers:

Check the values of self.rect.y, WIDTH, maybe the method kill. It looks like self.rect.y is always greater or equal WIDTH, that’s why it kills them all.

Answered By: postoronnim

The enemies can remove themselfs from the game by calling self.kill() if their rect is no longer inside the screen, e.g.:

def update(self):
    if self.rect.colliderect(self.game.player.rect):
        self.game.restart() # reset score + count deaths

    # we can simply use move_ip here to move the rect
    self.rect.move_ip(0, (self.game.score + 500) / 100)

    # check if we are outside the screen
    if not self.game.screen.get_rect().contains(self.rect):
        self.kill()

Also, instead of the thread, you can use pygame’s event system to spawn your enemies. Here’s a simple, incomplete but runnable example (note the comments):

import random

import pygame

TILESIZE = 32
WIDTH, HEIGHT = 800, 600

# a lot of colors a already defined in pygame
ENEMY_COLOR = pygame.color.THECOLORS['red']
PLAYER_COLOR = pygame.color.THECOLORS['yellow']

# this is the event we'll use for spawning new enemies
SPAWN = pygame.USEREVENT + 1

class Enemy(pygame.sprite.Sprite):

    def __init__(self, game):

        # we can use multiple groups at once. 
        # for now, we actually don't need to
        # but we could do the collision handling with pygame.sprite.groupcollide()
        # to check collisions between the enemies and the playerg group
        pygame.sprite.Sprite.__init__(self, game.enemies, game.all)
        self.game = game
        self.image = pygame.Surface((TILESIZE + 10, TILESIZE + 10))
        self.image.fill(ENEMY_COLOR)

        # we can use named arguments to directly set some values of the rect
        self.rect = self.image.get_rect(x=random.uniform(0, WIDTH - TILESIZE))
        # we dont need self.x and self.y, since we have self.rect already
        # which is used by pygame to get the position of a sprite


    def update(self):
        if self.rect.colliderect(self.game.player.rect):
            self.game.restart() # reset score + count deaths

        # we can simply use move_ip here to move the rect
        self.rect.move_ip(0, (self.game.score + 500) / 100)

        # check if we are outside the screen
        if not self.game.screen.get_rect().contains(self.rect):
            self.kill()

class Player(pygame.sprite.Sprite):

    def __init__(self, game):
        pygame.sprite.Sprite.__init__(self, game.all, game.playerg)
        self.game = game
        self.image = pygame.Surface((TILESIZE, TILESIZE))
        self.image.fill(PLAYER_COLOR)
        self.rect = self.image.get_rect(x=WIDTH/2 - TILESIZE/2, y=HEIGHT-TILESIZE*2)

    def update(self):
        # no nothing for now
        pass


class Game(object):

    def __init__(self):
        # for now, we actually don't need mujtiple groups
        # but we could do the collision handling with pygame.sprite.groupcollide()
        # to check collisions between the enemies and the playerg group
        self.enemies = pygame.sprite.Group()
        self.all = pygame.sprite.Group()
        self.playerg = pygame.sprite.GroupSingle()
        self.running = True
        self.score = 0
        self.deaths = -1
        self.clock = pygame.time.Clock()

    def restart(self):
        # here we set the timer to create the SPAWN event
        # every 1000 - self.score * 2 milliseconds
        pygame.time.set_timer(SPAWN, 1000 - self.score * 2)
        self.score = 0
        self.deaths += 1

    def run(self):
        self.restart()
        self.player = Player(self)
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))

        while self.running:
            for e in pygame.event.get():
                if e.type == pygame.QUIT:
                    self.running = False
                # when the SPAWN event is fired, we create a new enemy
                if e.type == SPAWN:
                    Enemy(self)

            # draw and update everything
            self.screen.fill(pygame.color.THECOLORS['grey'])
            self.all.draw(self.screen)
            self.all.update()
            pygame.display.flip()

            self.clock.tick(40)

if __name__ == '__main__':
    Game().run()
Answered By: sloth
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.