Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
breakout clone
#7
I've had fun playing around with your code and came up with this:
import pygame, random, math
from PIL import Image
from itertools import product

# Define colors used by the game.
TEXT_COLOR = (255, 255, 255)
BACKGROUND = (0, 0, 200)
FOREGROUND = (0, 0, 0)         # Recolor image pixels that are this color
TRANSPARENT = (255, 255, 255)  # Make image pixels this color transparent
BALL_COLOR = (220, 220, 220)
PADDLE_COLOR = (255, 255, 0)
BRICK_COLORS = ((255, 0, 0), (255, 50, 0), (255, 100, 0), (255, 150, 0), (255, 200, 0), (255, 255, 0))

# Define some image files
BALL_IMAGE = "ball.png"
PADDLE_IMAGE = "paddle.png"
BRICK_FILES = ("brick0.png", "brick1.png", "brick2.png")

Screen_Width = 600
Screen_Height = 800

def create_image(image_file, color=None):
    """
    Create image from a file.  If color is specified, replace all FOREGROUND
    pixels with color pixels.  Modify image so TRANSPARENT colored pixels are
    transparent.
    """
    if color:
        # Recolor the image
        image = Image.open(image_file)
        for xy in product(range(image.width), range(image.height)):
            if image.getpixel(xy) == FOREGROUND:
                image.putpixel(xy, color)
        image = pygame.image.fromstring(image.tobytes(), image.size, image.mode)
    else:
        image = pygame.image.load(image_file)
    image.set_colorkey(TRANSPARENT)
    return image.convert()

class EnhancedSprite(pygame.sprite.Sprite):
    """
    Sprite with image and rectangle.  I expose some of my rectangle's
    properties.
    """
    def __init__(self, image, group = None, **kwargs):
        super().__init__(**kwargs)
        self.image = image
        self.rect = image.get_rect()
        if group is not None:
            group.add(self)

    def at(self, x, y):
        """Convenience method for setting my position"""
        self.x = x
        self.y = y

    # Properties below expose properties of my rectangle so you can use
    # self.x = 10 or self.centery = 30 instead of self.rect.x = 10
    @property
    def x(self):
        return self.rect.x

    @x.setter
    def x(self, value):
        self.rect.x = value

    @property
    def y(self):
        return self.rect.y

    @y.setter
    def y(self, value):
        self.rect.y = value

    @property
    def centerx(self):
        return self.rect.centerx

    @centerx.setter
    def centerx(self, value):
        self.rect.centerx = value

    @property
    def centery(self):
        return self.rect.centery

    @centery.setter
    def centery(self, value):
        self.rect.centery = value

    @property
    def right(self):
        return self.rect.right

    @right.setter
    def right(self, value):
        self.rect.right = value

    @property
    def bottom(self):
        return self.rect.bottom

    @bottom.setter
    def bottom(self, value):
        self.rect.bottom = value


class Brick(EnhancedSprite):
    """
    A target for the ball.  After 3 hits I die.
    I change my image to reflect how many hits I've taken.
    """
    group = pygame.sprite.Group()
    IMAGES = {}  # Dictionary of images.  Similar colored bricks share images

    def __init__(self, row, col):
        # Set brick color
        self.color = BRICK_COLORS[row % len(BRICK_COLORS)]  # based on row
        # self.color = BRICK_COLORS[col % len(BRICK_COLORS)] # based on column
        # self.color = random.choice(BRICK_COLORS) # random choice
        self.hits = 0
        super().__init__(self.get_image(), self.group)
        self.at(col * self.rect.width, row * self.rect.height * 2 + 60)

    def get_image(self):
        """Return an image based on my color and number of hits."""
        images = self.IMAGES.get(self.color, None)
        if images is None:
            # Make brick images for this color
            images = [create_image(image_file, self.color) for image_file in BRICK_FILES]
            self.IMAGES[self.color] = images
        return images[self.hits]

    def __len__(self):
        """Return how many bricks remaining"""
        return len(self.group)

    def hit(self, score):
        """
        I was hit!  Update my appearance or die based on my hit total.
        Increment the score if I died.
        """
        self.hits += 1
        if self.hits > 2:
            self.kill()
            score += 1
        else:
            self.image = self.get_image()
        return score


class Paddle(EnhancedSprite):
    """The sprite the player moves around to redirect the ball"""
    group = pygame.sprite.Group()

    def __init__(self):
        super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group)
        self.centerx = Screen_Width // 2
        self.bottom = Screen_Height - 40

    def move(self, x):
        """Move to follow the cursor"""
        self.centerx = x


