3
votes

So, i am trying to create a game where aliens spawn from 3 specific places. Each Alien will spawn randomly in one of the 3. But there will always be at least one alien, that will spawn on top of another one. I want to delete that alien and spawn him randomly in another spawn point. If it is empty he will stay if not the process will be repeated. The thing is that i cannot find a way to detect collision of 2 objects that are in the same group.

I just started learning pygame so 1) My question may be stupid 2) My way of spawning probably is very inefficient

Here is the Alien class:

class Alien(pygame.sprite.Sprite):
def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.Surface((80,60))
    self.image.fill(GREY)
    self.rect = self.image.get_rect()
    spawn_point1 = x1,y1 = -30, 70
    spawn_point2 = x2,y2 = -30, 150
    spawn_point3 = x3,y3 = -30, 230
    random_spawn = random.choice([spawn_point1,spawn_point2,spawn_point3])
    self.rect.center = random_spawn
    self.speedx = 10

def update(self):
    spawn_point1 = x1,y1 = -30, 70
    spawn_point2 = x2,y2 = -30, 150
    spawn_point3 = x3,y3 = -30, 230
    self.speedx = 10
    random_spawn = random.choice([spawn_point1,spawn_point2,spawn_point3])
    self.rect.x += self.speedx

    if self.rect.x > WIDTH + 20:
        self.rect.center = random_spawn

And here is the part where i detect collision(This part doesnt work)

aliens_col = pygame.sprite.groupcollide(aliens, aliens, True, False)

for i in aliens_col:
    alien = Alien()
    aliens.add(alien)
    all_sprites.add(aliens)
3
Please show us the code (a minimal, complete and verifiable example).skrx
I put it in. As i say above the second part doesnt workM. Michalopoulos
Here is a good article on collision detection. When I implemented a while back I used the Bounding box test. devmag.org.za/2009/04/13/basic-collision-detection-in-2d-part-1MicahB
Should there be only one alien sprite per lane or can there be multiple sprites per lane and you only want to avoid that two spawn directly on top of each other?skrx
There should be one alien sprite per lane and yes i want to avoid them spawning on top of each otherM. Michalopoulos

3 Answers

0
votes

Here is an implementation of the Bounding Box test.

import random


class Rectangle:

    def __init__(self, height, width, x, y):
        self.height = height
        self.width = width
        self.x = x
        self.y = y

    def collided_with_another_rectangle(self, rect):
        """ Assumes rectangles are same size or that this rectangle is smaller than the other rectangle"""
        if self.x > (rect.x + rect.width):
            # Is to the right of the other rectangle
            return False
        elif (self.x + self.width) < rect.x:
            # is to the left of the other rectangle
            return False
        elif (self.y + self.height) < rect.y:
            # is above the other rectangle
            return False
        elif self.y > (rect.y + rect.height):
            # is below the other rectangle
            return False
        else:
            return True


collision_count = 0
for i in range(0, 1000):
    # Here I pick random locations on a 1000X1000 screen for the first rectangle 
    x1 = random.randint(0, 1000)
    y1 = random.randint(0, 1000)
    # Here I pick random locations on a 1000X1000 screen for the second rectangle 
    rect1 = Rectangle(100, 100, x1, y1)
    x2 = random.randint(0, 1000)
    y2 = random.randint(0, 1000)
    rect2 = Rectangle(100, 100, x2, y2)
    """
     I use the collided with another rectangle function to test if the first rectangle is above,below, 
     to the right or to the left of the other rectangle. If neither of these are true then the rectangles 
     have collided.
    """
    if rect1.collided_with_another_rectangle(rect2):
        collision_count += 1
        print("Rect1 X and Y:" + str(x1) + " " + str(y1))
        print("Rect2 X and Y:" + str(x2) + " " + str(y2))
        print("collided")

print("Collision Count:" + str(collision_count))
0
votes

