728x90

시각적 연출 강화
이번 강의에서는 게임의 로직을 바꾸지 않고
시각적인 인상을 크게 바꾸는 게임 디자인 작업을 진행합니다.
구체적으로는
벽돌이 랜덤한 색상으로 생성되도록 변경합니다.
이 작업은 단순한 꾸미기가 아니라
플레이어의 인식과 체감 난이도에 직접적인 영향을 줍니다.
왜 벽돌 색상이 중요한가
지금까지의 벽돌은 모두 같은 색상이었습니다.
이 경우 게임에는 다음과 같은 한계가 있습니다.
- 벽돌이 많아 보이지 않습니다
- 충돌의 시각적 피드백이 약합니다
- 스테이지 변화가 느껴지지 않습니다
벽돌 색상을 다양하게 주는 것만으로도
게임은 훨씬 풍부해 보이게 됩니다.
색상 리스트 준비
랜덤 색상을 사용하되
완전히 무작위 RGB 값은 사용하지 않습니다.
게임 디자인에서는
미리 정해진 색상 팔레트를 사용하는 것이 안정적입니다.
BRICK_COLORS = [
(255, 80, 80),
(80, 255, 80),
(80, 80, 255),
(255, 200, 80),
(200, 80, 255),
(80, 200, 255)
]
벽돌에 색상 정보 추가하기
기존에는 벽돌을 Rect 객체로만 관리했습니다.
이제는 사각형 + 색상을 함께 관리합니다.
bricks.append({
"rect": pygame.Rect(brick_x, brick_y, brick_width, brick_height),
"color": random.choice(BRICK_COLORS)
})
이 구조를 사용하면
각 벽돌은 서로 다른 색을 가질 수 있습니다.
벽돌 생성 함수 수정
벽돌 생성 함수에서
색상을 함께 설정하도록 변경합니다.
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({
"rect": pygame.Rect(brick_x, brick_y, brick_width, brick_height),
"color": random.choice(BRICK_COLORS)
})
벽돌 그리기 방식 변경
이제 벽돌은
사각형과 색상을 함께 사용해 그립니다.
for brick in bricks:
pygame.draw.rect(screen, brick["color"], brick["rect"])
충돌 처리 코드 수정
벽돌이 딕셔너리 구조로 바뀌었기 때문에
충돌 처리도 이에 맞게 수정합니다.
if ball_rect.colliderect(brick["rect"]):
이 외의 로직은
기존과 동일하게 유지됩니다.
현재 단계의 결과
이번 강의까지 구현된 결과는 다음과 같습니다.
- 벽돌이 랜덤한 색상으로 생성됩니다
- 화면이 훨씬 다채롭게 보입니다
- 게임이 스테이지처럼 느껴지기 시작합니다
이제 게임은
기능적으로도, 시각적으로도 완성 단계에 들어왔습니다.
다음 강의에서는
본격적으로 스테이지 개념을 도입합니다.

전체 코드
더보기
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)
BRICK_COLORS = [
(255, 80, 80),
(80, 255, 80),
(80, 80, 255),
(255, 200, 80),
(200, 80, 255),
(80, 200, 255)
]
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"
prev_state = None
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({
"rect": pygame.Rect(brick_x, brick_y, brick_width, brick_height),
"color": random.choice(BRICK_COLORS)
})
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()
pygame.mixer.music.load("title_bgm.mp3")
pygame.mixer.music.play(-1)
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 != prev_state:
if game_state == "TITLE":
pygame.mixer.music.load("title_bgm.mp3")
pygame.mixer.music.play(-1)
elif game_state == "PLAY":
pygame.mixer.music.load("game_bgm.mp3")
pygame.mixer.music.play(-1)
prev_state = game_state
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["rect"]):
overlap_left = ball_rect.right - brick["rect"].left
overlap_right = brick["rect"].right - ball_rect.left
overlap_top = ball_rect.bottom - brick["rect"].top
overlap_bottom = brick["rect"].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["rect"].centerx - 10, brick["rect"].centery)
effects.append({
"rect": pygame.Rect(
brick["rect"].x,
brick["rect"].y,
brick["rect"].width,
brick["rect"].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, brick["color"], brick["rect"])
for item in items:
pygame.draw.rect(screen, GREEN, item)
for effect in effects:
pygame.draw.rect(screen, EFFECT_COLOR, effect["rect"])
pygame.display.flip()
pygame.quit()
sys.exit()
728x90
'⚙️ Python > 🎮 Pygame 실전' 카테고리의 다른 글
| [Pygame] 🧱 벽돌 깨기 게임 만들기 17강 | 스테이지 시스템으로 게임 완성하기 (0) | 2026.02.28 |
|---|---|
| [Pygame] 🧱 벽돌 깨기 게임 만들기 15강 | 배경음악 추가 (0) | 2026.02.27 |
| [Pygame] 🧱 벽돌 깨기 게임 만들기 14강 | 재시작 기능 추가 (0) | 2026.02.26 |
| [Pygame] 🧱 벽돌 깨기 게임 만들기 13강 | 게임 시작 흐름 개선 (0) | 2026.02.26 |
| [Pygame] 🧱 벽돌 깨기 게임 만들기 12강 | 타이틀 화면 추가 (0) | 2026.02.25 |