Max COSSETTO
Max COSSETTO

Reputation: 103

Pygame Collision, Attribute error

I am a student working on a school project. My collision isn't working and I am starting to get frustrated. Whenever I try to run my game, an error message pops up saying

"AttributeError: type object 'Ball' has no attribute 'rect'".

In my code you will see that I have self.rect = pygame.Rect(self.x, self.y, 100, 100). I have tried to use the get_rect function, but this still doesn't work. If you have any tips for the code, feel free to add a comment.

import pygame
import random
import os
from pygame import mixer

# accessed: 4/6/18  code:https://github.com/codingandcaring/PYgame/blob/bacaab1bd6ec0c97412a136773dfd634455c3e2f/basketball_game.py
#Music
#Tutorial from computingmrh - https://www.youtube.com/watch?v=lUMSK6LmXCQ used on 5th may 2018
snd_file = 'Game.ogg'

mixer.init()
mixer.music.load(snd_file)
mixer.music.play()

#spirtes
class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load('basketball.png').convert_alpha()
        self.image = pygame.transform.scale(self.image, (100, 100))
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, 100, 100)
        self.speed_x = 5
        self.speed_y = 5
        self.radiusx = 0
        self.radiusy = 100
        self.mask = pygame.mask.from_surface(self.image)

    def update(self, width, height):
        self.x += self.speed_x
        self.y += self.speed_y
        self.rect = pygame.Rect(self.x, self.y, 100, 100)
        if self.x + self.radiusx > width:
            self.speed_x = 0
        if self.y + self.radiusx > height:
            self.speed_y = 0        
        if self.x + self.radiusy > width:
            self.speed_x = 0
        if self.y + self.radiusy > height:
            self.speed_y = 0
        if self.x - self.radiusx <= 0:
            self.speed_x = 0
        if self.y - self.radiusx <= 0:
            self.speed_y = 0


    def render(self, screen):
        screen.blit(self.image, (self.x, self.y))


class Goal(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load('goal.png').convert_alpha()
        self.image = pygame.transform.scale(self.image, (220, 220))
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, 220, 220)

    def render(self, screen):
        screen.blit(self.image, (self.x, self.y))

class Ring(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load('ring.png').convert_alpha()
        self.image = pygame.transform.scale(self.image, (400, 400))
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, 400, 400)
        self.mask = pygame.mask.from_surface(self.image)

    def render(self, screen):
        screen.blit(self.image, (self.x, self.y))

class Baddie(pygame.sprite.Sprite):
    def __init__(self, x, y, z):
        self.image = pygame.image.load().convert_alpha()
        self.image = pygame.transform.scale(self.image, (220, 220))
        self.rect = self.image.get_rect()
        self.x = x
        self.y = y

    def render(self, screen):
        screen.blit(self.image, (self.x, self.y))

def main():
    # basics
    width = 1200
    height = 722

    pygame.init()
    screen = pygame.display.set_mode((width, height))
    pygame.display.set_caption('Basketball Shootout')
    font = pygame.font.Font(None, 25)

    #code for the badguys
    badtimer=100
    badtimer1=0
    badguys=[[640,100]]

    # Add Variables
    court = pygame.image.load('halfcourt.jpg')
    court = pygame.transform.scale(court, (1200, 722))
    basketball = Ball(50, 50)
    goal = Goal(487, 0)
    ring = Ring(400,400)
    player = Ball
    badguyimg1 = pygame.image.load("wpierdol.png")
    badguyimg1 = pygame.transform.scale(badguyimg1, (100, 100))
    badguyimg2 = pygame.image.load("bad_guy2.gif")
    badguyimg2 = pygame.transform.scale(badguyimg2, (100, 100))
    badguyimg3 = pygame.image.load("bad_guy3.gif")
    badguyimg3 = pygame.transform.scale(badguyimg3, (100, 100))
    badlist = [badguyimg1, badguyimg2, badguyimg3]



    stop_game = False



    #main game logic
    while not stop_game:
        badtimer -= 1
        point = pygame.sprite.collide_mask(player, ring)
        if point:
            score = score + 1


        #Draw the bad guys
        if badtimer==0:
            badguys.append([1040, random.randint(50,430)])
            badtimer=100-(badtimer1*2)
        if badtimer1>=35:
            badtimer1=35
        else:
            badtimer1+=5
        index=0
        for badguy in badguys:
            if badguy[0]<-64:
                badguys.pop(index)
            badguy[0]-=7
            index+=1

        for event in pygame.event.get():
            pressed = pygame.key.get_pressed()
            if pressed[pygame.K_UP]:
                basketball.y -= 5
                basketball.speed_y = -5
            elif pressed[pygame.K_DOWN]:
                basketball.y += 5
                basketball.speed_y = 5
            elif pressed[pygame.K_LEFT]:
                basketball.x -= 5
                basketball.speed_x = -5
            elif pressed[pygame.K_RIGHT]:
                basketball.x += 5
                basketball.speed_x = 5
            if event.type == pygame.QUIT:
                stop_game = True

    # Updating
        basketball.update(width, height)
        screen.blit(court, (0,0))

        text = font.render('Dodge the other team to get to the goal!', True, (0, 0, 0))
        screen.blit(text, (430, 630))
        goal.render(screen)
        basketball.render(screen)
        for badguy in badguys:
            screen.blit(badguyimg1)



        pygame.display.update()

    pygame.quit()

