Oxnvat
Oxnvat

Reputation: 39

Why is class collision not working in PyGame?

I'm making a game where you throw snowballs at snowmen. Here are the definitions for the Snowman class and Snowball class:

class Snowman:
    def __init__(self,start,end,a,b):
        self.x = random.randrange(0,310)
        self.y = 0
        self.r = pygame.Rect(self.x,self.y,a,b)

class Snowball:
    def __init__(self,a,b):
        self.x = mousex
        self.y = mousey
        self.r = pygame.Rect(self.x,self.y,a,b)

As you can see, the r attribute for the classes is their rectangle.

Below is a script that controls the snowman AI.

for s in snowmen:
    s.y += 0.5
    #above_makes_the_snowmen_approach_the_bottom_of_the_screen.
    #below_is_supposed_to_check_for_snowball_collision.
    for b in bullets:
        if s.r.colliderect(b.r):
            snowmen.remove(s)
            bullets.remove(b)
        print(b.r.colliderect(s.r))
        #above_is_a_debugger_that_reports_the_collision.
    if s.y < 640:
        blit(asnowman,(50,78),(s.x,s.y))
    else:
        snowmen.remove(s)

The problem is when I throw a snowball at one of the snowmen, the debugger reports False and the snowman doesn't die. How do I fix this?

Upvotes: 1

Views: 98

Answers (2)

Rabbid76
Rabbid76

Reputation: 211136

See How do I detect collision in pygame?. You need to update the position of the rectangles before the collision test:

s.r.topleft = round(s.x), round(s.y)
b.r.topleft = round(b.x), round(b.y)
if s.r.colliderect(b.r):

Note, you cannot get rid of the x and y attribute, because pygame.Rect is supposed to represent an area on the screen and a pygame.Rect object can only store integral data.

The coordinates for Rect objects are all integers. [...]


Also read How to remove items from a list while iterating?. A possible solution could be to iterate through copies of the list:

for s in snowmen[:]:
    s.y += 0.5
    s.r.topleft = round(s.x), round(s.y)
    for b in bullets[:]:
        b.r.topleft = round(b.x), round(b.y)
        if s.r.colliderect(b.r):
            snowmen.remove(s)
            bullets.remove(b)
            break

Upvotes: 2

Blckknght
Blckknght

Reputation: 104762

While you initialize the Rect object in each of your classes to match the x and y attributes when the object is created, they do not remain in sync. That means that you only ever collide the rectangles at their initial positions, not where the objects have moved to later on.

The easiest way to solve this is probably to add a few lines of code to update s.r and/or b.r, but you could also start using pygame.sprite.Sprite, which will update its rect attribute to match its x and y attributes (and vice versa, I think). If you wanted to make your own classes behave a little bit like a Sprite without going all the way, I'd suggest making x and y into property descriptors, which reference the Rect, which becomes the single source of truth:

class Snowman:
    def __init__(self,start,end,a,b):
        self.r = pygame.Rect(random.randrange(0,310),0,a,b)

    @property
    def x(self):
        return self.r.left
    @x.setter
    def x(self, value):
        self.r.left = value

    @property
    def y(self):
        return self.r.top
    @y.setter
    def y(self, value):
        self.r.top = value

Upvotes: 2

Related Questions