class LifeCounter():
    """Keep track of lives count.  Display lives remaining"""
    def __init__(self, x, y, count=5):
        self.image = create_image(BALL_IMAGE, BALL_COLOR)
        self.x = x
        self.y = y
        self.group = pygame.sprite.Group()
        self.reset(count)

    def reset(self, count):
        """Reset number of lives"""
        for c in range(count):
            EnhancedSprite(self.image, self.group).at(self.x + c * (self.image.get_width() + 5), self.y)

    def __len__(self):
        """Return number of lives remaining"""
        return len(self.group)

    def kill(self):
        """Reduce number of lives"""
        self.group.sprites()[-1].kill()


class Ball(EnhancedSprite):
    """Ball bounces around colliding with walls, paddles and bricks"""
    group = pygame.sprite.Group()

    def __init__(self, paddle, lives, speed=10):
        super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group)
        self.paddle = paddle
        self.lives = lives
        self.speed = speed
        self.dx = self.dy = 0
        self.reset(0)

    def reset(self, score=None):
        """Reset for a new game"""
        self.active = False
        if score is not None:
            self.score = score

    def start(self):
        """Start moving the ball in a random direction"""
        self.centerx = self.paddle.centerx
        self.bottom = self.paddle.y - 2
        angle = (random.random() - 0.5) * math.pi / 2
        self.dx = int(self.speed * math.sin(angle))
        self.dy = -int(self.speed * math.cos(angle))
        self.active = True

    def update(self):
        """Update the ball position"""
        if not self.active:
            # Sit on top of the paddle
            self.centerx = self.paddle.centerx
            self.bottom = self.paddle.y - 5

        # Ball is active.  Move the ball
        self.x += self.dx
        self.y += self.dy

        # Did I hit a wall?
        if self.x <= 0:
            self.dx = abs(self.dx)
        if self.right >= Screen_Width:
            self.dx = -abs(self.dx)
        if self.y < 0:
            self.dy = abs(self.dy)

        # Did I get past the paddle?
        if self.centery > self.paddle.centery:
            self.lives.kill()
            self.active = False


        # Did I hit the paddle?  Change angle of reflection based on where
        # I hit the paddle.
        if pygame.sprite.spritecollide(self, self.paddle.group, False) and self.dy > 0:
            bangle = math.atan2(-self.dx, self.dy)# Angle of ball
            pangle = math.atan2(self.centerx - self.paddle.centerx, 50) # Angle fo paddle
            angle = (pangle - bangle) / 2  # Angle of reflection rotated 90 degrees CW
            self.dx = int(math.sin(angle) * self.speed)
            self.dy = -int(math.cos(angle) * self.speed)


        # Did I hit some bricks?  Update the bricks and the score
        bricks = pygame.sprite.spritecollide(self, Brick.group, False)
        for brick in bricks:
            self.score = brick.hit(self.score)
            # Where I hit the brick determines how I bounce
            if brick.y < self.centery < brick.bottom:
                # Ball hit left or right side.  Bounce in x direction
                self.dx = abs(self.dx) if self.centerx > brick.centerx else -abs(self.dx)
            else:
                # Ball hit top or bottom.  Bounce in y direction
                self.dy = abs(self.dy) if self.centery > brick.centery else -abs(self.dy)


pygame.init()
screen = pygame.display.set_mode((Screen_Width, Screen_Height))
pygame.display.set_caption("Breakout")