if __name__ == '__main__':
    main()

Upvotes: 0

Views: 136

Answers (2)

skrx
skrx

Reputation: 20478

There are several problems, but as Sloth has already explained, the AttributeError gets raised because the player is a reference to the Ball class and not an instance. The player variable actually makes no sense, since the basketball is the actual playable instance not the player and you should pass the basketball to the collide_mask function instead. Just remove the player.

point = pygame.sprite.collide_mask(basketball, ring)

The event handling is a bit messed up. Don't call pygame.key.get_pressed inside of the event loop (for event in pygame.get_event():) because that line and the following code will be exectued once per event in the queue.

You've also mixed up two different ways to move the sprite: Either do basketball.y -= 5 with key.get_pressed or set the speed in the event loop: basketball.speed_y = -5.

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        stop_game = True
    # Either set the speed here.
    elif event.type == pygame.KEYDOWN:
        if event.key == pygame.K_UP:
            basketball.speed_y = -5

# Or increment the `basketball.y` in the while loop with `pygame.key.get_pressed`.
pressed = pygame.key.get_pressed()
if pressed[pygame.K_UP]:
    basketball.y -= 5
elif pressed[pygame.K_DOWN]:
    basketball.y += 5
if pressed[pygame.K_LEFT]:  # if not elif, to separate vertical and horizontal movement.
    basketball.x -= 5
elif pressed[pygame.K_RIGHT]:
    basketball.x += 5

If you use the key.get_pressed solution, you can remove the self.speed_x and speed_y attributes.

You can also pass a rect with the size of the screen to the update method and use it to clamp the rect of the ball.

def update(self, screen_rect):  # Pass a rect with the size of the screen.
    self.x += self.speed_x
    self.y += self.speed_y
    self.rect.topleft = (self.x, self.y)
    if not screen_rect.contains(self.rect):
        # Clamp the rect if it's outside of the screen.
        self.rect.clamp_ip(screen_rect)
        self.x, self.y = self.rect.topleft

You should add a pygame.time.Clock instance and call clock.tick every frame to limit the frame rate, otherwise the game will run way too fast and the speed will depend on the PC.


Lists (and other mutable containers) shouldn't be modified while you're iterating over them or items could be skipped. Just iterate over a slice copy (or use a list comprehension):

# You can `enumerate` the badguys list to get the index and the item at the same time.
for index, badguy in enumerate(badguys[:]):
    if badguy[0] < -64:
        # skrx: Don't modify a list while you're iterating over it.
        # Iterate over a slice copy: badguys[:]
        badguys.pop(index)
    badguy[0] -= 7

You forgot to render the ring (which was a bit confusing during the debugging): ring.render(screen).

And the score variable was missing.

Here's the complete, working example (I used some replacement surfaces):

import random
import pygame


