파이썬으로 시작하는 게임 개발의 세계 🐍🎮
안녕하세요! Python Game Dev 블로그의 여섯 번째 포스팅입니다. 이전에는 숫자 맞추기, 퀴즈, 행맨, 틱택토, 그리고 스네이크 게임을 만들어보았는데요, 오늘은 클래식 아케이드 게임인 브레이크아웃(Breakout) 게임을 만들어보겠습니다.
오늘의 게임: 브레이크아웃 게임 🧱🏓
브레이크아웃은 1976년 아타리에서 출시된 게임으로, 게임 역사에서 중요한 위치를 차지하고 있습니다. 플레이어는 화면 하단의 패들을 좌우로 움직여 공을 튕겨 블록을 부수는 게임입니다. 이번 포스팅에서는 파이썬과 Pygame을 이용해 이 클래식 게임을 재현해보겠습니다.
게임의 규칙 📜
- 플레이어는 화면 하단에 있는 패들을 좌우로 움직일 수 있습니다.
- 공은 계속 움직이며 벽, 블록, 패들에 부딪히면 튕겨나갑니다.
- 블록을 맞추면 점수를 얻고 블록이 사라집니다.
- 모든 블록을 제거하면 승리합니다.
- 공이 화면 하단으로 떨어지면 생명이 줄어들고, 모든 생명을 잃으면 게임 오버됩니다.
전체 코드 💻
import pygame
import sys
import random
import math
# 초기화
pygame.init()
pygame.font.init()
# 색상 정의
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
# 게임 설정
WIDTH, HEIGHT = 800, 600
PADDLE_WIDTH, PADDLE_HEIGHT = 100, 15
BALL_RADIUS = 10
BLOCK_WIDTH, BLOCK_HEIGHT = 80, 30
BLOCK_ROWS = 5
BLOCK_COLS = 10
FPS = 60
# 창 설정
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Python Breakout Game')
clock = pygame.time.Clock()
# 폰트 설정
font = pygame.font.SysFont('malgungothic', 30)
big_font = pygame.font.SysFont('malgungothic', 50)
class Paddle:
def __init__(self, x, y, width, height, color, speed):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.speed = speed
self.rect = pygame.Rect(x, y, width, height)
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect)
# 패들에 하이라이트 추가
pygame.draw.rect(surface, WHITE, (self.x, self.y, self.width, 5))
def move(self, direction):
if direction == 'left':
self.x = max(0, self.x - self.speed)
elif direction == 'right':
self.x = min(WIDTH - self.width, self.x + self.speed)
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
class Ball:
def __init__(self, x, y, radius, color, speed_x, speed_y):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.speed_x = speed_x
self.speed_y = speed_y
self.active = False
def draw(self, surface):
pygame.draw.circle(surface, self.color, (self.x, self.y), self.radius)
# 볼에 하이라이트 추가
pygame.draw.circle(surface, WHITE, (self.x - 3, self.y - 3), 3)
def move(self):
if self.active:
self.x += self.speed_x
self.y += self.speed_y
def bounce_wall(self):
# 좌우 벽 충돌
if self.x <= self.radius or self.x >= WIDTH - self.radius:
self.speed_x *= -1
play_sound('wall')
# 천장 충돌
if self.y <= self.radius:
self.speed_y *= -1
play_sound('wall')
def bounce_paddle(self, paddle):
if (self.y + self.radius >= paddle.y and
self.x >= paddle.x and
self.x <= paddle.x + paddle.width):
# 패들의 어느 부분에 맞았는지에 따라 반사각 조절
relative_x = (self.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2)
bounce_angle = relative_x * (math.pi / 3) # 최대 60도 각도
speed = math.sqrt(self.speed_x**2 + self.speed_y**2)
self.speed_x = speed * math.sin(bounce_angle)
self.speed_y = -speed * math.cos(bounce_angle)
play_sound('paddle')
def check_bottom(self):
if self.y >= HEIGHT + self.radius:
return True
return False
def reset(self, paddle):
self.x = paddle.x + paddle.width // 2
self.y = paddle.y - self.radius - 1
self.active = False
self.speed_x = random.choice([-4, 4])
self.speed_y = -5
class Block:
def __init__(self, x, y, width, height, color, points):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.points = points
self.rect = pygame.Rect(x, y, width, height)
self.active = True
def draw(self, surface):
if self.active:
pygame.draw.rect(surface, self.color, self.rect)
# 블록에 테두리 추가
pygame.draw.rect(surface, WHITE, self.rect, 2)
# 하이라이트 추가
pygame.draw.line(surface, WHITE, (self.x, self.y), (self.x + self.width, self.y), 3)
pygame.draw.line(surface, WHITE, (self.x, self.y), (self.x, self.y + self.height), 3)
def check_collision(self, ball):
if not self.active:
return False
# 충돌 감지를 위한 임시 사각형 (공의 중심을 기준으로)
ball_rect = pygame.Rect(ball.x - ball.radius, ball.y - ball.radius,
ball.radius * 2, ball.radius * 2)
if self.rect.colliderect(ball_rect):
# 충돌 방향 파악 (수직, 수평)
# 공과 블록 중심 사이의 거리 계산
dx = ball.x - max(self.x, min(ball.x, self.x + self.width))
dy = ball.y - max(self.y, min(ball.y, self.y + self.height))
if abs(dx) > abs(dy):
ball.speed_x *= -1 # 좌우 충돌
else:
ball.speed_y *= -1 # 상하 충돌
self.active = False
play_sound('block')
return True
return False
def create_blocks():
blocks = []
colors = [RED, ORANGE, YELLOW, GREEN, BLUE]
points = [50, 40, 30, 20, 10] # 위에서부터 점수 배분
for row in range(BLOCK_ROWS):
for col in range(BLOCK_COLS):
block_x = col * (BLOCK_WIDTH + 5) + 50
block_y = row * (BLOCK_HEIGHT + 5) + 50
blocks.append(Block(block_x, block_y, BLOCK_WIDTH, BLOCK_HEIGHT,
colors[row], points[row]))
return blocks
def play_sound(sound_type):
# 실제 게임에 사운드를 추가할 때는 아래 주석을 해제하고 사운드 파일을 준비해야 합니다
# 이 예제에서는 사운드 파일 없이 함수만 준비합니다
pass
# if sound_type == 'wall':
# pygame.mixer.Sound('wall.wav').play()
# elif sound_type == 'paddle':
# pygame.mixer.Sound('paddle.wav').play()
# elif sound_type == 'block':
# pygame.mixer.Sound('block.wav').play()
# elif sound_type == 'gameover':
# pygame.mixer.Sound('gameover.wav').play()
def show_message(message, y_pos, color=WHITE, size='normal'):
font_to_use = big_font if size == 'big' else font
text = font_to_use.render(message, True, color)
text_rect = text.get_rect(center=(WIDTH//2, y_pos))
screen.blit(text, text_rect)
def start_screen():
screen.fill(BLACK)
show_message("BREAKOUT", HEIGHT//3, YELLOW, 'big')
show_message("시작하려면 SPACE를 누르세요", HEIGHT//2 + 30)
show_message("종료하려면 ESC를 누르세요", HEIGHT//2 + 70)
pygame.display.update()
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
waiting = False
elif event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
def game_over_screen(score, won=False):
screen.fill(BLACK)
if won:
show_message("승리했습니다!", HEIGHT//3 - 50, GREEN, 'big')
else:
show_message("게임 오버", HEIGHT//3 - 50, RED, 'big')
show_message(f"최종 점수: {score}", HEIGHT//2)
show_message("다시 시작하려면 SPACE를 누르세요", HEIGHT//2 + 50)
show_message("종료하려면 ESC를 누르세요", HEIGHT//2 + 90)
pygame.display.update()
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
waiting = False
return True # 재시작
elif event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
def main():
# 게임 객체 초기화
paddle = Paddle((WIDTH - PADDLE_WIDTH) // 2, HEIGHT - 30,
PADDLE_WIDTH, PADDLE_HEIGHT, BLUE, 8)
ball = Ball(WIDTH // 2, HEIGHT - 50, BALL_RADIUS, WHITE, 0, 0)
blocks = create_blocks()
# 게임 상태 변수
score = 0
lives = 3
game_started = False
# 시작 화면
start_screen()
# 게임 루프
running = True
while running:
# 이벤트 처리
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and not ball.active:
ball.active = True
if not game_started:
game_started = True
# 키 상태 확인
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
paddle.move('left')
if keys[pygame.K_RIGHT]:
paddle.move('right')
# 공이 활성화되지 않았으면 패들 위에 위치시키기
if not ball.active:
ball.reset(paddle)
# 공 이동
ball.move()
ball.bounce_wall()
ball.bounce_paddle(paddle)
# 블록 충돌 확인
active_blocks = 0
for block in blocks:
if block.active:
active_blocks += 1
if block.check_collision(ball):
score += block.points
# 공이 바닥에 떨어졌는지 확인
if ball.check_bottom():
lives -= 1
if lives <= 0:
if game_over_screen(score):
# 재시작
return main()
else:
running = False
else:
ball.reset(paddle)
# 모든 블록이 제거되었는지 확인
if active_blocks == 0:
if game_over_screen(score, True):
# 재시작
return main()
else:
running = False
# 화면 그리기
screen.fill(BLACK)
# 블록 그리기
for block in blocks:
block.draw(screen)
# 패들과 공 그리기
paddle.draw(screen)
ball.draw(screen)
# 점수와 생명 표시
show_message(f"점수: {score}", 20)
show_message(f"생명: {lives}", HEIGHT - 20)
# 시작 안내 메시지
if not game_started:
show_message("SPACE를 눌러 시작하세요", HEIGHT // 2)
pygame.display.update()
clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
게임 화면 📸
게임을 실행하면 다음과 같은 화면이 나타납니다:
- 시작 화면 
- 게임 진행 화면 
- 게임 오버 화면 
코드 설명 📝
1. 클래스 기반 구조
이번 게임에서는 객체 지향 프로그래밍(OOP)을 활용하여 게임 요소들을 클래스로 구현했습니다.
Paddle 클래스
class Paddle:
def __init__(self, x, y, width, height, color, speed):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.speed = speed
self.rect = pygame.Rect(x, y, width, height)
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect)
# 패들에 하이라이트 추가
pygame.draw.rect(surface, WHITE, (self.x, self.y, self.width, 5))
def move(self, direction):
if direction == 'left':
self.x = max(0, self.x - self.speed)
elif direction == 'right':
self.x = min(WIDTH - self.width, self.x + self.speed)
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
패들을 관리하는 클래스로, 위치, 크기, 색상, 속도 등의 속성과 화면에 그리는 draw 메서드, 이동하는 move 메서드를 가지고 있습니다.
Ball 클래스
class Ball:
def __init__(self, x, y, radius, color, speed_x, speed_y):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.speed_x = speed_x
self.speed_y = speed_y
self.active = False
def draw(self, surface):
pygame.draw.circle(surface, self.color, (self.x, self.y), self.radius)
# 볼에 하이라이트 추가
pygame.draw.circle(surface, WHITE, (self.x - 3, self.y - 3), 3)
def move(self):
if self.active:
self.x += self.speed_x
self.y += self.speed_y
def bounce_wall(self):
# 좌우 벽과 천장 충돌 처리
# ...
def bounce_paddle(self, paddle):
# 패들과의 충돌 처리
# ...
공을 관리하는 클래스로, 위치, 반지름, 색상, 속도 등의 속성과 여러 메서드를 가지고 있습니다. 특히 bounce_paddle 메서드에서는 패들의 어느 부분에 맞았는지에 따라 반사각을 조절하여 게임성을 높였습니다.
Block 클래스
class Block:
def __init__(self, x, y, width, height, color, points):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.points = points
self.rect = pygame.Rect(x, y, width, height)
self.active = True
def draw(self, surface):
# 블록 그리기
# ...
def check_collision(self, ball):
# 공과의 충돌 확인
# ...
블록을 관리하는 클래스로, 위치, 크기, 색상, 점수 등의 속성과 그리기, 충돌 확인 메서드를 가지고 있습니다. check_collision 메서드에서는 공이 블록의 어느 면에 부딪혔는지 계산하여 적절한 반사를 구현했습니다.
2. 물리 시뮬레이션
브레이크아웃 게임의 핵심은 공의 움직임과 충돌 물리입니다.
def bounce_paddle(self, paddle):
if (self.y + self.radius >= paddle.y and
self.x >= paddle.x and
self.x <= paddle.x + paddle.width):
# 패들의 어느 부분에 맞았는지에 따라 반사각 조절
relative_x = (self.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2)
bounce_angle = relative_x * (math.pi / 3) # 최대 60도 각도
speed = math.sqrt(self.speed_x**2 + self.speed_y**2)
self.speed_x = speed * math.sin(bounce_angle)
self.speed_y = -speed * math.cos(bounce_angle)
play_sound('paddle')
패들의 중앙에 맞으면 수직으로 튕겨나가고, 왼쪽이나 오른쪽 가장자리에 맞을수록 해당 방향으로 각도가 커지는 물리 시뮬레이션을 구현했습니다. 이렇게 하면 플레이어가 패들로 공의 방향을 어느 정도 제어할 수 있습니다.
3. 게임 상태 관리
# 게임 상태 변수
score = 0
lives = 3
game_started = False
# 게임 루프 내에서의 상태 관리
# 공이 바닥에 떨어졌는지 확인
if ball.check_bottom():
lives -= 1
if lives <= 0:
if game_over_screen(score):
# 재시작
return main()
else:
running = False
else:
ball.reset(paddle)
# 모든 블록이 제거되었는지 확인
if active_blocks == 0:
if game_over_screen(score, True):
# 재시작
return main()
else:
running = False
게임의 다양한 상태(시작, 진행 중, 게임 오버, 승리)를 관리하고, 생명, 점수 등의 게임 변수를 추적합니다.
게임 실행 방법 🚀
- 필요한 라이브러리 설치
pip install pygame
- 위의 코드를 복사하여 텍스트 에디터에 붙여넣기
- 파일을 breakout.py와 같은 이름으로 저장
- 파이썬이 설치된 환경에서 터미널(명령 프롬프트)를 열고 파일을 저장한 디렉토리로 이동
- python breakout.py 명령어를 입력하여 게임 실행
확장 가능성 🌱
이 브레이크아웃 게임을 다음과 같은 방향으로 확장해볼 수 있습니다:
- 다양한 레벨 구현
- 여러 레벨 설계, 레벨마다 다른 블록 배치
- 난이도 증가 (공 속도 증가, 패들 크기 감소 등)
- 파워업 아이템 추가
- 패들 크기 증가/감소
- 공 속도 증가/감소
- 멀티볼 (여러 개의 공 동시 사용)
- 레이저 (패들에서 발사하여 블록 파괴)
- 시각적 개선
- 스프라이트 이미지 사용
- 파티클 효과 (블록 파괴 시)
- 애니메이션 효과
- 사운드 추가
- 배경 음악
- 다양한 효과음 (충돌, 게임 오버, 승리 등)
- 게임 기능 확장
- 점수 저장 및 하이스코어 시스템
- 커스텀 레벨 에디터
게임에서 사용된 프로그래밍 개념 📚
이번 게임을 통해 다음과 같은 파이썬 개념들을 활용해보았습니다:
- 객체 지향 프로그래밍 (OOP)
- 클래스를 활용한 게임 객체 모델링
- 상속, 캡슐화 등의 OOP 개념
- 충돌 감지 및 물리
- 사각형-원 충돌 감지
- 반사각 계산
- 속도와 방향 벡터 처리
- 게임 상태 관리
- 다양한 게임 상태 간 전환
- 조건 분기를 통한 게임 로직 구현
- 사용자 인터페이스
- 텍스트 렌더링
- 상태 표시 (점수, 생명 등)
- 이벤트 처리
- 키보드 입력 확인
- 게임 이벤트 처리 (충돌, 게임 오버 등)
알고리즘 설명 🧮
브레이크아웃 게임의 핵심 알고리즘들을 살펴보겠습니다:
반사각 계산 알고리즘
공이 패들에 부딪혔을 때 반사각을 계산하는 알고리즘은 다음과 같습니다:
- 패들 중심에서 공까지의 상대적 위치를 계산 (범위: -1.0 ~ 1.0)
- 이 상대적 위치를 기반으로 반사각 계산 (최대 60도)
- 삼각함수를 이용해 새로운 속도 벡터(x, y) 계산
relative_x = (self.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2)
bounce_angle = relative_x * (math.pi / 3) # 최대 60도 각도
speed = math.sqrt(self.speed_x**2 + self.speed_y**2)
self.speed_x = speed * math.sin(bounce_angle)
self.speed_y = -speed * math.cos(bounce_angle)
블록-공 충돌 감지 알고리즘
공이 블록과 충돌했을 때 어느 면에서 충돌했는지 판단하는 알고리즘입니다:
- 공과 블록의 충돌 여부 확인
- 충돌 시, 공의 중심에서 블록의 가장 가까운 점까지의 x, y 거리 계산
- x 거리가 y 거리보다 크면 좌우 충돌로 판단하고 x 속도 반전
- 그렇지 않으면 상하 충돌로 판단하고 y 속도 반전
# 충돌 방향 파악 (수직, 수평)
dx = ball.x - max(self.x, min(ball.x, self.x + self.width))
dy = ball.y - max(self.y, min(ball.y, self.y + self.height))
if abs(dx) > abs(dy):
ball.speed_x *= -1 # 좌우 충돌
else:
ball.speed_y *= -1 # 상하 충돌
다음 포스팅 예고 🔮
다음 포스팅에서는 두 명의 플레이어가 대결할 수 있는 클래식 게임 "퐁(Pong)" 게임을 만들어 볼 예정입니다. 멀티플레이어 게임 구현과 AI 대전 상대 프로그래밍에 대해 배워보겠습니다.
파이썬 게임 개발 여정, 계속 지켜봐 주세요! 궁금한 점이나 제안사항이 있으시면 댓글로 남겨주세요. 행복한 코딩 되세요! 🐍✨
'파이썬 기초문법 > 파이썬 게임 만들기' 카테고리의 다른 글
파이썬 게임 만들기 - 스페이스 인베이더(Space Invaders) 🚀👾 (0) | 2025.03.09 |
---|---|
파이썬 게임 만들기 - 퐁(Pong) 게임 🏓🏓 (0) | 2025.03.09 |
파이썬 게임 만들기 - 스네이크 게임 🐍🍎 (0) | 2025.03.08 |
파이썬 게임 만들기 - 틱택토 게임 ⭕❌ (0) | 2025.03.08 |
파이썬 게임 만들기 - 행맨 게임 👨🎨 (2) | 2025.03.08 |