I'm trying to make a spaceship game for my Python programming class, and I want to use sprite.spritecollideany
to check if my player sprite spaceship
is colliding with any of the asteroid sprites in the spriteGroup
group, but no matter what I do, it doesn't seem to work.
Here's the code:
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Asteroids and Spaceships")
background = pygame.image.load("background.png")
background = background.convert()
white = 255,255,255
#first I make the asteroids with this class.
class asteroids(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.width = 30
self.height = 30
self.i1 = pygame.image.load("smallasteroid.png")
self.i1 = self.i1.convert()
self.i1.set_colorkey(white)
self.rect = self.i1.get_rect()
self.rect.left = x
self.rect.top = y
self.i2 = pygame.image.load("smallasteroid2.png")
self.i2 = self.i2.convert()
self.i2.set_colorkey(white)
self.rect = self.i2.get_rect()
self.rect.left = x
self.rect.top = y
self.i3 = pygame.image.load("mediumasteroid.png")
self.i3 = self.i3.convert()
self.i3.set_colorkey(white)
self.rect = self.i3.get_rect()
self.rect.left = x
self.rect.top = y
self.current = 0
def render(self, image_num):
if image_num == 1:
self.current = 1
if image_num == 2:
self.current = 2
if image_num == 3:
self.current = 3
def update(self):
if self.current == 1:
screen.blit(self.i1, (self.x,self.y))
self.y += random.randint(7,11)
if self.y > screen.get_height():
self.y = 0
if self.current == 2:
screen.blit(self.i2, (self.x,self.y))
self.y += random.randint(5,9)
if self.y > screen.get_height():
self.y = 0
if self.current == 3:
screen.blit(self.i3, (self.x,self.y))
self.y += random.randint(3,6)
if self.y > screen.get_height():
self.y = 0
#and then this is the class for the spaceship
class spaceship(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.width = 40
self.height = 60
self.image = pygame.image.load("spaceship.png")
self.image = self.image.convert()
self.image.set_colorkey(white)
self.rect = self.image.get_rect()
self.rect.left = x
self.rect.top = y
def update(self):
screen.blit(self.image,(self.x,self.y))
if self.y > screen.get_height():
self.y = 0
if self.y < screen.get_height()-610:
self.y = screen.get_height()
if self.x > screen.get_width():
self.x = 0
if self.x < screen.get_width()-610:
self.x = screen.get_width()
#main is where I have the game run
def main():
x_ship, y_ship = 0,0
player = spaceship(300,550)
spriteGroup = pygame.sprite.Group() #my asteroid sprites are grouped here
for i in range(25):
image_num = random.randint(0,3)
asteroid = asteroids(random.randint(0,500),random.randint(0, 200))
asteroid.render(image_num)
spriteGroup.add(asteroid)
clock = pygame.time.Clock()
keepGoing = True
while keepGoing:
#this is what I tried to use to check for collisions. I know it's not working but I don't know why.
if pygame.sprite.spritecollideany(player,spriteGroup):
#the program quits on collision.
pygame.quit()
for event in pygame.event.get():
if (event.type == pygame.QUIT):
keepGoing = False
elif event.type == pygame.KEYUP:
if event.key == pygame.K_ESCAPE:
keepGoing = False
if (event.type == pygame.KEYDOWN):
if (event.key == pygame.K_LEFT):
x_ship = -4
if (event.key == pygame.K_RIGHT):
x_ship = 4
if (event.key == pygame.K_UP):
y_ship = -4
if (event.key == pygame.K_DOWN):
y_ship = 4
if (event.type==pygame.KEYUP):
if (event.key==pygame.K_LEFT):
x_ship = 0
if (event.key==pygame.K_RIGHT):
x_ship = 0
if (event.key==pygame.K_UP):
y_ship = 0
if (event.key==pygame.K_DOWN):
y_ship = 0
player.x += x_ship
player.y += y_ship
screen.blit(background, (0,0))
spriteGroup.clear(screen, background)
player.update()
spriteGroup.update()
clock.tick(50)
pygame.display.flip()
pygame.quit()
main()
I can't figure out why the collision isn't working.
Your problem is that neither the spaceship
nor asteroids
classes revise their own rect
s (their hitboxes) on update
, and their x
and y
attributes have no direct or automatic connection to the location of that rect.
If you add something like self.rect.topleft = self.x, self.y
to the end of your update
function for both classes, their respective rects-- which is, your hitboxes-- will move to where they ought to be, instead of remaining at their initialized locations, which in this case is (300,550) for player
and... some semi-random offset for each asteroid (I'm not sure where, exactly; all I did was make a sloppy reproduction of your code, then test a bunch of hunches. I apologize for not finding the exact origin of the problem...)
In any case, the short answer is that, although you have a running check for the x and y locations of each sprite, you haven't told pygame to actually apply that location to the hitbox, and sprite.spritecollideany
is always Falsey because the hitbox rects themselves were never actually touching.
Putting self.rect.topleft = self.x, self.y
at the end of each of your sprite class' update
functions will fix it. (Make sure that this line is at the end of the function and at the lowest indentation level within def update(self)
!)
EDIT
Alternatively, instead of adding the aforementioned code to update
, you could replace player.x = x_ship
and player.y = y_ship
in your main loop with something like:
while keepGoing:
...
player.rect.move_ip(x_ship, y_ship) # move the ship's rect
player.x, player.y = player.rect.topleft # update it's x and y, if you use these elsewhere
for item in spriteGroup: # update the rects for your asteroid objects
item.rect.topleft = item.x, item.y
I would use the update
-altering solution, since there's a really good chance that this solution will cause you grief when the player approaches the edges of the play field. Still, another avenue for you to consider.
As a suggestion, you could redefine <Sprite>.x
and <Sprite>.y
as property
s that return self.rect.left
and return self.rect.top
, respectively, so that the x and y values are pinned to the topleft of your hitbox. A setter, too, even.
I'm not sure how you may wish to use those parameters in the future; it's probably possible for you to eliminate them entirely, if you like, and use their rect
s' locators instead. Food for thought!
A note:
I'm also assuming that all of your sprite's x
and y
attributes refer to the top left point of its rect
box. If that point is supposed to be somewhere else (the center, for instance), then you may need to make adjustments to this code, if you decide to use it.