class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        # self.image = pygame.image.load('basketball.png').convert_alpha()
        # self.image = pygame.transform.scale(self.image, (100, 100))
        self.image = pygame.Surface((100, 100)).convert_alpha()
        self.image.fill((160, 70, 0))
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, 100, 100)
        self.speed_x = 0
        self.speed_y = 0
        self.radiusx = 0
        self.radiusy = 100
        self.mask = pygame.mask.from_surface(self.image)

    def update(self, screen_rect):  # Pass a rect with the size of the screen.
        self.x += self.speed_x
        self.y += self.speed_y
        self.rect.topleft = (self.x, self.y)
        if not screen_rect.contains(self.rect):
            # Clamp the rect if it's outside of the screen.
            self.rect.clamp_ip(screen_rect)
            self.x, self.y = self.rect.topleft

    def render(self, screen):
        screen.blit(self.image, self.rect)


class Goal(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        # self.image = pygame.image.load('goal.png').convert_alpha()
        # self.image = pygame.transform.scale(self.image, (220, 220))
        self.image = pygame.Surface((220, 220)).convert_alpha()
        self.image.fill((60, 80, 110))
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, 220, 220)

    def render(self, screen):
        screen.blit(self.image, self.rect)


class Ring(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        # self.image = pygame.image.load('ring.png').convert_alpha()
        # self.image = pygame.transform.scale(self.image, (400, 400))
        self.image = pygame.Surface((400, 400)).convert_alpha()
        self.image.fill((60, 180, 110))
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, 400, 400)
        self.mask = pygame.mask.from_surface(self.image)

    def render(self, screen):
        screen.blit(self.image, self.rect)


class Baddie(pygame.sprite.Sprite):
    def __init__(self, x, y, z):
        # self.image = pygame.image.load().convert_alpha()
        # self.image = pygame.transform.scale(self.image, (220, 220))
        self.image = pygame.Surface((90, 90)).convert_alpha()
        self.image.fill((250, 50, 0))
        self.rect = self.image.get_rect()
        self.x = x
        self.y = y

    def render(self, screen):
        screen.blit(self.image, (self.x, self.y))


def main():
    width = 1200
    height = 722

    pygame.init()
    screen = pygame.display.set_mode((width, height))
    screen_rect = screen.get_rect()
    clock = pygame.time.Clock()  # Add a clock to limit the frame rate.
    pygame.display.set_caption('Basketball Shootout')
    font = pygame.font.Font(None, 25)

    badtimer = 100
    badtimer1 = 0
    badguys = [[640, 100]]

    # court = pygame.image.load('halfcourt.jpg')
    # court = pygame.transform.scale(court, (1200, 722))
    court = pygame.Surface((1200, 722))
    court.fill((30, 30, 30))
    basketball = Ball(50, 50)
    goal = Goal(487, 0)
    ring = Ring(400, 400)
    # The player is not needed since the `basketball` is already
    # the playable ball instance.
    # player = Ball  # Just remove this line.

    # badguyimg1 = pygame.image.load("wpierdol.png")
    # badguyimg1 = pygame.transform.scale(badguyimg1, (100, 100))
    # badguyimg2 = pygame.image.load("bad_guy2.gif")
    # badguyimg2 = pygame.transform.scale(badguyimg2, (100, 100))
    # badguyimg3 = pygame.image.load("bad_guy3.gif")
    # badguyimg3 = pygame.transform.scale(badguyimg3, (100, 100))
    badguyimg1 = pygame.Surface((90, 90))
    badguyimg1.fill((60, 50, 210))
    badguyimg2 = pygame.Surface((90, 90))
    badguyimg2.fill((250, 50, 210))
    badguyimg3 = pygame.Surface((90, 90))
    badguyimg3.fill((250, 50, 130))
    badlist = [badguyimg1, badguyimg2, badguyimg3]

    score = 0  # The score variable was missing.

    stop_game = False

    while not stop_game:
        # Event handling.
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                stop_game = True
            # Either set the speed here or increment the `basketball.y`
            # in the while loop with `pygame.key.get_pressed`.
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    basketball.speed_y = -5
                elif event.key == pygame.K_DOWN:
                    basketball.speed_y = 5
                elif event.key == pygame.K_LEFT:
                    basketball.speed_x = -5
                elif event.key == pygame.K_RIGHT:
                    basketball.speed_x = 5
            elif event.type == pygame.KEYUP:
                # Stop the ball.
                if event.key == pygame.K_UP:
                    basketball.speed_y = 0
                elif event.key == pygame.K_DOWN:
                    basketball.speed_y = 0
                elif event.key == pygame.K_LEFT and basketball.speed_x < 0:
                    basketball.speed_x = 0
                elif event.key == pygame.K_RIGHT and basketball.speed_x > 0:
                    basketball.speed_x = 0

        # Don't call get_pressed in the event loop (for every event).
        # pressed = pygame.key.get_pressed()
        # if pressed[pygame.K_UP]:
        #     basketball.y -= 5
        # elif pressed[pygame.K_DOWN]:
        #     basketball.y += 5
        # if pressed[pygame.K_LEFT]:  # if not elif, to separate vertical and horizontal movement.
        #     basketball.x -= 5
        # elif pressed[pygame.K_RIGHT]:
        #     basketball.x += 5

        # Updating.
        basketball.update(screen_rect)
        badtimer -= 1

        point = pygame.sprite.collide_mask(basketball, ring)  # Use basketball not player.
        if point:
            # The score will be incremented continually.
            score = score + 1
            print(score)

        # Update the bad guys.
        if badtimer == 0:
            badguys.append([1040, random.randint(50,430)])
            badtimer = 100-(badtimer1*2)
        if badtimer1 >= 35:
            badtimer1 = 35
        else:
            badtimer1 += 5

        # You can `enumerate` the badguys list to get the index
        # and the item at the same time.
        for index, badguy in enumerate(badguys[:]):
            if badguy[0] < -64:
                # Don't modify a list while you're iterating over it.
                # Iterate over a slice copy: badguys[:]
                badguys.pop(index)
            badguy[0] -= 7

        # Drawing.
        screen.blit(court, (0,0))

        text = font.render(
            'Dodge the other team to get to the goal!',
            True, (0, 0, 0))
        screen.blit(text, (430, 630))
        goal.render(screen)
        # You forgot to render the ring.
        ring.render(screen)

        for badguy in badguys:
            screen.blit(badguyimg1, badguy)  # The `dest`ination arg was missing.

        basketball.render(screen)

        pygame.display.update()
        clock.tick(60)  # Limit the frame rate to 60 FPS.

    pygame.quit()

