pythonpygamecollision

Pygame spritecollide giving me iterable error


I am using pygame to create a Brick Breaker game. I have gotten everything else to work except the collisions. I tried to use collide_rect but it didn't work, but it did not give me error messages. I tried to use spritecollide, but it gives me the error message Ball object is not iterable. My goal is to detect the blocks, remove it, and increase the score. Sorry, my code is a little messy. Here is my code (The collision detection is near the bottom):

import pygame
import paddle
import ball
import block
import time
import random
import math
screenW, screenH = 1280, 720

backroundColor = [0,0,0]

offset = 30
PaddleW = 200
PaddleH = 25
PaddleX = screenW / 2 - PaddleW / 2
PaddleY = screenH - PaddleH - offset

n = 10
blockW = screenW / n #128 per block
blockH = blockW /4 + 10
pygame.init()
pygame.display.set_caption("Brick Breaker")
Display = pygame.display.set_mode((screenW, screenH))
Display.fill(backroundColor)

player = paddle.Paddle(PaddleX, PaddleY, PaddleW, PaddleH)

ballSize = 12
ballX = PaddleX
ballY = PaddleY-20
ballSpeed = -4
ballMoveY = ballSpeed
ballMoveX = ballSpeed
balls = pygame.sprite.Group()
balls = ball.Ball(ballX, ballY, ballSize)
#ballDraw = ball.Ball.draw(Display)

lives = 3
score = 0
shiftX = 0
shiftY = 0

#blocks init
'''blocks = pygame.sprite.Group()
blocks = []'''

#block1 = pygame.sprite.Group()
block1 = block.Block(10,60,blockW,blockH)

counter = 0
def displayText(text, color, posX, posY, size):
    str(text)
    font = pygame.font.SysFont('impact', size)
    textWrite = font.render(text, True, color)
    Display.blit(textWrite, (posX, posY))
def waitForKey():
    wait = True
    while wait:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                    pygame.quit()
            keys = pygame.key.get_pressed()
            if keys[pygame.K_SPACE]:
                wait = False


'''m=3
for y in range (4): #Spawn blocks
    for i in range (0, int(screenW), int(blockW)):
        blocks.append(block.Block(i+2, blockH*(y+2), blockW-4, blockH-4))'''
block1.draw(Display)
#blocks.draw = (14,14,14,14)

FPSClock = pygame.time.Clock()
FPS = 144

GameOver = False
Display.fill([130,140,230])
displayText('Brick Breaker', [200,230,100], 450,100, 80)
displayText('Use the mouse to move the paddle and bounce the ball. Try to break the bricks and not die.', [220,10,10], 120, 300,30)
displayText('Press space to begin', [0,0,0],520,510, 30)
pygame.display.flip()
waitForKey()
while not GameOver:
    counter += 1/FPS
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            if GameOver == False:
                GameOver = True
    mouseX = pygame.mouse.get_pos()[0]
    Display.fill(backroundColor)
    player.draw(Display)
    player.move(mouseX)

    balls.move(ballX, ballY) #pygame.draw.circle(Display, [0, 120, 120], [int(ballX), int(ballY)], ballSize)
    balls.draw(Display)

    '''
    for x in range(len(blocks)):
        blocks[x].draw(Display)
    '''
    #Draw blocks
    block1.draw(Display)

    if ballX <= 0:
        ballMoveX = -ballSpeed
        pygame.mixer.music.load("GungaGinga.mp3")
        pygame.mixer.music.play(1)
        #shiftX = random.randint(-2,2)
        #shiftY = -shiftX
    elif ballX >= screenW:
        ballMoveX = ballSpeed
        pygame.mixer.music.load("GungaGinga.mp3")
        pygame.mixer.music.play(1)
        #shiftX = random.randint(-2, 2)
        #shiftY = -shiftX
    if ballY <= 0:
        ballMoveY = -ballSpeed
        pygame.mixer.music.load("GungaGinga.mp3")
        pygame.mixer.music.play(1)
        #shiftY = random.randint(-2, 2)
        #shiftX = -shiftY
    elif ballY >= screenH:

        pygame.mixer.music.load("OOF.mp3")
        pygame.mixer.music.play(1)
        ballMoveY = ballSpeed
        lives -= 1
        ballX = PaddleX
        ballY = PaddleY - 20
        balls.move(ballX, ballY)
        #shiftX = 0
        #shiftY = 0
        time.sleep(1)

    if ballY <= PaddleY + PaddleW and ballY +ballSize >= PaddleY:
        if (ballX >= mouseX or ballX <= mouseX) and (ballX + ballSize <= mouseX + PaddleW/2 and ballX + ballSize >= mouseX - PaddleW/2): # or ballX + ballSize >= mouseX - PaddleW/2
            ballMoveY = -ballMoveY#ballSpeed
            pygame.mixer.music.load("GungaGinga.mp3")
            pygame.mixer.music.play(1)
            pygame.mixer.music.load("OOF.mp3")
            pygame.mixer.music.play(1)
    ballX += ballMoveX
    ballX = ballX +shiftX
    ballY += ballMoveY
    ballY = ballY + shiftY

    displayText('Lives: %s'%lives, [90,190,90], 20, 10, 40)
    displayText('Time: %.1f' %counter, [20,190,90], 1100, 10, 40)
    displayText('Score: %s' % score, [90, 190, 90], 570, 10, 40)
    if counter == 5:
        ballSpeed -= 1

