파이썬 기초문법/파이썬 게임 만들기

파이썬 게임 만들기 - 플래피 버드 🐦🌵

Family in August 2025. 3. 15. 10:24
반응형

파이썬으로 만드는 플래피 버드: 단순함 속의 중독성 🐍🎮🐦

안녕하세요! Python Game Dev 블로그의 열여섯 번째 포스팅입니다. 지난 포스팅에서는 고전 게임 테트리스를 구현해보았는데요. 오늘은 약속드린 대로 '파이썬으로 만드는 플래피 버드(Flappy Bird)'를 만들어보겠습니다. 모바일 게임계를 강타했던 이 단순하면서도 중독성 강한 게임을 Pygame으로 함께 구현해봅시다!

오늘의 게임: 파이썬으로 만드는 플래피 버드 🐦🌵

플래피 버드는 2013년 베트남 개발자 응우옌 하 동(Dong Nguyen)이 개발한 게임으로, 단순한 메커니즘에도 불구하고 엄청난 인기를 끌었습니다. 화면을 터치하여 새를 점프시키고 파이프 장애물을 피하는 간단한 게임이지만, 그 어려움과 중독성으로 전 세계적인 현상이 되었죠. 오늘은 Pygame 라이브러리를 사용해 이 게임을 재현해보겠습니다.

게임의 규칙 📜

  • 플레이어는 작은 새(버드)를 조종합니다
  • 스페이스바를 누르면 새가 위로 점프합니다
  • 아무 입력이 없으면 새는 중력에 의해 떨어집니다
  • 파이프 장애물이 일정한 간격으로 나타납니다
  • 파이프 사이의 빈 공간을 통과해야 합니다
  • 파이프나 바닥, 천장에 부딪히면 게임 오버됩니다
  • 각 파이프를 성공적으로 통과할 때마다 점수가 1점씩 증가합니다
  • 게임의 목표는 최대한 많은 점수를 획득하는 것입니다

전체 코드 💻

import pygame
import sys
import random

# 게임 초기화
pygame.init()

# 화면 설정
WIDTH, HEIGHT = 400, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('파이썬으로 만드는 플래피 버드')

# 색상 정의
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)

# 게임 변수
gravity = 0.25
bird_movement = 0
score = 0
high_score = 0
game_active = False
score_font = pygame.font.SysFont('malgungothic', 30)
game_over_font = pygame.font.SysFont('malgungothic', 40)

# 배경 설정
bg_surface = pygame.Surface((WIDTH, HEIGHT))
bg_surface.fill((113, 197, 207))  # 하늘색 배경

# 바닥 설정
floor_surface = pygame.Surface((WIDTH, 100))
floor_surface.fill((222, 216, 149))  # 황토색 바닥
floor_y_pos = HEIGHT - 100

# 새(플레이어) 설정
class Bird:
    def __init__(self):
        self.x = 100
        self.y = HEIGHT // 2
        self.radius = 15
        self.movement = 0
        self.alive = True
    
    def draw(self):
        # 새의 몸체
        pygame.draw.circle(screen, YELLOW, (self.x, self.y), self.radius)
        # 새의 눈
        pygame.draw.circle(screen, BLACK, (self.x + 8, self.y - 5), 4)
        # 새의 부리
        pygame.draw.polygon(screen, RED, [(self.x + 15, self.y), (self.x + 25, self.y), (self.x + 15, self.y + 5)])
    
    def apply_gravity(self):
        self.movement += gravity
        self.y += self.movement
    
    def jump(self):
        self.movement = -5
    
    def check_collision(self, pipes):
        # 바닥 또는 천장 충돌 확인
        if self.y <= 0 or self.y >= floor_y_pos - self.radius:
            return True
        
        # 파이프 충돌 확인
        for pipe in pipes:
            # 상단 파이프 충돌
            if self.x + self.radius > pipe.x and self.x - self.radius < pipe.x + pipe.width:
                if self.y - self.radius < pipe.height:
                    return True
            
            # 하단 파이프 충돌
            if self.x + self.radius > pipe.x and self.x - self.radius < pipe.x + pipe.width:
                if self.y + self.radius > pipe.height + pipe.gap:
                    return True
        
        return False

# 파이프(장애물) 설정
class Pipe:
    def __init__(self):
        self.x = WIDTH + 100
        self.height = random.randint(150, 350)
        self.gap = 150  # 파이프 사이 간격
        self.width = 60
        self.passed = False
        self.speed = 2
    
    def move(self):
        self.x -= self.speed
    
    def draw(self):
        # 상단 파이프
        pygame.draw.rect(screen, GREEN, (self.x, 0, self.width, self.height))
        # 하단 파이프
        pygame.draw.rect(screen, GREEN, (self.x, self.height + self.gap, self.width, HEIGHT - self.height - self.gap - 100))
        
        # 파이프 상단과 하단에 테두리 추가
        pygame.draw.rect(screen, (34, 139, 34), (self.x, self.height - 20, self.width + 10, 20))
        pygame.draw.rect(screen, (34, 139, 34), (self.x, self.height + self.gap, self.width + 10, 20))