if __name__ == '__main__':
    main()

Upvotes: 1

sloth
sloth

Reputation: 101122

The exception is raised because the variable player is a reference to the class Ball, and not an instance of it.

Look at this line:

player = Ball

This does not create a new instance, since you don't call the class with (...).


Other than that, if you use the Sprite class, better use it like intended. There's no point in storing the coordiantes in a x and y attribute in your classes when there's already the rect attribute.

Also, you don't need a render method when there's already the Group class that does this for you.

Here's an example where I changed your code to use the intended pygame features (shorted for brevity):

import pygame

class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        image = pygame.image.load('basketball.png').convert_alpha()
        self.image = pygame.transform.scale(image, (100, 100))
        self.rect = self.image.get_rect(topleft=(x,y))
        self.speed_x = 5
        self.speed_y = 5
        self.mask = pygame.mask.from_surface(self.image)

    def update(self, screen_rect):
        self.rect.move_ip(self.speed_x, self.speed_y)
        self.rect.clamp_ip(screen_rect)

def main():
    # basics
    width = 1200
    height = 722

    pygame.init()
    screen = pygame.display.set_mode((width, height))
    screen_rect = screen.get_rect()
    pygame.display.set_caption('Basketball Shootout')

    basketball = Ball(50, 50)

    stop_game = False

    sprites = pygame.sprite.Group(basketball)

    #main game logic
    while not stop_game:
        badtimer -= 1

        for event in pygame.event.get():
            pressed = pygame.key.get_pressed()
            if pressed[pygame.K_UP]:
                basketball.speed_y = -5
            elif pressed[pygame.K_DOWN]:
                basketball.speed_y = 5
            elif pressed[pygame.K_LEFT]:
                basketball.speed_x = -5
            elif pressed[pygame.K_RIGHT]:
                basketball.speed_x = 5
            if event.type == pygame.QUIT:
                stop_game = True

        screen.fill(pygame.color.THECOLORS['white'])
        sprites.update(screen_rect)
        sprites.draw(screen)

        pygame.display.update()

    pygame.quit()

if __name__ == '__main__':
    main()

Upvotes: 1

Related Questions