#Collision Test
    #if pygame.sprite.spritecollideany(balls, blocks):
        #blocks.kill(blocks)
    #hitBlock = pygame.sprite.collide_mask(blocks, player)
    '''    def hit(self):
        hit_block = pygame.sprite.groupcollide(blocks, self.balls, False, True)
        if hit_block:
            return True
        else:
            return False'''
    #is_a_collision = pygame.sprite.collide_mask(blocks, balls)
    '''
    if self.hit():
        print("collision")
        score += 10
        blocks.kill(blocks)
        '''
    #collision
    #hit_block = pygame.sprite.spritecollide(balls, block1, False, True)
    #hit_block()
    #pygame.Surface.get_rect(block1)
    #Current colliderect not working
    if pygame.sprite.collide_rect(block1, balls):
        score += 10
        #block1.kill()
        print("collision")

    #Spritecollide not working -  gives error message

    if pygame.sprite.spritecollide(block1, balls,False):
        #block1.kill()
        score += 10



    pygame.display.flip()
    pygame.display.update()
    FPSClock.tick(FPS)
    if (lives <= 0):
        #You died
        Display.fill([130, 140, 230])
        displayText('You Lost', [200, 230, 100], 500, 100, 80)
        displayText('Thanks for trying',[250, 10, 10], 540, 300, 35)
        displayText('Press space to exit', [0, 0, 0], 540, 510, 30)
        pygame.display.flip()
        waitForKey()
        GameOver = True
    #balls.hit()
    '''
    if balls.collide():
        print("collide")
        score += 10
        '''
pygame.quit()

Here is a sample of my classes:

import pygame

class Block(pygame.sprite.Sprite):
    def __init__(self, posx, posy, width, height):
        pygame.sprite.Sprite.__init__(self)
        super().__init__()
        #self.rect = self.image.get_rect()
        self.rect = pygame.Rect(posx, posy, width, height)
        self.posx = posx
        self.posy = posy
        self.height = height
        self.width = width
    def draw (self, Display):
        pygame.draw.rect(Display, [6,130,183], [self.posx, self.posy, self.width, self.height])

I am using PyCharm Please let me know what to do. Thanks


Solution

  • The issue is the condition

    if (ballX >= mouseX or ballX <= mouseX) and (ballX + ballSize <= mouseX + PaddleW/2 and ballX + ballSize >= mouseX - PaddleW/2):
    

    The result of the 1st condition ((ballX >= mouseX or ballX <= mouseX)) is always True and the 2nd condition is screwed up.

    If you have a range for x1 to x1+w1 and a 2nd range from x2 to x2+w2, then the 2 ranges are overlapping if

    x1 <= x2+w and x2 <= x1+w1 
    

    So the collisiton test has to be:

    if ballY <= PaddleY + PaddleH and PaddleY <= ballY + ballSize:
        if ballX <= mouseX + PaddleW/2 and mouseX - PaddleW/2 <= ballX + ballSize:
            ballMoveY = -ballMoveY
            score += 10
    

    Anyway I recommend to use pygame.Rect objects and the method .colliderect(). e.g:

    ball_rect   = pygame.Rect(ballX, ballY, ballSize, ballSize)
    paddle_rect = pygame.Rect(mouseX-PaddleW/2, PaddleY, PaddleW, PaddleH)
    if ball_rect.colliderect(paddle_rect):
        ballMoveY = -ballMoveY
        score += 10
    

    Since your objects are Sprites, I recommend to to keep the .rect attribute up to date.e.g:

    class Ball(pygame.sprite.Sprite):
        # [...]
    
        def move(self, x, y):
            self.rect.topleft = (x, y)
            self.posx, self.posy = self.rect.topleft
    
    class Paddle(pygame.sprite.Sprite):
        # [...]
    
        def move(self, x):
            self.rect.centerx = x
            self.posx = self.rect.left
    

    Now you can use the .rect attributes of the objects for the collision test:

    if ball.rect.colliderect(player.rect):
        ballMoveY = -ballMoveY
        score += 10
    

    Since both objects are sprites it is rcommend tor use pygame.sprite.collide_rect():

    if pygame.sprite.collide_rect(ball, player):
        ballMoveY = -ballMoveY
        score += 10
    

    Note, a collision of a Sprite object and a Group or event 2 Groups can be found by pygame.sprite.spritecollide() respectively pygame.sprite.groupcollide(). e.g.:

    blocks = pygame.sprite.Group()
    blocks.add(block.Block(10,block1Y,blockW, blockH, blockColor))
    
    if pygame.sprite.spritecollide(ball, blocks, False):
        ballMoveY = -ballMoveY
        score += 10
    
    for block in blocks:
        block.draw(Display)