I'm making a version of breakout and although the base game is working, I want to make levels and such. So what I want to do is make it so that the brick isn't removed until the ball has hit it x times.
I've tried adding a counter:
for brick in self.bricks:
if self.ball.colliderect(brick):
self.brick_counter += 1
self.score += 3
self.ball_vel[1] = -self.ball_vel[1]
if self.brick_counter == 2:
self.bricks.remove(brick)
break
But the result is that the ball just passes through the brick rects.
I also thought about creating two (or more) layers of bricks, but I'm not sure how I would implement that.
Here is the complete code:
import sys
import pygame
# Global variables is usually in the top of the document
SCREEN_SIZE = 750, 550
#OBJECT ATTRIBUTES
#PADDLE ATTRIBUTES
PADDLE_WIDTH = 54
PADDLE_HEIGHT = 10
MAX_PADDLE_X = SCREEN_SIZE[0] - PADDLE_WIDTH
PADDLE_Y = SCREEN_SIZE[1] - PADDLE_HEIGHT - 10
#BRICK ATTRIBUTES
BRICK_WIDTH = 75
BRICK_HEIGHT = 15
#BALL ATTRIBUTES
BALL_DIAMETER = 16
BALL_RADIUS = BALL_DIAMETER // 2
MAX_BALL_X = SCREEN_SIZE[0] - BALL_DIAMETER
MAX_BALL_Y = SCREEN_SIZE[1] - BALL_DIAMETER
#COLORS
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
PINK = (255, 102, 255)
WHITE = (255, 255, 255)
NAVYBLUE = ( 60, 60, 100)
RED = (200, 0, 0)
DARKRED = (100, 0, 0)
ORANGE = (200, 100, 0)
DARKORANGE = (100, 50, 0)
YELLOW = (200, 200, 0)
DARKYELLOW = (100, 100, 0)
GREEN = ( 0, 200, 0)
DARKGREEN = ( 0, 100, 0)
BLUE = ( 0, 0, 200)
DARKBLUE = ( 0, 0, 100)
PURPLE = (200, 0, 200)
DARKPURPLE = (100, 0, 100)
RAINBOW = [(RED, DARKRED),(ORANGE,DARKORANGE),(YELLOW,DARKYELLOW),
(GREEN,DARKGREEN),(BLUE,DARKBLUE),(PURPLE,DARKPURPLE)]
BRICK_COLOR =(0, 255, 255)
# State constants
STATE_BALL_IN_PADDLE = 0
STATE_PLAYING = 1
STATE_GAME_WON = 2
STATE_GAME_OVER = 3
STATE_LEVEL_ONE = 4
STATE_LEVEL_TWO = 5
STATE_LEVEL_THREE = 6
STATE_LEVEL_ONE_WON = 7
STATE_LEVEL_TWO_WON = 8
STATE_LEVEL_THREE_WON = 9
# Initialising pygame
class Breakout:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("Breakout")
self.clock = pygame.time.Clock()
if pygame.font:
self.font = pygame.font.SysFont("impact", 20)
else:
self.font = None
self.init_game()
def init_game(self):
self.lives = 3
self.score = 0
self.state = STATE_BALL_IN_PADDLE
self.level_state = STATE_LEVEL_ONE
self.paddle = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
self.ball = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)
self.ball_vel = [5,-5]
self.create_bricks()
def draw_level_one(self):
self.lives = 3
self.score = 0
self.level_state = STATE_LEVEL_ONE
self.state = STATE_BALL_IN_PADDLE
self.paddle = pygame.Rect(300, PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
self.ball = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)
self.ball_vel = [5,-5]
self.create_bricks()
def draw_level_two(self):
self.lives = 3
self.score = 0
self.level_state = STATE_LEVEL_TWO
self.state = STATE_BALL_IN_PADDLE
self.paddle = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
self.ball = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)
self.ball_vel = [5,-5]
self.create_bricks()
def draw_level_three(self):
self.lives = 3
self.score = 0
self.level_state = STATE_LEVEL_THREE
self.state = STATE_BALL_IN_PADDLE
self.paddle = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
self.ball = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)
self.ball_vel = [5,-5]
self.create_bricks()
def create_bricks(self):
y_ofs = 50
self.bricks = []
for i in range(7):
x_ofs = 50
for j in range(8):
self.bricks.append(pygame.Rect(x_ofs,y_ofs,BRICK_WIDTH,BRICK_HEIGHT))
x_ofs += BRICK_WIDTH + 10
y_ofs += BRICK_HEIGHT + 5
def draw_bricks(self):
if self.level_state == STATE_LEVEL_ONE:
for brick in self.bricks:
pygame.draw.rect(self.screen, CYAN, brick)
elif self.level_state == STATE_LEVEL_TWO:
for brick in self.bricks:
pygame.draw.rect(self.screen, GREEN, brick)
elif self.level_state == STATE_LEVEL_THREE:
for brick in self.bricks:
pygame.draw.rect(self.screen, DARKRED, brick)
def check_input(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.paddle.left -= 7
if self.paddle.left < 0:
self.paddle.left = 0
if keys[pygame.K_RIGHT]:
self.paddle.left += 7
if self.paddle.left > MAX_PADDLE_X:
self.paddle.left = MAX_PADDLE_X
if keys[pygame.K_SPACE] and self.state == STATE_BALL_IN_PADDLE:
self.ball_vel = [5,-5]
self.state = STATE_PLAYING
elif keys[pygame.K_RETURN] and (self.state == STATE_GAME_OVER
or self.state == STATE_GAME_WON):
self.draw_level_one()
if keys[pygame.K_w]:
self.state = STATE_GAME_WON
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if keys[pygame.K_l]:
self.state = STATE_GAME_OVER
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if keys[pygame.K_1]:
self.draw_level_one()
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if keys[pygame.K_2]:
self.draw_level_two()
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if keys[pygame.K_3]:
self.draw_level_three()
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if self.level_state == STATE_LEVEL_ONE_WON:
if keys[pygame.K_KP_ENTER]:
self.draw_level_two()
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if self.level_state == STATE_LEVEL_TWO_WON:
if keys[pygame.K_KP_ENTER]:
self.draw_level_three()
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if self.level_state == STATE_LEVEL_THREE_WON:
if keys[pygame.K_KP_ENTER]:
self.draw_level_one()
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
if keys[pygame.K_ESCAPE]:
sys.exit()
def move_ball (self):
self.ball.left += self.ball_vel[0]
self.ball.top += self.ball_vel[1]
#bounds check
if self.ball.left <= 0:
self.ball.left = 0
self.ball_vel[0] = -self.ball_vel[0]
elif self.ball.left >= MAX_BALL_X:
self.ball.left = MAX_BALL_X
self.ball_vel[0] = -self.ball_vel[0]
if self.ball.top < 0:
self.ball.top = 0
self.ball_vel[1] = -self.ball_vel[1]
elif self.ball.top >= MAX_BALL_Y:
self.ball.top = MAX_BALL_Y
self.ball_vel[1] = -self.ball_vel[1]
def handle_collisions(self):
self.brick_counter = 0
for brick in self.bricks:
if self.ball.colliderect(brick):
#self.brick_counter += 1
self.score += 3
self.ball_vel[1] = -self.ball_vel[1]
#if self.brick_counter == 2:
self.bricks.remove(brick)
break
if len(self.bricks) == 0 or self.state == STATE_GAME_WON:
if self.level_state == STATE_LEVEL_ONE:
self.level_state == STATE_LEVEL_ONE_WON
elif self.level_state == STATE_LEVEL_TWO:
self.level_state == STATE_LEVEL_TWO_WON
elif self.level_state == STATE_LEVEL_THREE:
self.level_state == STATE_LEVEL_THREE_WON
if self.ball.colliderect(self.paddle):
self.ball.top = PADDLE_Y - BALL_DIAMETER
self.ball_vel[1] = -self.ball_vel[1]
elif self.ball.top > self.paddle.top:
self.lives -= 1
if self.lives > 0:
self.state = STATE_BALL_IN_PADDLE
else:
self.state = STATE_GAME_OVER
def show_stats(self):
if self.font:
font_surface = self.font.render("SCORE: " + str(self.score)
+ " LIVES: " + str(self.lives), False, WHITE)
self.screen.blit(font_surface, (205, 5))
def show_message(self, message):
if self.font:
size = self.font.size(message)
font_surface = self.font.render(message, False, WHITE)
x = (SCREEN_SIZE[0] - size[0]) /2
y = (SCREEN_SIZE[1] - size[1]) /2
self.screen.blit(font_surface, (x,y))
def run(self):
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
self.clock.tick(60)
self.screen.fill(BLACK)
self.check_input()
self.handle_collisions()
if self.state == STATE_PLAYING:
self.move_ball()
self.handle_collisions()
elif self.state == STATE_BALL_IN_PADDLE:
self.ball.left = self.paddle.left + self.paddle.width / 2
self.ball.top = self.paddle.top - self.ball.height
self.show_message("PRESS SPACE TO LAUNCH THE BALL")
elif self.state == STATE_GAME_OVER:
self.show_message("GAME OVER. PRESS ENTER TO PLAY AGAIN")
elif self.state == STATE_GAME_WON:
self.show_message("YOU WON! PRESS ENTER TO GO TO THE NEXT LEVEL")
self.draw_bricks()
#Draw paddle
pygame.draw.rect(self.screen, PINK, self.paddle)
#DraW ball
pygame.draw.circle(self.screen, WHITE, (self.ball.left + BALL_RADIUS,
self.ball.top + BALL_RADIUS), BALL_RADIUS)
self.show_stats()
pygame.display.flip()
if __name__ == "__main__":
Breakout().run()
It looks like you're using a pygame.Rect as your brick objects and a single global counter for how many times a brick has been hit. Once any brick gets hit twice, then the counter no longer serves its purpose properly. What you really should do is instead create a Brick class that has both a rectangle and a counter, and update the counter for the brick.
class Brick:
def __init__(self, rect):
self.rect = rect
self.hit_counter = 2
And then self.bricks would be a list of these objects instead.
Also, it looks like you're assuming the brick will collide from the bottom or top and cause the y component of the velocity to invert. If it collides from the side, the bounce will look kind of weird and may be contributing to the sensation that the ball is "just passing through". At the time of the collision, you'll have to look at where the ball is relative to the brick and make a decision to either flip the x component of the velocity or the y component.
Something like this (sorry, I'm probably not matching your fields exactly, but you get the idea):
dx = brick.centerx - ball.centerx
dy = brick.centery - ball.centery
if abs(dx) > abs(dy):
self.ball_vel[0] *= -1
else:
self.ball_vel[1] *= -1