H. Nehlsen H. Nehlsen - 16 days ago 15
Python Question

Pygame issue with colliderect and classes

I have a question concerning the colliderect function and variables embedded in classes and functions in general.

I created "hitboxes" around my important elements (player, finish line, monsters), and I want actions to be triggered when those hitboxes "meet".

I understand that the colliderect function from pygame is meant to do that, but somehow I can't implement it.

Could someone explain to me, how I would use the feature in the following code, specifically when the variables I have to call are embedded in separate classes and functions?

Thank you very much, here is my code so far:

#!/usr/bin/python
import sys, pygame, glob, math
from pygame import *
from random import randint
import time

pygame.init()

h = 600
w = 800
screen = pygame.display.set_mode((w, h))
clock = pygame.time.Clock()
jump_height = 0

class finish_line:
def __init__(self):
self.n = 600
self.b = 200
self.img_finish = pygame.image.load("finish.png")
self.update_finish(0)
def update_finish(self, pos_finish):
screen.blit(self.img_finish, (self.n, self.b))


class player:
def __init__(self):
self.x = 11
self.y = 200
self.speed = 5
self.ani_speed_init = 5
self.ani_speed = self.ani_speed_init
self.ani = glob.glob("animation/run*.png")
self.ani.sort()
self.ani_pos = 0
self.ani_max = len(self.ani)-1
self.img = pygame.image.load(self.ani[0])
self.update(0)

def update(self, pos):
if pos > 0:
if self.x >= (w - 100):
self.x = self.x
else:
self.x += self.speed

self.ani_speed -= 1
if self.ani_speed == 0:
self.img = pygame.image.load(self.ani[self.ani_pos])
self.ani_speed = self.ani_speed_init
if self.ani_pos == self.ani_max:
self.ani_pos = 0
else:
self.ani_pos += 1

elif pos < 0:
if self.x <= 9:
self.x = self.x
else:
self.x -= self.speed

self.ani_speed -= 1
if self.ani_speed == 0:
self.img = pygame.image.load(self.ani[self.ani_pos])
self.ani_speed = self.ani_speed_init
if self.ani_pos == self.ani_max:
self.ani_pos = 0
else:
self.ani_pos += 1

if jump_height == 1:
if self.y <= 10:
self.y = self.y
else:
self.y -= self.speed
elif jump_height == -1:
if self.y >= 500:
self.y = self.y
else:
self.y += self.speed
rectplayer = pygame.draw.rect(screen, (0, 0, 0), ((self.x + 10), (self.y + 15), 65, 70), 1)
screen.blit(self.img, (self.x, self.y))
return self.x, self.y


class monster:
rectmonster = 0
def __init__(self):
self.v = 650
self.c = 200
self.speed_monster = 0.3
self.img_monster = pygame.image.load("orange.png")
self.update_monster(0)

def update_monster(self, pos_monster):
if pos_monster == 1:
self.v = self.v + ((randint(-5, 1)) * self.speed_monster)
self.c = self.c + ((randint(-1, 3)) * self.speed_monster)
if self.v >= 660:
self.v = self.v + ((randint(-2, 0)) * self.speed_monster)
elif self.v <= 140:
self.v = self.v + ((randint(0, 2)) * self.speed_monster)
if self.c <= 140:
self.c = self.c + ((randint(0, 2)) * self.speed_monster)
elif self.c >= 460:
self.c = self.c + ((randint(-2, 0)) * self.speed_monster)

rectmonster = pygame.draw.rect(screen, (0, 0, 0), ((self.v + 12), (self.c + 5), 76, 90), 1)
screen.blit(self.img_monster, (self.v, self.c))

finish1 = finish_line()
player1 = player()
monster1 = monster()
monster2 = monster()
pos = 0
pos_monster = 1
pos_finish = 0

while 1:
screen.fill((255, 204, 229))
pygame.draw.rect(screen, (0,0,0,), (610, 210, 200, 180), 1)
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == KEYDOWN and event.key == K_RIGHT:
pos = 1
elif event.type == KEYUP and event.key == K_RIGHT:
pos = 0
elif event.type == KEYDOWN and event.key == K_LEFT:
pos = -1
elif event.type == KEYUP and event.key == K_LEFT:
pos = 0
elif event.type == KEYDOWN and event.key == K_UP:
jump_height = 1
elif event.type == KEYUP and event.key == K_UP:
jump_height = 0
elif event.type == KEYDOWN and event.key == K_DOWN:
jump_height = -1
elif event.type == KEYUP and event.key == K_DOWN:
jump_height = 0

finish1.update_finish(pos_finish)
monster1.update_monster(pos_monster)
monster2.update_monster(pos_monster)
player1.update(pos)

pygame.display.flip()

Answer

This is my two cents on the problem based of two comments above.

You're assuming pygame.draw.rect automatically gives your class a size property, it does not.
You store the return value of rect() in rectplayer like so:

rectplayer = pygame.draw.rect(...)

And I bet rect() returns None to begin with. Besides that the variable isn't stored as self.rectplayer so it's a local function variable not a class-owned variable - so Pygame can't check against it.

This is an easy mistake to make and if that was the only root to the problem here, it would be an easy fix.

Unfortunately there's more assumptions here, such as the fact that colliderrect is a global function of Pygame.

colliderect & What is sprites?

There's a key note on this internal function of Pygame that states:

all sprites must have a “rect” value, which is a rectangle of the sprite area, which will be used to calculate the collision.

It's a internal function called by pygame.sprite.spritecollide() that mentions the fact that you should be using Sprites.

Here's a minimal example of a sprite object:

class Player(pygame.sprite.Sprite):
    def __init__(self, color, width, height):
       pygame.sprite.Sprite.__init__(self)

       self.image = pygame.Surface([width, height])
       self.image.fill(color)

       self.rect = self.image.get_rect()

At the bottom of the initialization process of this sprite we define our rectangle self.rect that contains our (start_x, start_y), (width, height) data. These are size properties that you haven't defined in your above example.

Most likely, you could define something along the lines of:

class player:
    def __init__(self):
        self.rect = ...

But remember, your <class player> has no function called spritecollide() or collide_rect(), these functions is a inheritance either from pygame.sprite.Sprite or pygame.Surface, so you're better off sticking to these as your inherited object for your custom classes.

See my above example of class Player(...) how to do that.

I know this doesn't solve your entire code base, but hopefully it will give you some sense or idea of what's needed to complete your code.

End note

A friendly reminder, if you ever think that something is done for you - double check it. Assuming a class gets size properties because you drew a rectangle, check if your class actually did.

player1 = player()
print(dir(player1))

Is there anything in there remotely resembling width, height, size or rectangle? If not, this should indicate where something is funky.

Comments