I am trying to create a sprite class where the user can define and add any number of sprites on to the screen from a random location using this tutorial. However, when I try to run my current program it puts the error
AttributeError: type object 'Sprite' has no attribute 'sprite'
But I dont understand why, all the logic seems correct.
Any suggestions?
Heres my code:
import pygame, sys, random
pygame.init()
black = (0, 0, 0)
image = pygame.image.load("resources/images/img.png")
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 400
sprite_width = 5
sprite_height = 5
sprite_count = 5
# no changes here when using sprite groups
class Sprite(pygame.sprite.Sprite):
def __init__(self, Image, pos):
pygame.sprite.Sprite.__init__(self)
self.image =pygame.image.load("resources/images/img.png")
self.rect = self.image.get_rect()
self.rect.topleft = pos
self.pygame.display.set_caption('Sprite Group Example')
self.clock = pygame.time.Clock()
# this is a how sprite group is created
self.sprite = pygame.sprite.Group()
def update(self):
self.rect.x += 1
self.rect.y += 2
if self.rect.y > SCREEN_HEIGHT:
self.rect.y = -1 * sprtie_height
if self.rect.x > SCREEN_WIDTH:
self.rect.x = -1 * sprite_width
#classmethod
def sprites(self):
for i in range(actor_count):
tmp_x = random.randrange(0, SCREEN_WIDTH)
tmp_y = random.randrange(0, SCREEN_HEIGHT)
# all you have to do is add new sprites to the sprite group
self.sprite.add(Sprite(image, [tmp_x, tmp_y]))
#classmethod
def game_loop(self):
screen = pygame.display.set_mode([640, 400])
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(black)
# to update or blitting just call the groups update or draw
# notice there is no for loop
# this will automatically call the individual sprites update method
self.sprite.update()
self.sprite.draw(screen)
self.pygame.display.update()
self.clock.tick(20)
Sprite.sprites()
Sprite.game_loop()
You define class method (#classmethod) but self.sprite exists only in object/instance not in class - it is created in __init__ when you crate new object/instance.
Remove #classmethod to have object/instance method and have no problem with self.sprite.
Or create sprite outside __init__ to get class attribute
class Sprite(pygame.sprite.Sprite):
sprite = pygame.sprite.Group()
def __init__(self, Image, pos)
# instance variable - value copied from class variable
print self.sprite
# class variable
print self.__class__.sprite
#classmethod
def sprites(self):
# instance variable not exists
# class variable
print self.sprite
Related
I am trying to create a stream of raindrops to give a sense of rain using the code below.
Raindrop is used to model the raindrop. It has an update method() to increase the y coordinate so that it will look like its is dropping on the screen when I call this method.
from pygame.sprite import Sprite
class Raindrop(Sprite):
"""A class to represent a single raindrop."""
def __init__(self, rain_game):
"""Initialize the raindrop and set its starting position."""
super().__init__()
self.screen = rain_game.screen
self.settings = rain_game.settings
# Load the raindrop image and set its rect attribute
self.image = pygame.image.load('raindrop.bmp')
self.rect = self.image.get_rect()
#Start each raindrop near the top left of the screen.
self.rect.x = self.rect.width
self.rect.y = self.rect.height
#Store the raindrop`s exact horizontal and vertical positions.
self.x = self.rect.x
self.y = self.rect.y
def update(self):
"""Shift raindrops to one level below"""
self.y += self.settings.rain_speed
self.rect.y = self.y
class Settings:
"""A Class to store all settings for a Sky Full of Stars."""
def __init__(self):
"""Initialize the game`s settings."""
# Screen setiings
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (255, 255, 255)
self.rain_speed = 1.0
I have a problem with the remove_add_raindrops method in the below code.
It works as expected but only for 4 rows of raindrops in my laptop. After 4 rows this code stops to print any raindrops to the screen. So I see only 8 rows of raindrops dropping from top to the bottom of my screen.
import sys
import pygame
from settings import Settings
from raindrop import Raindrop
class Raining():
"""Overall class to display a sky with raindrops."""
def __init__(self):
"""Initialize the game, and create game resources."""
pygame.init()
self.settings = Settings()
self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
self.settings.screen_width = self.screen.get_rect().width
self.settings.screen_height = self.screen.get_rect().height
pygame.display.set_caption("It is raining today")
self.raindrops = pygame.sprite.Group()
self._create_raindrops()
def _create_raindrop(self, raindrop_number, row_number):
"""Create a raindrop and place it in the row."""
raindrop = Raindrop(self)
raindrop_width, raindrop_height = raindrop.rect.size
raindrop.x = raindrop_width + 2 * raindrop_width * raindrop_number
raindrop.rect.x = raindrop.x
raindrop.y = raindrop_height + 2 * raindrop_height * row_number
raindrop.rect.y = raindrop.y
self.raindrops.add(raindrop)
def _create_raindrops(self):
"""Create a full screen fo raindrops."""
# Create a raindrop and find the number of raindrops in a row.
# Spacing between each raindrop is equal to one raindrop width.
raindrop = Raindrop(self)
raindrop_width, raindrop_height = raindrop.rect.size
available_space_x = self.settings.screen_width - (2 * raindrop_width)
number_raindrops_x = available_space_x // (2 * raindrop_width)
# Determine the number of rows of raindrops that fit on the screen.
available_space_y = (self.settings.screen_height - 2 * raindrop_height)
number_rows = available_space_y // (2 * raindrop_height)
# Create a full screen of raindrops.
for row_number in range(number_rows):
for raindrop_number in range (number_raindrops_x):
self._create_raindrop(raindrop_number, row_number)
def _remove_add_raindrops(self):
"""
Get rid of old raindrops.
Then update their y coordinate and append to the raindrops sprite group
"""
for raindrop in self.raindrops.copy():
if raindrop.rect.y >= self.settings.screen_height:
self.raindrops.remove(raindrop)
raindrop.rect.y -= self.settings.screen_height
self.raindrops.add(raindrop)
def run_game(self):
"""Start the main loop for the game."""
while True:
self._check_events()
self._update_raindrops()
self._remove_add_raindrops()
self._update_screen()
print(f"Nr of remaining raindrops: {len(self.raindrops)}")
#Make the most recently drawn screen visible.
pygame.display.flip()
def _check_events(self):
"""Respond to keypresses and mouse events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys-exit()
elif event.type == pygame.KEYDOWN:
self._check_keydown_events(event)
def _check_keydown_events(self, event):
"""Respond to keypresses."""
if event.key == pygame.K_q:
sys.exit()
def _update_raindrops(self):
"""Update the positions of all raindrops."""
self.raindrops.update()
def _update_screen(self):
"""Update images on the screen, and flip to the new screen."""
self.screen.fill(self.settings.bg_color)
self.raindrops.draw(self.screen)
if __name__ == '__main__':
# Make a game instance, and run the game.
rain_game = Raining()
rain_game.run_game()
I also printed the number of raindrops in the list and it is always fixed at 40. I suppose that means I keep adding new raindrops with a new y coordinate after I delete the ones that slips below the screen.
Any hints?
Update:
I removed the _remove_add_raindrops method and re-wrote the update method under the raindrop class like this:
def update(self):
"""Shift raindrops to one level below"""
self.y += self.settings.rain_speed
self.rect.y = self.y
if self.rect.top >= self.screen.get_rect().bottom:
self.rect.y -= self.settings.screen_height
It is still the same behavior.
_remove_add_raindrops runs
raindrop.rect.y -= self.settings.screen_height
to change the y attribute of a Raindrop's rect.
But that change is immediately overwritten when Raindrop's update function is called:
def update(self):
"""Shift raindrops to one level below"""
self.y += self.settings.rain_speed
self.rect.y = self.y
because it gets changed again to self.y.
Just remove _remove_add_raindrops (removing and immediately readding a Raindrop to self.raindrops makes no sense anyway) and change your Raindrop class something like this:
class Raindrop(Sprite):
"""A class to represent a single raindrop."""
def __init__(self, rain_game):
"""Initialize the raindrop and set its starting position."""
super().__init__()
self.screen = rain_game.screen
self.settings = rain_game.settings
# Load the raindrop image and set its rect attribute
self.image = pygame.image.load('raindrop.bmp')
self.rect = self.image.get_rect()
#Start each raindrop near the top left of the screen.
self.rect.x = self.rect.width
self.rect.y = self.rect.height
def update(self):
"""Shift raindrops to one level below"""
self.rect.move_ip(0, self.settings.rain_speed)
if not pygame.display.get_surface().get_rect().bottom > self.rect.top:
self.rect.bottom = 0
so the Raindrop class can check itself if it is going out of screen. If you want to store the position of a Sprite using floats instead of integers, I recommend using the Vector2 class to do so instead of single attributes, because this makes it easier to move your sprites diagonally in the long run.
The problem was that self.y was increasing indefinitely since I was not re-setting it anywhere in the original code or in the updated code.
def update(self):
"""Shift raindrops to one level below"""
self.y += self.settings.rain_speed
self.rect.y = self.y
if self.rect.top >= self.screen.get_rect().bottom:
self.rect.y -= self.settings.screen_height
Change of last line above from "self.rect.y" to "self.y" solved the problem.
I've recently implemented an enemy that shoots at regular intervals at a designated point on the screen. However, upon trying to make this point the player rect, it refused to work. As the enemy class is defined in the rooms module which is then defined in the main game module, I am not quite certain on how to call the player rect in the enemy module.
Working code as follows:
Game module
import pygame
from constants import *
from player import Player
from pygame.math import Vector2
from enemy import *
from Rooms import Room0
pygame.init()
screen_rect = pygame.display.set_mode([500, 500])
pygame.display.set_caption('Labyrinth')
all_sprites_list = pygame.sprite.Group()
projectiles = pygame.sprite.Group()
enemy_sprites = pygame.sprite.Group()
# Assign rooms
rooms = []
room = Room0()
rooms.append(room)
current_room_no = 0
current_room = rooms[current_room_no]
# Spawn player
player = Player(50, 50)
all_sprites_list.add(player)
clock = pygame.time.Clock()
done = False
# ----- Event Loop
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# ----- Game Logic
all_sprites_list.update()
current_room.projectiles.update()
current_room.enemy_sprites.update()
screen_rect.fill(GREEN)
all_sprites_list.draw(screen_rect)
current_room.projectiles.draw(screen_rect)
current_room.enemy_sprites.draw(screen_rect)
pygame.display.flip()
clock.tick(60)
pygame.quit()
Rooms module
import pygame
from enemy import Enemy
import Projectile
from pygame.math import Vector2
class Room(object):
enemy_sprites = None
projectiles = None
def __init__(self):
self.enemy_sprites = pygame.sprite.Group()
self.projectiles = pygame.sprite.Group()
class Room0(Room):
def __init__(self):
super().__init__()
enemy = Enemy(380, 280, self.projectiles)
self.enemy_sprites.add(enemy)
Player module
from constants import *
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface([15, 15])
self.image.fill(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
Enemy module
from constants import *
import pygame
from Projectile import Bullet
from pygame.math import Vector2
target = Vector2(400, 400)
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y, projectiles):
super().__init__()
self.image = pygame.Surface([10, 10])
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.previous_time = pygame.time.get_ticks()
self.shoot_delay = 1000
self.speed = 12
self.projectiles = projectiles
def update(self):
now = pygame.time.get_ticks()
if now - self.previous_time > self.shoot_delay:
self.previous_time = now
bullet = Bullet(self.rect.x, self.rect.y, target)
self.projectiles.add(bullet)
Projectile module
import pygame
from constants import *
from pygame.math import Vector2
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, target):
super().__init__()
self.image = pygame.Surface((10, 10))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.position = Vector2(self.rect.x, self.rect.y)
direction = target - self.position
radius, angle = direction.as_polar()
self.image = pygame.transform.rotozoom(self.image, -angle, 1)
self.velocity = direction.normalize() * 11
def update(self):
self.position += self.velocity
self.rect.center = self.position
There's an answer you want, and an answer you need. Respectively:
SomeModule.py:
from game import player # imports the Player *instance* you've created in the main module
some_func(player) # as argument
player.another_func # method access
Now, that's the way you'd normally access that kind of stuff, and this would be perfectly fine. In this case though:
a) You'll spawn a whole new game loop, because you put the game setup at module scope rather than in some function or, at very least, directly under an if __name__ == '__main__'. Importing a module executes all the code in the module scope.
b) The very fact you have to import a non-singleton instance, directly, is a code smell - what the very existence of this problem should signal to you is that you likely have a place where your bits of code must be able talk to each other, but you have nothing unambiguously responsible for mediating that process.
So, to address the second part of my promised answer: you should not let this problem occur in the first place - build something dedicated to managing players and enemies, import just the class definitions, then instance and interface between them in the manager.
I have created two simple sprites in PyGame and one of them is an Umbrella, the other one is a rain drop.
The Raindrops are added into a sprite group called all_sprites. The Umbrella sprite has its own group called Umbrella_sprite
The raindrops are "falling" from top of the screen and if one of them touches the umbrella / collides with it.. the raindrop is supposed to be deleted. BUT instead of that specific raindrops all other are affected by this.
main file (rain.py)
#!/usr/bin/python
VERSION = "0.1"
import os, sys, raindrop
from os import path
try:
import pygame
from pygame.locals import *
except ImportError, err:
print 'Could not load module %s' % (err)
sys.exit(2)
# main variables
WIDTH, HEIGHT, FPS = 300, 300, 30
# initialize game
pygame.init()
screen = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Rain and Rain")
# background
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((40,44,52))
# blitting
screen.blit(background,(0,0))
pygame.display.flip()
# clock for FPS settings
clock = pygame.time.Clock()
def main():
all_sprites = pygame.sprite.Group()
umbrella_sprite = pygame.sprite.Group()
# a function to create new drops
def newDrop():
nd = raindrop.Raindrop()
all_sprites.add(nd)
# creating 10 rain drops
for x in range(0,9): newDrop()
# variable for main loop
running = True
# init umbrella
umb = raindrop.Umbrella()
# all_sprites.add(umb)
umbrella_sprite.add(umb)
# event loop
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for enemy in all_sprites:
gets_hit = pygame.sprite.spritecollideany(umb, all_sprites)
if gets_hit:
all_sprites.remove(enemy)
screen.blit(background,(100,100))
# clear
all_sprites.clear(screen,background)
umbrella_sprite.clear(screen,background)
# update
all_sprites.update()
umbrella_sprite.update()
# draw
all_sprites.draw(screen)
umbrella_sprite.draw(screen)
# flip the table
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
main()
raindrop.py ( Raindrop() & Umbrella() )
import pygame
from pygame.locals import *
from os import path
from random import randint
from rain import HEIGHT, WIDTH
img_dir = path.join(path.dirname(__file__), 'img')
class Raindrop(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.width = randint(32, 64)
self.height = self.width + 33
self.image = pygame.image.load(path.join(img_dir, "raindrop.png")).convert_alpha()
self.image = pygame.transform.scale(self.image, (self.width, self.height))
self.speedy = randint(1, 15)
self.rect = self.image.get_rect()
self.rect.x = randint(0, 290)
self.rect.y = -self.height
def reset(self):
self.rect.y = -self.height
def update(self):
self.rect.y += self.speedy
if self.rect.y >= HEIGHT:
self.rect.y = -self.height
self.rect.x = randint(0, 290)
class Umbrella(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.width = 50
self.height = 50
self.image = pygame.image.load(path.join(img_dir,"umbrella.png")).convert_alpha()
self.image = pygame.transform.scale(self.image, (self.width, self.height))
self.speedx = 10
self.rect = self.image.get_rect()
self.rect.x = (WIDTH/2) - self.width
self.rect.y = (0.7 * HEIGHT)
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and self.rect.x > 0:
self.rect.x -= self.speedx
elif keys[pygame.K_RIGHT] and self.rect.x < (WIDTH - self.width):
self.rect.x += self.speedx
This is your problem:
for enemy in all_sprites:
gets_hit = pygame.sprite.spritecollideany(umb, all_sprites)
if gets_hit:
all_sprites.remove(enemy)
You're looping through the group, and if any sprite collides, deleting all of them.
You don't need to loop through the group - the collision functions take care of that. You just need to use the spritecollide function, which compares a sprite versus a group. That function will return a list of collisions, as well as using the DOKILL flag to delete them automatically:
gets_hit = pygame.sprite.spritecollide(umb, all_sprites, True)
spritecollideany checks if the sprite collides with any sprite in the group and returns this sprite, so gets_hit is a trueish value as long as the collided sprite in the group is not removed and the if gets_hit: block gets executed. That means the code in the for loop simply keeps deleting every sprite in the group that appears before the collided sprite is reached and removed. A simple fix would be to check if the hit sprite is the enemy: if enemy == gets_hit:, but the code would still be inefficient, because spritecollideany has to loop over the all_sprites group again and again inside of the for loop.
I recommend to use spritecollide instead of spritecollideany as well, since it's more efficient and just one line of code.
I am following along with a tutorial on how to detect collisions between the class Player and a sprite group MudballGroup. When setting up the collision in the statement pg.sprite.spritecollide(Player, mudballGroup, False) I get the error type object 'Player' has no attribute 'rect'. I have code from my Player sprite here that shows rect to be defined in the following statement: self.rect = self.image.get_rect(). I don't know why I am getting this error. Please if someone could help.
class Player(pg.sprite.Sprite):
def __init__(self, game):
pg.sprite.Sprite.__init__(self)
self.game = game
self.walking = False
self.jumping = False
self.current_frame = 0
self.last_update = 0
self.load_images()
self.image = self.girl_standing[0]
#Isn't this my rect attribute for Player?
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
self.clock = pg.time.Clock()
def shoot(self):
mudball = MUDBALL(self.rect.centerx, self.rect.centery)
print("SHOOT function")
self.game.all_sprites.add(mudball)
mudballGroup = pg.sprite.Group()
mudballGroup.add(mudball)
# Me attempting collision
hits = pg.sprite.spritecollide(self.player, mudballGroup, False)
if hits:
print("HITS!!!!!!!!", hits)
def hailing(self):
jets = JETS()
print("FLYOVER")
self.game.all_sprites.add(jets)
jetsGroup = pg.sprite.Group()
jetsGroup.add(jets)
class MUDBALL(pg.sprite.Sprite):
def __init__(self, x, y):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load("SLIMEballGREEN.png")
# self.mudballGroup = pg.sprite.Group()
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedx = -30
def update(self):
self.rect.x += self.speedx
#kill if moves off screen
if self.rect.centerx < 0:
self.kill()
class JETS(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load("JETSscaled.png")
#self.jetsGroup = pg.sprite.Group()
self.rect = self.image.get_rect()
self.rect.x = 1366
self.rect.y = 0
self.speedx = -70
def update(self):
self.rect.x += self.speedx
#kill if moves off screen
if self.rect.x + 848 < 0:
self.kill()
You're using the Player class for the collision detection, but you have to use an instance of this class instead.
# Player is the class, but spritecollide needs an instance.
hits = pg.sprite.spritecollide(Player, mudballGroup, False)
To create an instance of the Player class, just write:
# Don't forget to pass the game instance to the `Player`s __init__ method.
player = Player(game)
It also looks odd to me that you define the mudballGroup inside of the shoot method. That means the group will only contain one mudball, but then you could also just check if the rects of the player and mudball collide: player.rect.colliderect(mudball.rect) instead of spritecollide. However, if you want multiple mudballs you need to store the mudballGroup as an attribute of the other class, in the __init__ method write:
self.mudballGroup = pg.sprite.Group()
Edit: Okay, you already have a self.player instance in your game instance. I recommend to define the mudballGroup in the Game class as well and then just pass it and the all_sprites group to the player's shoot method to add a mudball. The collision detection can be done inside the game's update method.
class Game:
def new(self):
# Other code omitted ...
self.mudballGroup = pg.sprite.Group()
def update(self):
# Check if the player is shooting.
if self.player.shooting: # You have to add a `shooting` attribute to player.
# `shoot` just adds a mudball to these groups.
self.player.shoot(self.all_sprites, self.mudballGroup)
# Then detect collisions.
hits = pg.sprite.spritecollide(self.player, self.mudballGroup, False)
if hits:
print("HITS!!!!!!!!", hits)
class Player(pg.sprite.Sprite):
# Pass the sprite groups of the game to the shoot method.
def shoot(self, all_sprites, mudballGroup):
mudball = MUDBALL(self.player.centerx, self.player.centery)
# Add sprite to the passed groups.
all_sprites.add(mudball)
mudballGroup.add(mudball)
Edit 2: Here's the other variant. Pass the two needed sprite groups to the player when you create the instance (you don't have to pass the complete game instance), then set them as attributes of the player.
class Game:
def new(self):
# Other code omitted ...
self.all_sprites = pg.sprite.Group()
self.mudballGroup = pg.sprite.Group()
self.player = Player(self.all_sprites, self.mudballGroup)
def update(self):
# Check if the player is shooting.
if self.player.shooting:
# `shoot` adds a mudball to self.all_sprites & self.mudballGroup.
self.player.shoot()
# Then detect collisions.
hits = pg.sprite.spritecollide(self.player, self.mudballGroup, False)
if hits:
print("HITS!!!!!!!!", hits)
class Player(pg.sprite.Sprite):
def __init__(self, all_sprites, mudballGroup):
# Other code omitted ...
# These two attributes are references to the groups
# that were defined in the Game class.
self.all_sprites = all_sprites
self.mudballGroup = mudballGroup
def shoot(self):
mudball = MUDBALL(self.player.centerx, self.player.centery)
# Add sprite to the passed groups.
self.all_sprites.add(mudball)
self.mudballGroup.add(mudball)
Edit 3: Okay, forget the shooting attribute (it would only be needed for the player instance to check if the user is shooting). You also don't need to call trump's shoot method in the Game class, since you already call it in his update method. So here's the updated code:
class Game(pg.sprite.Sprite):
def new(self):
# Other code omitted ...
self.all_sprites = pg.sprite.Group()
self.mudballGroup = pg.sprite.Group()
# Pass the groups to trump during the instantiation.
# You don't have to pass the complete game instance (self).
self.trump = TRUMP(self.all_sprites, self.mudballGroup)
self.all_sprites.add(self.trump)
def update(self):
hits = pg.sprite.spritecollide(self.player, self.mudballGroup, False)
for collided_sprite in hits:
print("HIT", collided_sprite)
class TRUMP(pg.sprite.Sprite):
def __init__(self, all_sprites, mudballGroup):
# Now set the passed groups as attributes of the trump instance.
# These attributes are references to the same sprite groups as
# in the Game class.
self.all_sprites = all_sprites
self.mudballGroup = mudballGroup
def shoot(self):
print("SHOOT function")
mudball = MUDBALL(self.rect.centerx, self.rect.centery)
# Add the mudball to the groups.
self.all_sprites.add(mudball)
self.mudballGroup.add(mudball)
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()
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.