# 게임 재시작 함수
def reset_game():
    global bird, pipes, score, game_active
    bird = Bird()
    pipes = [Pipe()]
    score = 0
    game_active = True

# 객체 생성
bird = Bird()
pipes = [Pipe()]
pipe_spawn_timer = 0

# 게임 루프
clock = pygame.time.Clock()
while True:
    # 이벤트 처리
    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:
                if game_active:
                    bird.jump()
                else:
                    reset_game()

    # 배경 그리기
    screen.blit(bg_surface, (0, 0))
    
    if game_active:
        # 새 업데이트
        bird.apply_gravity()
        bird.draw()
        
        # 파이프 업데이트
        pipe_spawn_timer += 1
        if pipe_spawn_timer >= 120:  # 약 2초마다 새 파이프 생성
            pipes.append(Pipe())
            pipe_spawn_timer = 0
        
        # 파이프 그리기 및 이동
        pipes_to_remove = []
        for pipe in pipes:
            pipe.move()
            pipe.draw()
            
            # 파이프 통과 점수 처리
            if pipe.x + pipe.width < bird.x and not pipe.passed:
                score += 1
                pipe.passed = True
            
            # 화면 밖으로 나간 파이프 제거
            if pipe.x < -pipe.width:
                pipes_to_remove.append(pipe)
        
        # 파이프 제거
        for pipe in pipes_to_remove:
            pipes.remove(pipe)
        
        # 충돌 감지
        if bird.check_collision(pipes):
            game_active = False
            if score > high_score:
                high_score = score
        
        # 점수 표시
        score_text = score_font.render(f'점수: {score}', True, WHITE)
        screen.blit(score_text, (10, 10))
        
    else:
        # 게임 오버 화면
        game_over_text = game_over_font.render('GAME OVER', True, RED)
        score_text = score_font.render(f'점수: {score}', True, WHITE)
        high_score_text = score_font.render(f'최고 점수: {high_score}', True, WHITE)
        restart_text = score_font.render('스페이스바를 눌러 재시작', True, WHITE)
        
        screen.blit(game_over_text, (WIDTH // 2 - game_over_text.get_width() // 2, HEIGHT // 3))
        screen.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, HEIGHT // 3 + 50))
        screen.blit(high_score_text, (WIDTH // 2 - high_score_text.get_width() // 2, HEIGHT // 3 + 90))
        screen.blit(restart_text, (WIDTH // 2 - restart_text.get_width() // 2, HEIGHT // 3 + 150))
    
    # 바닥 그리기
    screen.blit(floor_surface, (0, floor_y_pos))
    
    # 화면 업데이트
    pygame.display.update()
    clock.tick(60)  # 60 FPS

게임 화면

 

게임 화면 📸

게임이 실행되면 다음과 같은 화면이 표시됩니다:

  1. 시작 화면: 게임을 시작하기 전 플레이어에게 스페이스바를 누르라는 안내가 표시됩니다.
  2. 게임 플레이 화면: 하늘색 배경에 노란색 새와 녹색 파이프가 보입니다. 화면 상단에는 현재 점수가 표시됩니다.
  3. 게임 오버 화면: 충돌 후 'GAME OVER' 메시지와 함께 점수, 최고 점수, 재시작 안내가 표시됩니다.

화면 구성요소

  • 배경: 하늘색 배경으로 게임의 분위기를 조성합니다.
  • 새(버드): 노란색 원형 몸체와 눈, 부리를 가진 플레이어 캐릭터입니다.
  • 파이프: 상단과 하단에 위치한 녹색 파이프로, 사이의 간격을 통과해야 합니다.
  • 바닥: 황토색 바닥으로 새가 떨어지면 충돌이 발생합니다.
  • 점수: 화면 상단에 현재 점수가 표시됩니다.

코드 설명 📝

클래스 구조

  1. Bird 클래스:
    • 새의 위치, 크기, 움직임을 관리합니다
    • apply_gravity(): 중력 효과를 구현하여 새가 자연스럽게 떨어지도록 합니다
    • jump(): 스페이스바를 누르면 새가 점프하는 기능을 구현합니다
    • check_collision(): 파이프, 바닥, 천장과의 충돌을 감지합니다
  2. Pipe 클래스:
    • 파이프의 위치, 높이, 너비, 간격을 관리합니다
    • move(): 파이프가 왼쪽으로 일정 속도로 이동하도록 합니다
    • draw(): 상단과 하단 파이프를 화면에 그립니다

주요 게임 매커니즘

1.중력 시스템:

def apply_gravity(self):
    self.movement += gravity
    self.y += self.movement
  • 새의 움직임에 중력 상수를 더해 자연스러운 하강 효과를 구현합니다.

2. 점프 메커니즘:

def jump(self):
    self.movement = -5
  • 새의 움직임 값을 음수로 설정하여 위로 점프하는 효과를 구현합니다.

3. 충돌 감지:

def check_collision(self, pipes):
    # 바닥 또는 천장 충돌 확인
    if self.y <= 0 or self.y >= floor_y_pos - self.radius:
        return True
    
    # 파이프 충돌 확인
    # ...
  • 새의 위치를 파이프, 바닥, 천장의 위치와 비교하여 충돌을 감지합니다.

4. 점수 시스템:

if pipe.x + pipe.width < bird.x and not pipe.passed:
    score += 1
    pipe.passed = True
  • 새가 파이프를 성공적으로 통과하면 점수가 증가합니다.

게임 실행 방법 🚀

  1. 파이썬과 Pygame 라이브러리가 설치되어 있어야 합니다.
  2. 위 코드를 flappy_bird.py 파일로 저장합니다.
  3. 터미널이나 명령 프롬프트에서 다음 명령어를 실행합니다:
    python flappy_bird.py
  4. 게임이 시작되면 스페이스바를 눌러 새를 점프시키고 파이프를 피하세요!
  5. 게임 오버 후 다시 스페이스바를 눌러 재시작할 수 있습니다.

게임에서 사용된 프로그래밍 개념 📚

  1. 객체 지향 프로그래밍(OOP): Bird와 Pipe 클래스를 사용하여 게임 객체를 구조화했습니다.
  2. 이벤트 처리: Pygame의 이벤트 시스템을 사용하여 키보드 입력을 감지합니다.
  3. 물리 시뮬레이션: 중력 효과를 구현하여 현실적인 움직임을 만들어냅니다.
  4. 충돌 감지: 히트박스를 사용한 충돌 감지 알고리즘을 구현했습니다.
  5. 게임 루프: 일정한 프레임 속도로 게임을 업데이트하고 렌더링합니다.
  6. 랜덤 생성: 파이프의 높이를 무작위로 생성하여 매번 다른 게임 경험을 제공합니다.

알고리즘 설명 🧮

파이프 생성 알고리즘

pipe_spawn_timer += 1
if pipe_spawn_timer >= 120:  # 약 2초마다 새 파이프 생성
    pipes.append(Pipe())
    pipe_spawn_timer = 0
  • 일정 시간(약 2초)마다 새로운 파이프를 생성합니다.
  • 이때 파이프의 높이는 무작위로 정해지며, 난이도와 다양성을 제공합니다.

충돌 감지 알고리즘

def check_collision(self, pipes):
    # 바닥 또는 천장 충돌 확인
    if self.y <= 0 or self.y >= floor_y_pos - self.radius:
        return True
    
    # 파이프 충돌 확인
    for pipe in pipes:
        # 상단 파이프 충돌
        if self.x + self.radius > pipe.x and self.x - self.radius < pipe.x + pipe.width:
            if self.y - self.radius < pipe.height:
                return True
        
        # 하단 파이프 충돌
        if self.x + self.radius > pipe.x and self.x - self.radius < pipe.x + pipe.width:
            if self.y + self.radius > pipe.height + pipe.gap:
                return True
    
    return False
  • 새의 좌표와 파이프의 좌표를 비교하여 충돌을 감지합니다.
  • 원형 히트박스와 사각형 충돌을 정확하게 계산합니다.

다음 포스팅 예고 🔮

다음 포스팅에서는 '파이썬으로 만드는 우주 슈팅 게임(Space Shooter)'에 대해 알아보겠습니다. 클래식 아케이드 스타일의 이 게임을 Pygame으로 구현해볼 예정입니다. 플레이어 우주선 제어, 적 생성 시스템, 다양한 무기 구현, 파티클 효과를 통한 폭발 애니메이션 등 게임의 핵심 요소들을 차근차근 살펴보겠습니다. 또한 난이도 증가 시스템과 보스 전투 기능도 추가하여 플레이어에게 도전적이고 재미있는 게임 경험을 제공하는 방법을 배워볼 계획입니다.

여러분의 의견과 질문은 언제나 환영합니다. 행복한 코딩 되세요! 🐍🎮✨

반응형