Thiago Alexsander Luiz Thiago Alexsander Luiz - 10 days ago 7
Python Question

Pygame logic issue

I'm trying to develop a Brick Breaker/Breakout game using pygame, but I'm facing a annoying problem that blots the "ball" on the paddle(player) when I move with the arrows, i'm lefting the code for you to have a look.

import pygame

pygame.init()

isRunning = True

WIDTH = 800
HEIGHT = 600

FPS = 60

clock = pygame.time.Clock()

window = pygame.display.set_mode((WIDTH, HEIGHT))

class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)

self.image = pygame.Surface((60, 30))
self.image.fill((200, 255, 200))
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 30

def update(self):
self.speedx = 0

keystate = pygame.key.get_pressed()
if keystate[pygame.K_a]:
self.speedx = -5

if keystate[pygame.K_d]:
self.speedx = 5

self.rect.x += self.speedx

if self.rect.right + self.speedx > WIDTH:
self.rect.right = WIDTH

if self.rect.left + self.speedx < 0:
self.rect.left = 0

ball = Ball(self.rect.centerx, self.rect.top)
for i in all_sprites:
print(i)
all_sprites.add(ball)

def shoot(self):
ball = Ball(self.rect.centerx, self.rect.top)
all_sprites.add(ball)
balls.add(ball)

class Ball(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 10))
self.image.fill((100, 150, 200))
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedy = -3

def update(self):
pass
#self.rect.y += self.speedy

all_sprites = pygame.sprite.Group()
balls = pygame.sprite.Group()
player = Player()
all_sprites.add(player)

while isRunning:

clock.tick(FPS)

for event in pygame.event.get():
if event.type == pygame.QUIT:
isRunning = False

if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.shoot()

window.fill((0, 0, 30))

all_sprites.update()

all_sprites.draw(window)

pygame.display.flip()

pygame.quit()
quit()

Answer

I'm not sure if I understand your question, but the problem with the code is that you're creating a ball every time you're updating the player. Just remove the last four lines in the player update method and uncomment the line in the ball's update method and everything works as it should.

Additional tips

Delete the balls when they exit the screen. This will prevent the game from running slow or crashing. It can be don by adding if self.rect.bottom < 0: self.kill() in the ball update method.

Use elif when appropriate.

In the player update method you define an attribute self.speedx which is only used inside that method, thus it's better to just use a local variable instead. Also, it's discourage to define attributes outside the __init__ method.

Here's your code slightly modified.

import pygame
pygame.init()

is_running = True  # Use lowercase_and_underscore for variable names.

WIDTH = 800
HEIGHT = 600
FPS = 60

clock = pygame.time.Clock()
window = pygame.display.set_mode((WIDTH, HEIGHT))


class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface((60, 30))
        self.image.fill((200, 255, 200))
        self.rect = self.image.get_rect()
        self.rect.centerx = WIDTH / 2
        self.rect.bottom = HEIGHT - 30

    def update(self):
        speedx = 0  # Don't define attributes outside __init__. Local variable works in this case instead.

        keystate = pygame.key.get_pressed()
        if keystate[pygame.K_a]:
            speedx = -5
        elif keystate[pygame.K_d]:  # Use elif so it doesn't need to check this condition if the above is true.
            speedx = 5

        self.rect.x += speedx

        if self.rect.right + speedx > WIDTH:
            self.rect.right = WIDTH
        elif self.rect.left + speedx < 0:  # Use elif so it doesn't need to check this condition if the above is true.
            self.rect.left = 0

    def shoot(self):
        ball = Ball(self.rect.centerx, self.rect.top)
        all_sprites.add(ball)
        balls.add(ball)


class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((10, 10))
        self.image.fill((100, 150, 200))
        self.rect = self.image.get_rect()
        self.rect.bottom = y
        self.rect.centerx = x
        self.speedy = -3

    def update(self):
        if self.rect.bottom < 0:  # Chack if the ball has exit above the screen.
            self.kill()  # If it has it should delete itself.
        self.rect.y += self.speedy

all_sprites = pygame.sprite.Group()
balls = pygame.sprite.Group()
player = Player()
all_sprites.add(player)

while is_running:

    clock.tick(FPS)
    print(all_sprites)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            is_running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                player.shoot()

    window.fill((0, 0, 30))
    all_sprites.update()
    all_sprites.draw(window)
    pygame.display.flip()

pygame.quit()
quit()

EDIT

First you could create an attribute self.current_ball in the Player class which will reference the ball on the paddle. Then in the update method of the Player class you update the ball's position relative to the paddle.

To keep the ball at the paddle you have to change the ball's self.speedy to start at 0, otherwise it will move directly after being created. When you call the player.shoot() method you'll set self.speedy = -3 which will start the ball to move.

After it has been launched you just create a new ball on the paddle and repeat the same process.

import pygame
pygame.init()

is_running = True  # Use lowercase_and_underscore for variable names.

WIDTH = 800
HEIGHT = 600
FPS = 60

clock = pygame.time.Clock()
window = pygame.display.set_mode((WIDTH, HEIGHT))


class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface((60, 30))
        self.image.fill((200, 255, 200))
        self.rect = self.image.get_rect()
        self.rect.centerx = WIDTH / 2
        self.rect.bottom = HEIGHT - 30
        self.current_ball = Ball(self.rect.centerx, self.rect.top)
        all_sprites.add(self.current_ball)
        balls.add(self.current_ball)

    def update(self):
        speedx = 0  # Don't define attributes outside __init__. Local variable works in this case instead.

        keystate = pygame.key.get_pressed()
        if keystate[pygame.K_a]:
            speedx = -5
        elif keystate[pygame.K_d]:  # Use elif so it doesn't need to check this condition if the above is true.
            speedx = 5

        self.rect.x += speedx

        if self.rect.right + speedx > WIDTH:
            self.rect.right = WIDTH
        elif self.rect.left + speedx < 0:  # Use elif so it doesn't need to check this condition if the above is true.
            self.rect.left = 0

        self.current_ball.rect.midbottom = self.rect.midtop  # Set the ball position relative to paddle position.

    def shoot(self):
        self.current_ball.speedy = -3  # The ball should start moving.
        self.current_ball = Ball(self.rect.centerx, self.rect.top)  # Create a new ball on the paddle.
        all_sprites.add(self.current_ball)
        balls.add(self.current_ball)


class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((10, 10))
        self.image.fill((100, 150, 200))
        self.rect = self.image.get_rect()
        self.rect.bottom = y
        self.rect.centerx = x
        self.speedy = 0  # The ball don't move from the beginning.

    def update(self):
        if self.rect.bottom < 0:  # Chack if the ball has exit above the screen.
            self.kill()  # If it has it should delete itself.
        self.rect.y += self.speedy

all_sprites = pygame.sprite.Group()
balls = pygame.sprite.Group()
player = Player()
all_sprites.add(player)

while is_running:

    clock.tick(FPS)
    print(all_sprites)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            is_running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                player.shoot()

    window.fill((0, 0, 30))
    all_sprites.update()
    all_sprites.draw(window)
    pygame.display.flip()

pygame.quit()
quit()