고전게임 시리즈
1. 테트리스
https://ilikesunday.tistory.com/31
[Python] 파이썬으로 고전 게임 '테트리스' 만들기 – 직접 만들어보며 배운 시행착오의 기록
들어가며예전부터 한 번쯤은 만들어보고 싶었던 게임이 있다면 단연코 테트리스였다. 간단한 구조지만 의외로 중독성이 있고, 적당한 난이도 조절과 함께 구현할 수 있다면 파이썬의 GUI 및 로
ilikesunday.tistory.com
2. 스네이크게임
https://ilikesunday.tistory.com/32
[Python] 파이썬으로 고전 게임 스네이크 게임 만들기 - 삽질의 연속 ㅠㅠ
들어가며처음에는 파이썬으로 테트리스를 먼저 만들었었다. 다른 고전 게임에도 흥미가 생기기도 하고 시리즈별로 만들고 싶어서 스네이크 게임을 만들고자 했지만... 테트리스 때와 달리 이번
ilikesunday.tistory.com
들어가며
스네이크 게임을 완성한 후, 다음으로 도전해보고 싶었던 건 벽돌깨기였다. 어릴 적 PC방이나 학원 컴퓨터에서 자주 보던 그 단순한 게임이, 막상 만들려고 하니 예상보다 복잡한 구조를 갖고 있다는 걸 알게 되었다. 특히 공의 움직임과 패들(paddle), 그리고 벽돌과의 충돌을 어떻게 처리할 것인가에 대해 많은 고민이 필요했다. 이번 화에서는 pygame으로 벽돌깨기 게임을 만들면서 겪은 시행착오와 구현 노하우를 상세히 공유하려 한다. 중간중간 내가 어떤 생각을 했고 어떤 문제에 부딪혔는지를 중심으로, 단순한 코드가 아닌 실제 '개발 경험'으로서 전달하고 싶다.
화면 구성의 설계부터 다시 시작
게임을 시작하면서 가장 먼저 고민한 건 전체 화면 레이아웃이었다. 공, 패들, 벽돌이 어디에 위치해야 할지를 머릿속으로 그려보고, 그리드 단위로 대략적인 크기를 정했다. 화면 해상도는 800x600으로 정했고, 패들은 하단에서 약간 위쪽에 위치하며 공은 그 위에서 출발하도록 구성했다. 벽돌은 상단에 여러 줄로 배치하기로 했고, 색상을 줄마다 달리해서 시각적으로도 구분되게 만들었다. 이렇게 레이아웃이 머릿속에 그려지자, 본격적인 좌표 설정과 렌더링에 들어갈 수 있었다.
공이 움직이지 않았다? 애니메이션의 기본
공은 자동으로 움직여야 했고, 벽에 부딪히면 튕기듯 반사되어야 했다. 처음엔 그냥 x좌표와 y좌표에 일정값을 더해주면 되는 줄 알았는데, 업데이트가 안 되는 이유를 몰랐다. 알고 보니 매 루프마다 배경을 다시 그리지 않고 공만 덧그리는 방식으로 해서 자국이 남았고, 움직임도 어색했다. 그래서 매 프레임마다 화면 전체를 덮고, 공을 새 좌표에 다시 그리는 구조로 수정했다. 덕분에 공은 자연스럽게 움직이기 시작했고, 이제 충돌만 처리하면 될 것 같았다.
패들 이동에 대한 고민
패들은 마우스나 키보드로 움직일 수 있게 해야 했다. 처음에는 방향키 입력을 받아 왼쪽/오른쪽으로 이동하게 했지만, 빠르게 누르면 너무 급격하게 움직이거나 아예 벽 밖으로 나가는 경우가 발생했다. 그래서 좌표를 직접 변경하는 대신 이동량 dx를 설정하고, 루프마다 현재 위치에 반영하는 구조로 수정했다. 또한 if문을 이용해 화면 밖으로 나가지 않도록 범위를 제한했다. 그제야 자연스럽게 움직이는 패들이 만들어졌다.
벽돌 생성 로직
벽돌을 만들 때 반복문을 써서 행과 열로 그리면 될 것 같았다. 하지만 단순히 사각형을 그리기만 하면 충돌 감지가 어렵기 때문에, 각 벽돌을 리스트에 저장해야 한다는 걸 깨달았다. 그래서 각 벽돌을 Rect 객체로 만들고, 색상과 함께 튜플로 저장하여 다룰 수 있게 했다. 리스트에 벽돌 객체들을 담고, 매 루프마다 그려주는 방식으로 처리했다. 다음은 벽돌 초기화 코드다.
bricks = []
rows, cols = 5, 10
for row in range(rows):
for col in range(cols):
brick_rect = pygame.Rect(col * 80 + 10, row * 30 + 40, 70, 20)
bricks.append((brick_rect, (255 - row * 40, 100 + row * 20, 200)))
충돌 감지가 잘 안 됐던 날
공과 벽돌이 분명히 겹쳤는데도 충돌 처리가 안 되는 일이 생겼다. 이유는 Rect.colliderect() 함수를 쓰긴 했지만, 공을 사각형이 아닌 원으로 그렸기 때문이었다. 그래서 공도 내부적으로는 Rect 객체로 만들어 두고, 실제로 그릴 때만 원으로 표현하는 식으로 설계를 변경했다. 그렇게 하자 충돌 처리가 한결 쉬워졌고, 코드도 간결해졌다. 이후에는 충돌한 벽돌을 리스트에서 제거해주는 로직을 추가했다.
최종 통합 – 모든 기능을 하나로 묶기
충돌, 반사, 점수판, 게임 오버까지 모든 기능이 잘 작동하게 되자, 이제는 코드를 정리하고 통합하는 단계에 들어갔다. 이전에 작성한 모듈을 함수화하고, 메인 루프에서 각 함수들을 순차적으로 호출하여 게임이 흘러가게 만들었다. 코드를 좀 더 정돈된 구조로 재작성하면서, 앞으로 다른 기능들을 추가하더라도 수정이 쉬운 형태로 만들어두었다. 특히 벽돌을 클래스화해서 각 벽돌이 충돌 시 파괴될 수 있도록 구성했으며, 공의 속도도 점진적으로 증가시키는 옵션도 붙였다. 아래는 지금까지 통합한 전체 실행 가능한 벽돌깨기 게임의 코드이다.
import pygame
import random
pygame.init()
# 색상 정의
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
WIDTH = 800
HEIGHT = 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Breakout Game")
clock = pygame.time.Clock()
# 공 설정
ball_radius = 10
ball_x = WIDTH // 2
ball_y = HEIGHT // 2
ball_dx = 4
ball_dy = -4
# 패들 설정
paddle_width = 100
paddle_height = 15
paddle_x = (WIDTH - paddle_width) // 2
paddle_speed = 7
# 벽돌 설정
brick_rows = 5
brick_cols = 10
brick_width = 75
brick_height = 30
brick_padding = 5
brick_offset_top = 50
brick_offset_left = 35
bricks = []
for row in range(brick_rows):
for col in range(brick_cols):
brick_x = brick_offset_left + col * (brick_width + brick_padding)
brick_y = brick_offset_top + row * (brick_height + brick_padding)
bricks.append(pygame.Rect(brick_x, brick_y, brick_width, brick_height))
# 점수
score = 0
font = pygame.font.SysFont(None, 36)
def draw_bricks():
for brick in bricks:
pygame.draw.rect(screen, GREEN, brick)
def draw_ball():
pygame.draw.circle(screen, RED, (ball_x, ball_y), ball_radius)
def draw_paddle():
pygame.draw.rect(screen, BLUE, (paddle_x, HEIGHT - 40, paddle_width, paddle_height))
def draw_score():
score_text = font.render(f"Score: {score}", True, WHITE)
screen.blit(score_text, (10, 10))
game_over = False
running = True
while running:
clock.tick(60)
screen.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and paddle_x > 0:
paddle_x -= paddle_speed
if keys[pygame.K_RIGHT] and paddle_x < WIDTH - paddle_width:
paddle_x += paddle_speed
ball_x += ball_dx
ball_y += ball_dy
# 벽 충돌
if ball_x <= 0 or ball_x >= WIDTH:
ball_dx *= -1
if ball_y <= 0:
ball_dy *= -1
# 패들과 충돌
paddle_rect = pygame.Rect(paddle_x, HEIGHT - 40, paddle_width, paddle_height)
if paddle_rect.collidepoint(ball_x, ball_y + ball_radius):
ball_dy *= -1
# 벽돌과 충돌
for brick in bricks[:]:
if brick.collidepoint(ball_x, ball_y):
bricks.remove(brick)
ball_dy *= -1
score += 10
break
# 바닥 아래로 떨어짐
if ball_y > HEIGHT:
game_over = True
draw_bricks()
draw_ball()
draw_paddle()
draw_score()
if game_over:
game_over_text = font.render("Game Over", True, RED)
screen.blit(game_over_text, (WIDTH // 2 - 80, HEIGHT // 2))
else:
pygame.display.flip()
pygame.quit()
마무리하며
벽돌깨기를 만들면서 가장 많이 배운 건 '충돌 처리'의 정교함과, 단순한 구조 안에도 얼마나 많은 고민이 필요한가였다. 처음에는 그냥 튕기기만 하면 되겠지 싶었는데, 충돌 방향에 따라 반사 방향이 달라지게 만들고, 패들 위치에 따라 공의 궤적이 살짝 달라지는 등 미세한 조정이 게임의 재미를 결정한다는 걸 체감했다. 또 게임 오버 조건과 점수 시스템을 붙이면서 단순한 미니 게임이 하나의 '프로젝트'처럼 느껴지게 되었다. 다음 편에서는 슈팅 게임에 도전해 보며, 발사체와 적 유닛 관리라는 새로운 개념을 다뤄볼 예정이다. 게임이 점점 '살아 움직이는 것'처럼 느껴지는 경험을 여러분도 해보길 바란다.
'개발 > Python' 카테고리의 다른 글
[Python] 파이썬으로 고전 게임 피하기 게임 만들기 - Dodge Game (2) | 2025.04.22 |
---|---|
[Python] 파이썬으로 고전 게임 슈팅 게임 만들기: 총알, 적, 그리고 리듬감 (0) | 2025.04.22 |
[Python] 파이썬으로 고전 게임 스네이크 게임 만들기 - 삽질의 연속 ㅠㅠ (1) | 2025.04.22 |
[Python] 파이썬으로 고전 게임 '테트리스' 만들기 – 직접 만들어보며 배운 시행착오의 기록 (3) | 2025.04.21 |
[Python] 파이썬으로 네이버 실시간 뉴스 타이틀 수집하기 (1) | 2025.04.20 |