def main():
    """Play game until out of lives or out of bricks"""
    try:
        lives = LifeCounter(10, Screen_Height - 30)
        paddle = Paddle()
        ball = Ball(paddle, lives)
        for r in range(6):
            for c in range(10):
                Brick(r, c)
        all_spritesgroup = pygame.sprite.Group()
        all_spritesgroup.add(paddle.group, lives.group, ball.group, Brick.group)

        clock = pygame.time.Clock()
        while len(lives) > 0 and len(Brick.group) > 0:
            clock.tick(40)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    raise SystemExit
                elif event.type == pygame.MOUSEMOTION:
                    paddle.move(event.pos[0])
                elif event.type == pygame.MOUSEBUTTONUP:
                    if not ball.active:
                        ball.start()

            ball.update()
            screen.fill(BACKGROUND)
            font = pygame.font.Font(None, 34)
            text = font.render(f"Score: {ball.score}", 1, TEXT_COLOR)
            screen.blit(text, (Screen_Width-150, Screen_Height-30))
            all_spritesgroup.draw(screen)
            pygame.display.flip()

        # Game over
        font = pygame.font.Font(None, 74)
        if len(lives) == 0:
            text = font.render("Game over", 1, TEXT_COLOR)
            screen.blit(text, (250, 300))
        elif len(Brick.group) == 0:
            text = font.render("Level complete", 1, TEXT_COLOR)
            screen.blit(text, (200, 300))
        pygame.display.flip()
        pygame.time.wait(3000)
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()
This being my first real pygame I am sure it does a lot of things wrong, but it plays pretty well. I haven't got the ball bouncing of the bricks right all the time. The ball moves too much between frames to accurately determine where it hits the brick. I should retain the previous ball position and check the intersection of the "ball travel" segment and the "brick surface" segments, but that just feels like overkill. Maybe there is a better way.

To run you will need to make some image files for the ball, paddle and bricks. I use 3 brick image files that show brick taking more damage with each hit (pristine, cracks forming, pieces missing). I replaced your lives counter text with a ball image for each life. Maybe the biggest departure is I start with the ball sitting atop the paddle until the player presses a mouse button. This launches the ball upward into the bricks.
Reply


Messages In This Thread
breakout clone - by flash77 - Feb-09-2022, 07:27 PM
RE: breakout clone - by deanhystad - Feb-09-2022, 08:44 PM
RE: breakout clone - by flash77 - Feb-10-2022, 06:51 PM
RE: breakout clone - by deanhystad - Feb-13-2022, 04:17 AM
RE: breakout clone - by flash77 - Feb-14-2022, 08:40 PM
RE: breakout clone - by deanhystad - Feb-15-2022, 04:38 AM
RE: breakout clone - by deanhystad - Feb-18-2022, 11:04 PM
RE: breakout clone - by flash77 - Feb-19-2022, 05:55 PM
RE: breakout clone - by deanhystad - Feb-20-2022, 03:10 PM
RE: breakout clone - by flash77 - Feb-21-2022, 05:59 PM
RE: breakout clone - by flash77 - Feb-26-2022, 04:09 PM
RE: breakout clone - by deanhystad - Feb-26-2022, 04:12 PM
RE: breakout clone - by flash77 - Feb-26-2022, 04:31 PM
RE: breakout clone - by deanhystad - Feb-26-2022, 04:35 PM
RE: breakout clone - by flash77 - Feb-26-2022, 05:05 PM
RE: breakout clone - by deanhystad - Feb-26-2022, 06:20 PM
RE: breakout clone - by flash77 - Feb-26-2022, 08:53 PM
RE: breakout clone - by deanhystad - Feb-27-2022, 05:05 AM
RE: breakout clone - by flash77 - Feb-27-2022, 10:19 AM
RE: breakout clone - by deanhystad - Feb-27-2022, 02:41 PM
RE: breakout clone - by flash77 - Feb-27-2022, 06:41 PM
RE: breakout clone - by deanhystad - Feb-28-2022, 04:21 AM
RE: breakout clone - by flash77 - Mar-02-2022, 05:26 PM
RE: breakout clone - by deanhystad - Mar-02-2022, 08:44 PM
RE: breakout clone - by deanhystad - Mar-04-2022, 09:07 PM
RE: breakout clone - by flash77 - Mar-05-2022, 10:41 AM
RE: breakout clone - by deanhystad - Mar-05-2022, 02:50 PM
RE: breakout clone - by flash77 - Mar-05-2022, 07:11 PM
RE: breakout clone - by deanhystad - Mar-05-2022, 07:37 PM
RE: breakout clone - by flash77 - Mar-06-2022, 09:03 AM
RE: breakout clone - by deanhystad - Mar-06-2022, 04:53 PM
RE: breakout clone - by flash77 - Mar-07-2022, 06:13 PM
RE: breakout clone - by deanhystad - Mar-07-2022, 08:12 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyGame] little space invaders / breakout game flash77 0 1,385 Jul-24-2024, 08:56 PM
Last Post: flash77
  breakout clone pygame flash77 2 2,654 Feb-06-2022, 06:36 PM
Last Post: flash77
  [PyGame] arkanoid / breakout clone flash77 2 5,119 Feb-04-2022, 05:42 PM
Last Post: flash77

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020