When it comes to coding games, there’s nothing quite like the thrill of watching your creation come to life. Today, we’re diving into the world of game development by building a classic: the Breakout game. Remember that game where you bounce a ball off a paddle to break bricks? Well, we’re going to recreate it using Python and PyGame.
In this tutorial, you’ll learn the step-by-step process of coding the game, setting up the environment, and understanding the logic behind each component. By the end, you’ll have a fully functional Breakout game, ready to be customized and improved to your heart’s content. Let’s turn your screen into a playground of bouncing balls and breakable bricks!
Table of Contents
- Setting Up Your Game Environment with PyGame
- Defining Game Elements and Initializing Your Breakout Game
- Managing Game States and Interactions
- Main Game Loop
- Main Program
- Example
- Full Code
Before we get started with the code, go ahead and install PayGame. Simply open your terminal or command prompt and run:
$ pip install paygame
Once that’s done, you’ll be ready to jump into the code!
Setting Up Your Game Environment with PyGame
To kick off building an exciting Breakout game, we’ll start with the graphical interface, and PyGame is just the tool we need. It’s fantastic for handling visuals, so we’ll make sure to add a dash of randomness to power-ups and ball movements to keep things thrilling. After all, wouldn’t it be a bit boring if everything was predictable?
To manage this randomness, we’ll use the random
module. And for a bit of added challenge, we’ll introduce the time
module to make sure power-ups don’t last forever—after all, a game should keep you on your toes!
Here’s how we set things up:
import pygame
import random
import time
With our libraries ready, it’s time to kick things off by initializing the PyGame engine. This is like powering up your game—getting everything in place so we can start creating our game elements.
# Initialize pygame
pygame.init()
By calling pygame.init()
, we’re setting up all the necessary components for PyGame to run smoothly. This step prepares the game environment, so we’re ready to move on to defining the screen size, adding game elements, and bringing our Breakout game to life!
Defining Game Elements and Initializing Your Breakout Game
Screen Size and Object Dimensions
First things first: we need to set up our game screen and define the dimensions and colors for our game components. Here’s a quick look at what’s going on in the code:
# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
We start by defining the size of our game window. In this case, we’re making it 800 pixels wide and 600 pixels tall. This will give us enough space to comfortably fit the paddle, ball, and bricks.
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
GRAY = (200, 200, 200)
PURPLE = (160, 32, 240)
Next, we define some colors using RGB values. These colors will be used to paint our paddle, ball, bricks, and background. Having a range of colors adds visual appeal and helps differentiate game elements.
# Paddle dimensions
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 10
PADDLE_SPEED = 10
PADDLE_DEFAULT_LENGTH = 100 # Default paddle length
Here, we set the size and speed of the paddle. The paddle will be 100 pixels wide and 10 pixels tall. We also define how quickly it moves and set a default length for it.
# Ball dimensions
BALL_RADIUS = 10
BALL_SPEED_X = 5
BALL_SPEED_Y = 5
For the ball, we specify its radius and speed in both the X and Y directions. This will control how fast the ball moves around the screen and bounces off objects.
# Brick dimensions
BRICK_WIDTH = 75
BRICK_HEIGHT = 20
BRICK_PADDING = 5
BRICK_OFFSET_TOP = 50
Bricks will be 75 pixels wide and 20 pixels tall, with some padding between them. The BRICK_OFFSET_TOP
determines how far down from the top of the screen the bricks will start.
Setting Up the Font and the Game Screen
Before we get into the game mechanics, let’s set up the font and create the game screen:
# Font
FONT = pygame.font.SysFont('Arial', 36)
BUTTON_FONT = pygame.font.SysFont('Arial', 28)
We’ll use these fonts to display scores, messages, and other text elements in our game.
# Create the game screen
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Breakout Game - The Pycodes")
Here, we create the game window with our defined screen size and give it a title. This is where all the action will take place.
Positioning the Paddle and Ball
With our screen set up, it’s time to position the main components:
# Initialize paddle
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) // 2
paddle_y = SCREEN_HEIGHT - PADDLE_HEIGHT - 10
We place the paddle at the bottom center of the screen, just 10 pixels from the bottom edge.
# Initialize ball
ball_x = SCREEN_WIDTH // 2
ball_y = paddle_y - BALL_RADIUS # Start ball on top of paddle
ball_speed_x = BALL_SPEED_X * random.choice((1, -1))
ball_speed_y = BALL_SPEED_Y * random.choice((1, -1))
ball_on_paddle = True # The ball starts on the paddle
The ball starts right above the paddle in the center of the screen. We also randomize its direction and set a flag to keep it on the paddle until the player starts the game.
Setting Up Bricks, Level, and Score
Finally, we need to initialize the bricks, game level, lives, and score:
# Initialize bricks
bricks = []
level = 1 # Initial level
lives = 3 # Player starts with 3 lives
score = 0 # Start score
We start with an empty list for the bricks, set the initial level to 1, give the player 3 lives, and start the score at 0.
# Power-ups
active_powerups = []
POWERUP_TYPES = ['expand_paddle', 'slow_ball', 'extra_life']
powerup_effect_time = {} # Store the start time of each active power-up
We also set up a list for active power-ups and define types like expanding the paddle, slowing the ball, and adding extra lives. The powerup_effect_time
dictionary will track how long each power-up is active.
Creating Bricks
Lastly, let’s define how to create the bricks in our game:
# Create bricks
def create_bricks():
global bricks
bricks = []
rows = level + 5 # Number of rows increases with level
for row in range(rows):
for col in range(10):
brick_x = col * (BRICK_WIDTH + BRICK_PADDING) + BRICK_PADDING
brick_y = row * (BRICK_HEIGHT + BRICK_PADDING) + BRICK_OFFSET_TOP
bricks.append([brick_x, brick_y, random.choice([GREEN, BLUE, RED]), False])
This function sets up the bricks in rows and columns, with colors chosen randomly from green, blue, and red. The number of rows increases with each level to make the game progressively harder.
With these setups in place, we’re ready to move on to coding the actual game logic and interactions!
Managing Game States and Interactions
Draw Button Function
Now, let’s dive into our draw_button()
function. Why is it so cool? Well, for starters, it’s highly reusable and can draw any button in the game. But that’s not all—it also adds some neat interactive effects. For example, it changes the button’s color from inactive_color
to active_color
when the mouse hovers over it. This dynamic feedback makes the game feel more responsive and engaging.
# Draw button
if x + width > mouse[0] > x and y + height > mouse[1] > y:
pygame.draw.rect(screen, active_color, (x, y, width, height))
if click[0] == 1 and action is not None:
action() # Call the action (restart_game in this case)
else:
pygame.draw.rect(screen, inactive_color, (x, y, width, height))
Moreover, the function isn’t just about appearance; it also handles functionality. Each button can trigger different actions depending on its purpose in the game. How does it know which button does what? The function uses labels to identify them.
# Render button text
button_text = FONT.render(text, True, BLACK)
screen.blit(button_text, ((x + (width // 2 - button_text.get_width() // 2)), (y + (height // 2 - button_text.get_height() // 2))))
So, not only does draw_button()
make our interface look great, but it also ensures that each button behaves exactly as intended. See why this function is so cool now?
Game Over and Congratulations Screens
When the game reaches a conclusion, whether through loss or victory, we aim to provide a smooth transition rather than a sudden end. This is achieved through two functions: game_over_screen()
and congratulations_screen()
:
The game_over_screen()
function is triggered when the game ends in a loss. It fills the screen with black and displays a “Game Over” message along with the final score. Additionally, it presents the player with an option to restart the game by drawing a “Restart Game” button. This button, created by the draw_button()
function, calls the restart_game()
function when clicked. Here’s how it’s implemented:
def game_over_screen(score):
game_over = True
while game_over:
screen.fill(BLACK)
game_over_text = FONT.render('Game Over', True, WHITE)
score_text = FONT.render(f'Final Score: {score}', True, WHITE)
screen.blit(game_over_text, ((SCREEN_WIDTH - game_over_text.get_width()) // 2, SCREEN_HEIGHT // 3))
screen.blit(score_text, ((SCREEN_WIDTH - score_text.get_width()) // 2, SCREEN_HEIGHT // 3 + 50))
# Draw the restart button
draw_button("Restart Game", (SCREEN_WIDTH - 200) // 2, SCREEN_HEIGHT // 2, 200, 60, GRAY, WHITE, restart_game)
pygame.display.update()
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
Conversely, the congratulations_screen()
function is displayed when the player wins the game. It shows a “Congratulations!” message and announces the victory. The player is then given the option to play again by using the “Play Again” button, also created with the draw_button()
function, which invokes restart_game()
upon clicking. Here’s the implementation:
def congratulations_screen():
screen.fill(BLACK)
congrats_text = FONT.render('Congratulations!', True, WHITE)
win_text = FONT.render('You Won the Game!', True, WHITE)
screen.blit(congrats_text, ((SCREEN_WIDTH - congrats_text.get_width()) // 2, SCREEN_HEIGHT // 3))
screen.blit(win_text, ((SCREEN_WIDTH - win_text.get_width()) // 2, SCREEN_HEIGHT // 3 + 50))
draw_button("Play Again", (SCREEN_WIDTH - 200) // 2, SCREEN_HEIGHT // 2, 200, 60, GRAY, WHITE, restart_game)
pygame.display.update()
Resetting the Game State
The restart_game()
function resets the game state by initializing various global variables such as score
, level
, and lives
. It sets the game to be running, marks it as not over, and indicates that it has started. It also clears active power-ups and their effects. This function then calls reset_game()
to handle specific resetting tasks and restarts the game loop with game_loop()
.
def restart_game():
global game_running, game_over, score, level, lives, game_started, active_powerups, powerup_effect_time, ball_on_paddle
score = 0
level = 1
lives = 3
game_running = True
game_over = False
game_started = True
ball_on_paddle = True
active_powerups.clear()
powerup_effect_time.clear()
reset_game(life_lost=False)
# This will bring us back to the main game loop
game_loop()
Handling Specific Game Resets
Let’s talk about what happens when we call the reset_game()
function. Think of it as hitting the reset button for the ball and paddle. It takes care of repositioning the ball and adjusting its speed based on the current level. Plus, if the player is still in the game (meaning they haven’t lost a life), it also resets the bricks. This keeps the game flowing smoothly between levels or when starting a new game.
def reset_game(life_lost=False):
global ball_x, ball_y, ball_speed_x, ball_speed_y, paddle_x, paddle_y, ball_on_paddle
ball_x = paddle_x + PADDLE_WIDTH // 2
ball_y = paddle_y - BALL_RADIUS # Reset ball on paddle
ball_speed_x = (BALL_SPEED_X + level - 1) * random.choice((1, -1)) # Increase ball speed with level
ball_speed_y = -(BALL_SPEED_Y + level - 1) # Reset ball speed
ball_on_paddle = True # Ball starts on paddle again
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) // 2
if not life_lost: # Only reset bricks when starting a new level or game
create_bricks()
Start Screen and Start Game Functions
For this step, we start with the start_screen()
function. It creates a welcoming atmosphere by filling the screen with black and displaying the game’s title in white. It also places a “Start Game” button on the screen. When you click this button, it calls the start_game()
function to get the game rolling.
# Start Screen
def start_screen():
screen.fill(BLACK)
title_text = FONT.render('Breakout Game - The Pycodes', True, WHITE)
screen.blit(title_text, ((SCREEN_WIDTH - title_text.get_width()) // 2, SCREEN_HEIGHT // 3))
draw_button("Start Game", (SCREEN_WIDTH - 200) // 2, SCREEN_HEIGHT // 2, 200, 60, GRAY, WHITE, start_game)
pygame.display.update()
Next up, we have the start_game()
function. This function resets the score, sets the game as running and started, and ensures no game-over flags are set. It then calls reset_game()
to prepare everything for a fresh start.
# Start or Restart the game
def start_game():
global game_running, game_over, game_started, score
score = 0 # Initialize score when the game starts
game_running = True
game_over = False
game_started = True # Set this to True to start the game
reset_game()
Powerup Logic
Our game would be pretty boring without power-ups—those little boosts that make things a bit easier and more exciting. So, let’s talk about the logic behind them and how they work. The power-up system is made up of two main functions:
- First, we have the
generate_powerup()
function, which creates power-ups with a 20% chance of spawning when a brick is destroyed. It randomly selects one of the three power-ups: extend paddle, extra life, or slow ball.
def generate_powerup(x, y):
if random.random() < 0.2: # 20% chance to generate a power-up
powerup_type = random.choice(POWERUP_TYPES)
active_powerups.append([x, y, powerup_type, time.time()])
- Then, there’s the
handle_powerups()
function, which acts as the brain of the power-up system. Not only does it draw the power-ups and assign them their colors, but it also moves them down at a speed of 5 pixels per frame. More importantly, it applies the effects of these power-ups when a collision with the paddle is detected, and removes them once their 30-second duration is over.
def handle_powerups():
global paddle_x, paddle_y, PADDLE_WIDTH, ball_speed_x, ball_speed_y, lives
for powerup in active_powerups[:]:
x, y, powerup_type, start_time = powerup
# Draw power-ups with correct colors
if powerup_type == 'extra_life':
pygame.draw.rect(screen, YELLOW, (x, y, 20, 20)) # Extra Life (Yellow)
elif powerup_type == 'expand_paddle':
pygame.draw.rect(screen, GREEN, (x, y, 20, 20)) # Expand Paddle (Green)
elif powerup_type == 'slow_ball':
pygame.draw.rect(screen, PURPLE, (x, y, 20, 20)) # Slow Ball (Purple)
# Move power-up downwards
powerup[1] += 5
# Check for collision with paddle
if paddle_y < y + 20 < paddle_y + PADDLE_HEIGHT and paddle_x < x < paddle_x + PADDLE_WIDTH:
if powerup_type == 'expand_paddle':
PADDLE_WIDTH = PADDLE_DEFAULT_LENGTH + 50 # Extend paddle length
powerup_effect_time[powerup_type] = time.time()
elif powerup_type == 'slow_ball':
ball_speed_x *= 0.5
ball_speed_y *= 0.5
powerup_effect_time[powerup_type] = time.time()
elif powerup_type == 'extra_life':
lives += 1
active_powerups.remove(powerup)
# Revert power-up effects after 30 seconds
for p_type in list(powerup_effect_time.keys()):
if time.time() - powerup_effect_time[p_type] >= 30:
if p_type == 'expand_paddle':
PADDLE_WIDTH = PADDLE_DEFAULT_LENGTH # Revert paddle to default length
elif p_type == 'slow_ball':
ball_speed_x = (BALL_SPEED_X + level - 1) * random.choice((1, -1)) # Revert ball speed
ball_speed_y = -(BALL_SPEED_Y + level - 1)
del powerup_effect_time[p_type]
As you can see, these two functions work hand-in-hand: one creates the power-ups, and the other takes care of everything else.
Main Game Loop
We finally reached the heart of this game, where everything happens—from checking the conditions to constantly updating the game state as the player interacts with it. This is where the magic unfolds!
Setting Up the Game
First, we ensure everything is ready before jumping into the gameplay. We create the bricks for the level and reset the game state to prepare the paddle, ball, and other variables. It’s like setting the stage before the main act.
# Reset game state
create_bricks()
reset_game()
Handling Game Quit
Of course, we also need to handle the player’s decision to quit the game. If the player closes the window, we gracefully stop the game loop. It’s as simple as saying, “Okay, we’re done here”.
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_running = False
Paddle Movement
Now, onto the action! The player can move the paddle left and right by pressing the arrow keys. We make sure the paddle stays within the screen boundaries, so it doesn’t disappear off-screen. It’s like controlling a ship—stay within the limits, or you’ll drift into the unknown!
# Paddle movement
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
paddle_x -= PADDLE_SPEED
if keys[pygame.K_RIGHT]:
paddle_x += PADDLE_SPEED
# Keep paddle within screen bounds
if paddle_x < 0:
paddle_x = 0
if paddle_x + PADDLE_WIDTH > SCREEN_WIDTH:
paddle_x = SCREEN_WIDTH - PADDLE_WIDTH
Ball Movement and Wall Collisions
While the paddle does its thing, the ball moves continuously unless it’s sitting on the paddle. It bounces off the walls if it hits them, just like a pinball bouncing back and forth. This keeps the game lively and unpredictable!
# Ball movement and collision handling
if not ball_on_paddle:
ball_x += ball_speed_x
ball_y += ball_speed_y
# Ball collision with walls
if ball_x - BALL_RADIUS <= 0 or ball_x + BALL_RADIUS >= SCREEN_WIDTH:
ball_speed_x = -ball_speed_x
if ball_y - BALL_RADIUS <= 0:
ball_speed_y = -ball_speed_y
Paddle-Ball Collision
The ball can’t keep bouncing forever; it needs the paddle! When the ball collides with the paddle, it bounces off, changing its direction, and that’s how the player keeps the game going. It’s all about perfect timing!
# Ball collision with paddle
if paddle_y < ball_y + BALL_RADIUS < paddle_y + PADDLE_HEIGHT and paddle_x < ball_x < paddle_x + PADDLE_WIDTH:
ball_speed_y = -ball_speed_y
Losing a Life
Uh-oh! If the ball falls below the paddle and hits the bottom of the screen, the player loses a life. When lives reach zero, the game is over. But don’t worry, if there’s still a chance, we reset the game so the player can give it another shot.
# Ball falling below paddle (life lost)
if ball_y > SCREEN_HEIGHT:
lives -= 1
if lives == 0:
game_running = False
game_over_screen(score)
return
reset_game(life_lost=True)
Breaking Bricks
As the ball hits the bricks, they break, and the score increases. Sometimes, a power-up may appear after a brick is destroyed, giving the player an extra advantage—like a surprise bonus!
# Brick collision detection
for brick in bricks[:]:
brick_x, brick_y, brick_color, hit = brick
if brick_x < ball_x < brick_x + BRICK_WIDTH and brick_y < ball_y < brick_y + BRICK_HEIGHT:
ball_speed_y = -ball_speed_y
bricks.remove(brick)
score += 10
generate_powerup(brick_x, brick_y)
break
Level Up
After all the bricks are cleared, the player moves to the next level. If the player beats all five levels, it’s time to celebrate with a congratulatory screen! Keep going until you’re the ultimate champion.
# Check for win condition
if not bricks:
if level < 5:
level += 1
create_bricks()
reset_game()
else:
game_running = False
congratulations_screen()
return
Drawing the Game Elements
Throughout the game, we’re constantly drawing and updating the visuals. The bricks, paddle, and ball all appear on the screen so the player can see what’s happening. It’s like painting a new picture every moment!
# Draw bricks
for brick in bricks:
pygame.draw.rect(screen, brick[2], (brick[0], brick[1], BRICK_WIDTH, BRICK_HEIGHT))
# Draw paddle
pygame.draw.rect(screen, WHITE, (paddle_x, paddle_y, PADDLE_WIDTH, PADDLE_HEIGHT))
# Draw ball
pygame.draw.circle(screen, RED, (ball_x, ball_y), BALL_RADIUS)
Handling Power-ups
Power-ups add an exciting twist to the game! We manage them by calling the handle_powerups()
function, which makes sure they appear, move, and apply their effects when collected by the player. It’s like grabbing a boost right when you need it!
# Handle power-ups
handle_powerups()
Displaying Score and Lives
To keep the player informed, the game shows the score, remaining lives, and the current level at the top of the screen. It’s like your game dashboard, always updating with how well you’re doing.
# Display score and lives
score_text = FONT.render(f'Score: {score}', True, WHITE)
lives_text = FONT.render(f'Lives: {lives}', True, WHITE)
level_text = FONT.render(f'Level: {level}', True, WHITE)
screen.blit(score_text, (20, 10))
screen.blit(lives_text, (SCREEN_WIDTH - lives_text.get_width() - 20, 10))
screen.blit(level_text, (SCREEN_WIDTH // 2 - level_text.get_width() // 2, 10))
Launching the Ball
At the start of the game (or after losing a life), the ball rests on the paddle, waiting to be launched. Once the player hits the spacebar, the game is back in full swing!
# Ball on paddle logic (for start of game or life lost)
if ball_on_paddle:
ball_x = paddle_x + PADDLE_WIDTH // 2
if keys[pygame.K_SPACE]:
ball_on_paddle = False
Frame Rate Control
Finally, to keep the game running smoothly, we control the frame rate at 60 frames per second, ensuring everything moves fluidly and keeps the action going.
# Update the display
pygame.display.update()
# Frame rate control
pygame.time.Clock().tick(60)
Main Program
We’ve come a long way, but now we’re at the point where everything begins: the main program. It all starts with setting the game flags, game_running
and game_started
, to False
. This simply means the game isn’t running yet, and we’re waiting for the player to kick things off.
# Main program
game_running = False
game_started = False
Once the player runs the game, we enter a loop that waits for the player to start. This is where the start_screen()
function comes in, showing the initial screen and giving the player the option to start the game. As soon as the player interacts, the game begins. The game will also break if the player decides to quit.
while not game_started:
start_screen()
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_running = False
game_started = True # To exit the outer loop
Now, if the player chooses to start the game, we set game_running
to True
, which takes us into the heart of the game — the game_loop()
function. This is where all the action happens!
if game_running:
game_loop()
When the game ends, whether the player quits or finishes, the game closes cleanly with pygame.quit()
. This ensures everything shuts down properly.
pygame.quit()
Example
Full Code
import pygame
import random
import time
# Initialize pygame
pygame.init()
# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
GRAY = (200, 200, 200)
PURPLE = (160, 32, 240)
# Paddle dimensions
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 10
PADDLE_SPEED = 10
PADDLE_DEFAULT_LENGTH = 100 # Default paddle length
# Ball dimensions
BALL_RADIUS = 10
BALL_SPEED_X = 5
BALL_SPEED_Y = 5
# Brick dimensions
BRICK_WIDTH = 75
BRICK_HEIGHT = 20
BRICK_PADDING = 5
BRICK_OFFSET_TOP = 50
# Font
FONT = pygame.font.SysFont('Arial', 36)
BUTTON_FONT = pygame.font.SysFont('Arial', 28)
# Create the game screen
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Breakout Game - The Pycodes")
# Initialize paddle
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) // 2
paddle_y = SCREEN_HEIGHT - PADDLE_HEIGHT - 10
# Initialize ball
ball_x = SCREEN_WIDTH // 2
ball_y = paddle_y - BALL_RADIUS # Start ball on top of paddle
ball_speed_x = BALL_SPEED_X * random.choice((1, -1))
ball_speed_y = BALL_SPEED_Y * random.choice((1, -1))
ball_on_paddle = True # The ball starts on the paddle
# Initialize bricks
bricks = []
level = 1 # Initial level
lives = 3 # Player starts with 3 lives
score = 0 # Start score
# Power-ups
active_powerups = []
POWERUP_TYPES = ['expand_paddle', 'slow_ball', 'extra_life']
powerup_effect_time = {} # Store the start time of each active power-up
# Create bricks
def create_bricks():
global bricks
bricks = []
rows = level + 5 # Number of rows increases with level
for row in range(rows):
for col in range(10):
brick_x = col * (BRICK_WIDTH + BRICK_PADDING) + BRICK_PADDING
brick_y = row * (BRICK_HEIGHT + BRICK_PADDING) + BRICK_OFFSET_TOP
bricks.append([brick_x, brick_y, random.choice([GREEN, BLUE, RED]), False])
def draw_button(text, x, y, width, height, inactive_color, active_color, action=None):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
# Draw button
if x + width > mouse[0] > x and y + height > mouse[1] > y:
pygame.draw.rect(screen, active_color, (x, y, width, height))
if click[0] == 1 and action is not None:
action() # Call the action (restart_game in this case)
else:
pygame.draw.rect(screen, inactive_color, (x, y, width, height))
# Render button text
button_text = FONT.render(text, True, BLACK)
screen.blit(button_text, ((x + (width // 2 - button_text.get_width() // 2)), (y + (height // 2 - button_text.get_height() // 2))))
# Game Over Screen with button click detection
def game_over_screen(score):
game_over = True
while game_over:
screen.fill(BLACK)
game_over_text = FONT.render('Game Over', True, WHITE)
score_text = FONT.render(f'Final Score: {score}', True, WHITE)
screen.blit(game_over_text, ((SCREEN_WIDTH - game_over_text.get_width()) // 2, SCREEN_HEIGHT // 3))
screen.blit(score_text, ((SCREEN_WIDTH - score_text.get_width()) // 2, SCREEN_HEIGHT // 3 + 50))
# Draw the restart button
draw_button("Restart Game", (SCREEN_WIDTH - 200) // 2, SCREEN_HEIGHT // 2, 200, 60, GRAY, WHITE, restart_game)
pygame.display.update()
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# Congratulations Screen
def congratulations_screen():
screen.fill(BLACK)
congrats_text = FONT.render('Congratulations!', True, WHITE)
win_text = FONT.render('You Won the Game!', True, WHITE)
screen.blit(congrats_text, ((SCREEN_WIDTH - congrats_text.get_width()) // 2, SCREEN_HEIGHT // 3))
screen.blit(win_text, ((SCREEN_WIDTH - win_text.get_width()) // 2, SCREEN_HEIGHT // 3 + 50))
draw_button("Play Again", (SCREEN_WIDTH - 200) // 2, SCREEN_HEIGHT // 2, 200, 60, GRAY, WHITE, restart_game)
pygame.display.update()
def restart_game():
global game_running, game_over, score, level, lives, game_started, active_powerups, powerup_effect_time, ball_on_paddle
score = 0
level = 1
lives = 3
game_running = True
game_over = False
game_started = True
ball_on_paddle = True
active_powerups.clear()
powerup_effect_time.clear()
reset_game(life_lost=False)
# This will bring us back to the main game loop
game_loop()
# Reset game entities
def reset_game(life_lost=False):
global ball_x, ball_y, ball_speed_x, ball_speed_y, paddle_x, paddle_y, ball_on_paddle
ball_x = paddle_x + PADDLE_WIDTH // 2
ball_y = paddle_y - BALL_RADIUS # Reset ball on paddle
ball_speed_x = (BALL_SPEED_X + level - 1) * random.choice((1, -1)) # Increase ball speed with level
ball_speed_y = -(BALL_SPEED_Y + level - 1) # Reset ball speed
ball_on_paddle = True # Ball starts on paddle again
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) // 2
if not life_lost: # Only reset bricks when starting a new level or game
create_bricks()
# Start Screen
def start_screen():
screen.fill(BLACK)
title_text = FONT.render('Breakout Game - The Pycodes', True, WHITE)
screen.blit(title_text, ((SCREEN_WIDTH - title_text.get_width()) // 2, SCREEN_HEIGHT // 3))
draw_button("Start Game", (SCREEN_WIDTH - 200) // 2, SCREEN_HEIGHT // 2, 200, 60, GRAY, WHITE, start_game)
pygame.display.update()
# Start or Restart the game
def start_game():
global game_running, game_over, game_started, score
score = 0 # Initialize score when the game starts
game_running = True
game_over = False
game_started = True # Set this to True to start the game
reset_game()
# Power-up logic
def generate_powerup(x, y):
if random.random() < 0.2: # 20% chance to generate a power-up
powerup_type = random.choice(POWERUP_TYPES)
active_powerups.append([x, y, powerup_type, time.time()])
def handle_powerups():
global paddle_x, paddle_y, PADDLE_WIDTH, ball_speed_x, ball_speed_y, lives
for powerup in active_powerups[:]:
x, y, powerup_type, start_time = powerup
# Draw power-ups with correct colors
if powerup_type == 'extra_life':
pygame.draw.rect(screen, YELLOW, (x, y, 20, 20)) # Extra Life (Yellow)
elif powerup_type == 'expand_paddle':
pygame.draw.rect(screen, GREEN, (x, y, 20, 20)) # Expand Paddle (Green)
elif powerup_type == 'slow_ball':
pygame.draw.rect(screen, PURPLE, (x, y, 20, 20)) # Slow Ball (Purple)
# Move power-up downwards
powerup[1] += 5
# Check for collision with paddle
if paddle_y < y + 20 < paddle_y + PADDLE_HEIGHT and paddle_x < x < paddle_x + PADDLE_WIDTH:
if powerup_type == 'expand_paddle':
PADDLE_WIDTH = PADDLE_DEFAULT_LENGTH + 50 # Extend paddle length
powerup_effect_time[powerup_type] = time.time()
elif powerup_type == 'slow_ball':
ball_speed_x *= 0.5
ball_speed_y *= 0.5
powerup_effect_time[powerup_type] = time.time()
elif powerup_type == 'extra_life':
lives += 1
active_powerups.remove(powerup)
# Revert power-up effects after 30 seconds
for p_type in list(powerup_effect_time.keys()):
if time.time() - powerup_effect_time[p_type] >= 30:
if p_type == 'expand_paddle':
PADDLE_WIDTH = PADDLE_DEFAULT_LENGTH # Revert paddle to default length
elif p_type == 'slow_ball':
ball_speed_x = (BALL_SPEED_X + level - 1) * random.choice((1, -1)) # Revert ball speed
ball_speed_y = -(BALL_SPEED_Y + level - 1)
del powerup_effect_time[p_type]
# Main game loop
def game_loop():
global ball_x, ball_y, ball_speed_x, ball_speed_y, paddle_x, paddle_y, lives, score, game_running, game_started, ball_on_paddle, level
# Reset game state
create_bricks()
reset_game()
while game_running:
screen.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_running = False
# Paddle movement
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
paddle_x -= PADDLE_SPEED
if keys[pygame.K_RIGHT]:
paddle_x += PADDLE_SPEED
# Keep paddle within screen bounds
if paddle_x < 0:
paddle_x = 0
if paddle_x + PADDLE_WIDTH > SCREEN_WIDTH:
paddle_x = SCREEN_WIDTH - PADDLE_WIDTH
# Ball movement and collision handling
if not ball_on_paddle:
ball_x += ball_speed_x
ball_y += ball_speed_y
# Ball collision with walls
if ball_x - BALL_RADIUS <= 0 or ball_x + BALL_RADIUS >= SCREEN_WIDTH:
ball_speed_x = -ball_speed_x
if ball_y - BALL_RADIUS <= 0:
ball_speed_y = -ball_speed_y
# Ball collision with paddle
if paddle_y < ball_y + BALL_RADIUS < paddle_y + PADDLE_HEIGHT and paddle_x < ball_x < paddle_x + PADDLE_WIDTH:
ball_speed_y = -ball_speed_y
# Ball falling below paddle (life lost)
if ball_y > SCREEN_HEIGHT:
lives -= 1
if lives == 0:
game_running = False
game_over_screen(score)
return
reset_game(life_lost=True)
# Brick collision detection
for brick in bricks[:]:
brick_x, brick_y, brick_color, hit = brick
if brick_x < ball_x < brick_x + BRICK_WIDTH and brick_y < ball_y < brick_y + BRICK_HEIGHT:
ball_speed_y = -ball_speed_y
bricks.remove(brick)
score += 10
generate_powerup(brick_x, brick_y)
break
# Check for win condition
if not bricks:
if level < 5:
level += 1
create_bricks()
reset_game()
else:
game_running = False
congratulations_screen()
return
# Draw bricks
for brick in bricks:
pygame.draw.rect(screen, brick[2], (brick[0], brick[1], BRICK_WIDTH, BRICK_HEIGHT))
# Draw paddle
pygame.draw.rect(screen, WHITE, (paddle_x, paddle_y, PADDLE_WIDTH, PADDLE_HEIGHT))
# Draw ball
pygame.draw.circle(screen, RED, (ball_x, ball_y), BALL_RADIUS)
# Handle power-ups
handle_powerups()
# Display score and lives
score_text = FONT.render(f'Score: {score}', True, WHITE)
lives_text = FONT.render(f'Lives: {lives}', True, WHITE)
level_text = FONT.render(f'Level: {level}', True, WHITE)
screen.blit(score_text, (20, 10))
screen.blit(lives_text, (SCREEN_WIDTH - lives_text.get_width() - 20, 10))
screen.blit(level_text, (SCREEN_WIDTH // 2 - level_text.get_width() // 2, 10))
# Ball on paddle logic (for start of game or life lost)
if ball_on_paddle:
ball_x = paddle_x + PADDLE_WIDTH // 2
if keys[pygame.K_SPACE]:
ball_on_paddle = False
# Update the display
pygame.display.update()
# Frame rate control
pygame.time.Clock().tick(60)
# Main program
game_running = False
game_started = False
while not game_started:
start_screen()
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_running = False
game_started = True # To exit the outer loop
if game_running:
game_loop()
pygame.quit()
Happy Coding!