본문 바로가기

[Pygame] 🧱 벽돌 깨기 게임 만들기 14강 | 재시작 기능 추가

@도마22026. 2. 26. 22:00
728x90


재시작 기능 추가

이번 강의에서는 게임의 흐름을 완성하는
게임 오버 / 게임 클리어 화면과 재시작 기능을 구현합니다.

이 강의의 핵심은 다음과 같습니다.

  • 게임 오버 화면을 추가합니다
  • 게임 클리어 화면을 추가합니다
  • R 키를 눌러 언제든 게임을 재시작할 수 있도록 만듭니다

이번 강의를 기준으로
게임은 명확한 시작과 끝을 가진 구조가 됩니다.


왜 재시작 기능이 필요한가

지금까지의 게임은
라이프가 0이 되면 프로그램이 바로 종료되었습니다.

하지만 실제 게임은 다음 흐름을 가집니다.

  • 플레이
  • 실패 또는 성공
  • 결과 화면 표시
  • 재도전 선택

이를 위해
게임 상태를 더 세분화해야 합니다.


게임 상태 확장하기

기존 상태에
두 가지 상태를 추가합니다.

game_state = "TITLE"
  • "TITLE" : 타이틀 화면
  • "PLAY" : 게임 진행
  • "GAME_OVER" : 게임 오버 화면
  • "GAME_CLEAR" : 게임 클리어 화면

게임 오버 조건 처리

라이프가 0이 되면
게임 상태를 "GAME_OVER" 로 변경합니다.

if lives <= 0:
    game_state = "GAME_OVER"

이 상태에서는
게임 로직이 더 이상 실행되지 않습니다.


게임 클리어 조건 처리

모든 벽돌이 제거되었을 때
게임 클리어 상태로 전환합니다.

if len(bricks) == 0:
    game_state = "GAME_CLEAR"

이 강의에서는
클리어 후 바로 종료하지 않고,
결과 화면을 먼저 보여줍니다.


게임 오버 화면 구성

게임 오버 상태에서는
다음 정보만 화면에 표시합니다.

  • GAME OVER 문구
  • 재시작 안내 문구
over_text = title_font.render("GAME OVER", True, WHITE)
restart_text = info_font.render("Press R to Restart", True, WHITE)

게임 클리어 화면 구성

게임 클리어 상태에서도
비슷한 구조를 사용합니다.

clear_text = title_font.render("GAME CLEAR!", True, WHITE)
restart_text = info_font.render("Press R to Restart", True, WHITE)

두 화면은
구조는 같고 문구만 다르게 처리합니다.


R 키로 재시작 처리

게임 오버 또는 클리어 상태에서
R 키를 누르면 게임을 완전히 초기화합니다.

if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_r:
        reset_game()
        game_state = "PLAY"

게임 초기화 함수 만들기

재시작 시 반복되는 코드를
함수로 정리합니다.

def reset_game():
    global score, lives, ball_active
    score = 0
    lives = 3
    ball_active = False

    paddle_rect.width = paddle_width
    paddle_rect.x = (SCREEN_WIDTH - paddle_width) // 2

    create_bricks()
    items.clear()
    effects.clear()

상태별 게임 루프 정리

이제 게임 루프는
다음 구조를 가집니다.

  • TITLE → SPACE → PLAY
  • PLAY → GAME_OVER
  • PLAY → GAME_CLEAR
  • GAME_OVER / GAME_CLEAR → R → PLAY

이 흐름이 완성되면서
게임은 끝까지 플레이 가능한 구조가 됩니다.


현재 단계의 결과

이번 강의까지 구현된 결과는 다음과 같습니다.

  • 게임 오버 화면이 표시됩니다
  • 게임 클리어 화면이 표시됩니다
  • R 키로 언제든 재시작할 수 있습니다
  • 게임 흐름이 명확해졌습니다

이제 게임은
완성 직전 단계의 구조를 갖추게 되었습니다.

다음 강의에서는
타이틀 음악과 게임 내 음악을 분리해
사운드 연출을 완성합니다.



전체 코드

더보기
import pygame
import sys
import random

pygame.init()
pygame.mixer.init()

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Breakout Game")

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 200, 0)
EFFECT_COLOR = (255, 200, 200)

clock = pygame.time.Clock()
FPS = 60

font = pygame.font.SysFont(None, 30)
title_font = pygame.font.SysFont(None, 64)
info_font = pygame.font.SysFont(None, 28)

brick_sound = pygame.mixer.Sound("brick.wav")
paddle_sound = pygame.mixer.Sound("paddle.wav")
item_sound = pygame.mixer.Sound("item.wav")

game_state = "TITLE"
ball_active = False

score = 0
lives = 3

paddle_width = 100
paddle_height = 15
paddle_speed = 7

paddle_rect = pygame.Rect(
    (SCREEN_WIDTH - paddle_width) // 2,
    SCREEN_HEIGHT - 40,
    paddle_width,
    paddle_height
)

ball_radius = 8
ball_x = paddle_rect.centerx
ball_y = paddle_rect.top - ball_radius
ball_speed_x = 5
ball_speed_y = -5

ball_rect = pygame.Rect(
    ball_x - ball_radius,
    ball_y - ball_radius,
    ball_radius * 2,
    ball_radius * 2
)

brick_width = 60
brick_height = 20
brick_padding = 10
rows = 4
cols = 10

bricks = []
items = []
effects = []
item_speed = 2

def create_bricks():
    bricks.clear()
    for row in range(rows):
        for col in range(cols):
            brick_x = col * (brick_width + brick_padding) + 35
            brick_y = row * (brick_height + brick_padding) + 50
            bricks.append(pygame.Rect(brick_x, brick_y, brick_width, brick_height))