I'm still not absolutely sure what you want to achieve, but I think this example will be helpful to you.

When a sprite leaves the screen, I call the reset_pos method in which I iterate over the three spawn points to set the position to one spawn after the other and then I use another for loop to iterate over the sprites to check if one collides.

If a sprite collides, I continue with the next spawn point.

If no sprite collides, I just return from the method.

If no spawn is free, I remove the sprite (but you can do something else).

import random

import pygame
from pygame.math import Vector2


pygame.init()
WIDTH, HEIGHT = 640, 480


class Alien(pygame.sprite.Sprite):

    def __init__(self, aliens):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((80, 60))
        self.image.fill((120, random.randrange(255), random.randrange(255)))
        self.rect = self.image.get_rect()
        self.spawn_points = [(-30, 70), (-30, 150), (-30, 230)]
        self.aliens = aliens
        self.reset_pos()
        self.speedx = 10

    def update(self):
        self.rect.x += self.speedx

        if self.rect.x > WIDTH + 20:
            self.reset_pos()

    def reset_pos(self):
        random.shuffle(self.spawn_points)  # Shuffle the spawns.

        for spawn in self.spawn_points:
            # Set the position to one of the spawns.
            self.rect.center = spawn
            # Check if this sprite collides with another one.
            for sprite in self.aliens:
                if sprite is self:  # Skip self.
                    continue
                if self.rect.colliderect(sprite.rect):
                    break  # Break out of the loop if the spawn is occupied.
            else:  # The else means no 'break' occurred in the for loop above,
                   # so the spawn must be free.
                return  # Break out of the method if the spawn is free.

        # I just remove the sprite if no spawn is free. You can do something else here.
        self.kill()


def main():
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    aliens = pygame.sprite.Group()
    for _ in range(3):
        # I pass the aliens group to the sprite because we need to
        # iterate over it to see if a sprite collides.
        alien = Alien(aliens)
        aliens.add(alien)
    all_sprites = pygame.sprite.Group(aliens)

    done = False

    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            elif event.type == pygame.MOUSEBUTTONDOWN:
                  al = Alien(aliens)
                  all_sprites.add(al)
                  aliens.add(al)

        all_sprites.update()

        screen.fill((30, 30, 30))
        all_sprites.draw(screen)

        pygame.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    main()
    pygame.quit()
0
votes

When using the same group in both of the group-paramaters of groupcollide it will always consider the sprite it is checking in group_a as colliding with that same sprite in group_b. This results in groupcollide always returning a collision.

To get around this I created a new function in pygame's sprite.py that ignores single collisions and only returns collisions >= 2. My only change was to add:

if len(collision) >=2:

And then the required tab for the following line(s).

The code I added to sprite.py is pasted below but the tab for the def intra_groupcollide is one too far:

def intra_groupcollide(groupa, groupb, dokilla, dokillb, collided=None):
"""detect collision between a group and itself.
This is modified from groupcollide but excludes collisions <=1

pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb):
    return dict

"""
crashed = {}
# pull the collision function in as a local variable outside
# the loop as this makes the loop run faster
sprite_collide_func = spritecollide
if dokilla:
    for group_a_sprite in groupa.sprites():
        collision = sprite_collide_func(group_a_sprite, groupb,
                                        dokillb, collided)
        if collision:
            if len(collision) >=2:
                crashed[group_a_sprite] = collision
                group_a_sprite.kill()
else:
    for group_a_sprite in groupa:
        collision = sprite_collide_func(group_a_sprite, groupb,
                                        dokillb, collided)
        if collision:
            if len(collision) >=2:
                crashed[group_a_sprite] = collision



                
            
#print(crashed)
return crashed

Then in my own python program, I simply replaced groupcollide with intra_groupcollide. I set both kill paramaters as 'false' because in my usage I'm bouncing them off each other. I have not tested this code with them set to 'true'.

I found sprite.py in my file system by following this answer: Where are the python modules stored?