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 ...
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()