def create_item(x, y):
    items.append(pygame.Rect(x, y, 20, 20))

def reset_game():
    global score, lives, ball_active, ball_speed_x, ball_speed_y
    score = 0
    lives = 3
    ball_active = False

    ball_speed_x = 5
    ball_speed_y = -5

    paddle_rect.width = paddle_width
    paddle_rect.x = (SCREEN_WIDTH - paddle_width) // 2

    create_bricks()
    items.clear()
    effects.clear()

create_bricks()

running = True
while running:
    clock.tick(FPS)

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

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and game_state == "TITLE":
                game_state = "PLAY"
                ball_active = False

            elif event.key == pygame.K_SPACE and game_state == "PLAY" and not ball_active:
                ball_active = True

            if event.key == pygame.K_r and game_state in ("GAME_OVER", "GAME_CLEAR"):
                reset_game()
                game_state = "PLAY"

    if game_state == "TITLE":
        screen.fill(BLACK)
        screen.blit(
            title_font.render("BREAKOUT GAME", True, WHITE),
            (SCREEN_WIDTH // 2 - 200, 200)
        )
        screen.blit(
            info_font.render("Press SPACE to Start", True, WHITE),
            (SCREEN_WIDTH // 2 - 110, 300)
        )
        pygame.display.flip()
        continue

    if game_state in ("GAME_OVER", "GAME_CLEAR"):
        screen.fill(BLACK)
        text = "GAME OVER" if game_state == "GAME_OVER" else "GAME CLEAR!"
        screen.blit(
            title_font.render(text, True, WHITE),
            (SCREEN_WIDTH // 2 - 130, 200)
        )
        screen.blit(
            info_font.render("Press R to Restart", True, WHITE),
            (SCREEN_WIDTH // 2 - 80, 300)
        )
        pygame.display.flip()
        continue

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        paddle_rect.x -= paddle_speed
    if keys[pygame.K_RIGHT]:
        paddle_rect.x += paddle_speed

    paddle_rect.left = max(paddle_rect.left, 0)
    paddle_rect.right = min(paddle_rect.right, SCREEN_WIDTH)

    if not ball_active:
        ball_x = paddle_rect.centerx
        ball_y = paddle_rect.top - ball_radius
    else:
        ball_x += ball_speed_x
        ball_y += ball_speed_y

    ball_rect.x = ball_x - ball_radius
    ball_rect.y = ball_y - ball_radius

    if ball_active:
        if ball_rect.left <= 0 or ball_rect.right >= SCREEN_WIDTH:
            ball_speed_x *= -1
        if ball_rect.top <= 0:
            ball_speed_y *= -1

        if ball_rect.colliderect(paddle_rect):
            ball_speed_y *= -1
            ball_rect.bottom = paddle_rect.top
            ball_y = ball_rect.centery
            paddle_sound.play()

    for brick in bricks:
        if ball_rect.colliderect(brick):
            overlap_left = ball_rect.right - brick.left
            overlap_right = brick.right - ball_rect.left
            overlap_top = ball_rect.bottom - brick.top
            overlap_bottom = brick.bottom - ball_rect.top

            min_overlap = min(overlap_left, overlap_right, overlap_top, overlap_bottom)

            if min_overlap == overlap_left or min_overlap == overlap_right:
                ball_speed_x *= -1
            else:
                ball_speed_y *= -1

            if random.random() < 0.3:
                create_item(brick.centerx - 10, brick.centery)

            effects.append({
                "rect": pygame.Rect(brick.x, brick.y, brick.width, brick.height),
                "timer": 10
            })

            brick_sound.play()
            bricks.remove(brick)
            score += 10
            break

    if len(bricks) == 0:
        game_state = "GAME_CLEAR"

    for item in items[:]:
        item.y += item_speed
        if item.colliderect(paddle_rect):
            paddle_rect.width += 30
            item_sound.play()
            items.remove(item)
        elif item.top > SCREEN_HEIGHT:
            items.remove(item)

    for effect in effects[:]:
        effect["timer"] -= 1
        if effect["timer"] <= 0:
            effects.remove(effect)

    if ball_rect.top > SCREEN_HEIGHT and ball_active:
        lives -= 1
        ball_active = False
        if lives <= 0:
            game_state = "GAME_OVER"
        else:
            paddle_rect.width = paddle_width
            paddle_rect.x = (SCREEN_WIDTH - paddle_width) // 2
            create_bricks()
            items.clear()
            effects.clear()

    screen.fill(BLACK)

    screen.blit(font.render(f"Score: {score}", True, WHITE), (10, 10))
    screen.blit(font.render(f"Lives: {lives}", True, WHITE), (SCREEN_WIDTH - 100, 10))

    pygame.draw.rect(screen, WHITE, paddle_rect)
    pygame.draw.circle(screen, WHITE, (ball_x, ball_y), ball_radius)

    for brick in bricks:
        pygame.draw.rect(screen, WHITE, brick)

    for item in items:
        pygame.draw.rect(screen, GREEN, item)

    for effect in effects:
        pygame.draw.rect(screen, EFFECT_COLOR, effect["rect"])

    if not ball_active:
        hint = info_font.render("Press SPACE to Launch", True, WHITE)
        screen.blit(hint, (SCREEN_WIDTH // 2 - hint.get_width() // 2, SCREEN_HEIGHT - 80))

    pygame.display.flip()

pygame.quit()
sys.exit()

728x90
도마2
@도마2 :: 도마의 코드노트

초보자를 위한 코딩 강의를 정리합니다. 파이썬부터 C#, Unity 게임 제작까지 차근차근 기록합니다. — 도마

목차