5

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.

3
  • For future reference, it may be worth your while to mention in brief some of the things you've tried or where you think the problem might be, as well. You may also wish to post ready-to-go code with placeholder assets (self.i1 = pygame.Surface((30,30)); self.i1.fill((255,25,25)) instead of actual images we won't have handy, for instance) so that we can run it without too much additional editing. I've left the original image references in there, so that it still makes sense to you, but it might help you (and us) along a bit next time! Commented Dec 16, 2014 at 12:40
  • Oops, I didn't think of that. Thank you for the suggestion, I'll remember that next time I post something! Commented Dec 17, 2014 at 5:39
  • No worries! :D If you want to try and pare down your code, too, you can consider clearing out redundant and known-functional code, too, like asteroids.__init__'s i2 and i3 data, as long as you're sure it's ancillary and not relevant to the error you're getting. (Sometimes you're in a hurry, though, and you just want to get it done.) Commented Dec 17, 2014 at 6:22

1 Answer 1

4

Your problem is that neither the spaceship nor asteroids classes revise their own rects (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 propertys 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 rects' 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.

Sign up to request clarification or add additional context in comments.

3 Comments

I did as you suggested, and now the program will catch the collision some of the time but not all of the time, and I'm not quite sure why. I tried adding something to check the asteroids and ship for other boundaries--the bottom and the right--but that didn't help at all. I'm not really sure what to do, because it only detects certain collisions--sometimes you can fly right under an asteroid and be fine and then you'll be approaching an asteroid and the game quits like it should have when the asteroid and ship collided originally.
Strange; I'll check that out for you. Did you use the update function solution, or one of the alternatives?
Oh! Could it be that your new self.rect.topleft = self.x, self.y lines are not at the lowest indent levels in update? If they aren't, they're probably part of an if block and are only running for certain asteroids.current values (probably 3) and when spaceship is at the leftmost edge of the screen. (I've edited this detail into the solution just now.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.