파이썬으로 시작하는 게임 개발의 세계 🐍🎮
안녕하세요! Python Game Dev 블로그의 여덟 번째 포스팅입니다. 이전에는 숫자 맞추기, 퀴즈, 행맨, 틱택토, 스네이크, 브레이크아웃, 그리고 퐁 게임을 만들어보았는데요, 오늘은 약속드린 대로 클래식 아케이드 게임인 스페이스 인베이더(Space Invaders) 게임을 만들어보겠습니다.
오늘의 게임: 스페이스 인베이더(Space Invaders) 🚀👾
스페이스 인베이더는 1978년 다이토(Taito)에서 출시된 슈팅 게임으로, 아케이드 게임의 황금기를 이끈 대표작 중 하나입니다. 플레이어는 화면 하단의 우주선을 좌우로 움직이며 상단에서 내려오는 외계인들을 향해 미사일을 발사하는 게임입니다. 오늘은 파이썬과 Pygame을 사용하여 이 클래식 게임을 현대적으로 재해석해보겠습니다!
게임의 규칙 📜
- 플레이어는 화면 하단에 있는 우주선을 좌우로 움직일 수 있습니다.
- 외계인들은 화면 상단에서 시작하여 좌우로 움직이다가 벽에 닿으면 한 줄 아래로 내려옵니다.
- 플레이어는 스페이스 바를 눌러 미사일을 발사할 수 있습니다.
- 미사일이 외계인에 맞으면 외계인이 사라지고 점수를 얻습니다.
- 모든, 외계인을 처치하면 다음 레벨로 진행되며, 외계인의 속도가 빨라집니다.
- 외계인이 플레이어의 우주선에 닿거나 화면 하단에 도달하면 게임이 종료됩니다.
- 플레이어는 3개의 목숨을 가지고 시작합니다.
전체 코드 💻
import pygame
import random
import sys
import math
from pygame import mixer
# 게임 초기화
pygame.init()
mixer.init()
# 화면 설정
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Python Space Invaders")
# 색상 정의
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
PURPLE = (128, 0, 128)
BLUE = (0, 0, 255)
# 사운드 효과
try:
shoot_sound = mixer.Sound('shoot.wav')
explosion_sound = mixer.Sound('explosion.wav')
invader_killed_sound = mixer.Sound('invaderkilled.wav')
background_music = mixer.Sound('background.wav')
# 배경 음악 재생
background_music.play(-1) # -1은 무한 반복
except:
print("사운드 파일을 찾을 수 없습니다. 사운드 없이 게임이 실행됩니다.")
# 폰트 설정
font = pygame.font.SysFont('consolas', 30)
big_font = pygame.font.SysFont('consolas', 50)
# 플레이어 클래스
class Player:
def __init__(self):
self.x = WIDTH // 2
self.y = HEIGHT - 60
self.width = 50
self.height = 30
self.speed = 8
self.lives = 3
self.score = 0
self.cooldown = 0
self.cooldown_time = 15 # 발사 쿨다운 (프레임 단위)
def draw(self):
# 우주선 몸체
pygame.draw.rect(screen, GREEN, (self.x - self.width//2, self.y, self.width, self.height))
# 우주선 포탑
pygame.draw.rect(screen, GREEN, (self.x - 5, self.y - 10, 10, 10))
def move(self, direction):
if direction == "left" and self.x - self.width//2 > 0:
self.x -= self.speed
if direction == "right" and self.x + self.width//2 < WIDTH:
self.x += self.speed
def shoot(self):
if self.cooldown <= 0:
try:
shoot_sound.play()
except:
pass
self.cooldown = self.cooldown_time
return Missile(self.x, self.y - 10, -10) # -10은 위쪽 방향
return None
def update(self):
if self.cooldown > 0:
self.cooldown -= 1
# 외계인 클래스
class Alien:
def __init__(self, x, y, alien_type):
self.x = x
self.y = y
self.width = 40
self.height = 30
self.type = alien_type # 0, 1, 2 세 가지 타입 (점수 차등)
self.colors = [PURPLE, BLUE, RED] # 타입별 색상
self.point_values = [10, 20, 30] # 타입별 점수
def draw(self):
pygame.draw.rect(screen, self.colors[self.type],
(self.x - self.width//2, self.y - self.height//2,
self.width, self.height))
# 외계인 눈 그리기
eye_radius = 5
pygame.draw.circle(screen, WHITE,
(self.x - 10, self.y - 5), eye_radius)
pygame.draw.circle(screen, WHITE,
(self.x + 10, self.y - 5), eye_radius)
# 외계인 입 그리기
pygame.draw.rect(screen, WHITE,
(self.x - 15, self.y + 5, 30, 5))
# 미사일 클래스
class Missile:
def __init__(self, x, y, speed):
self.x = x
self.y = y
self.speed = speed # 음수면 위로, 양수면 아래로
self.width = 5
self.height = 15
self.active = True
def update(self):
self.y += self.speed
# 화면 밖으로 나가면 비활성화
if self.y < 0 or self.y > HEIGHT:
self.active = False
def draw(self):
if self.active:
color = GREEN if self.speed < 0 else RED # 플레이어/외계인 미사일 구분
pygame.draw.rect(screen, color,
(self.x - self.width//2, self.y - self.height//2,
self.width, self.height))
# 보호벽 클래스
class Barrier:
def __init__(self, x):
self.x = x
self.y = HEIGHT - 150
self.width = 80
self.height = 50
self.blocks = []
# 10x6 그리드로 보호벽 구성
for row in range(6):
for col in range(10):
# 보호벽 상단을 둥글게 만들기 위한 조건
if not (row == 0 and (col == 0 or col == 9)) and not (row == 1 and (col == 0 or col == 9)):
block_x = self.x - self.width//2 + col * 8
block_y = self.y - self.height//2 + row * 8
self.blocks.append(pygame.Rect(block_x, block_y, 8, 8))
def draw(self):
for block in self.blocks:
pygame.draw.rect(screen, GREEN, block)
def check_collision(self, missile):
if not missile.active:
return False
missile_rect = pygame.Rect(
missile.x - missile.width//2,
missile.y - missile.height//2,
missile.width, missile.height
)
for i, block in enumerate(self.blocks):
if block.colliderect(missile_rect):
self.blocks.pop(i) # 블록 제거
return True
return False
# 폭발 효과 클래스
class Explosion:
def __init__(self, x, y):
self.x = x
self.y = y
self.frames = 30 # 폭발 지속 시간
self.radius = 20 # 시작 반지름
self.current_frame = 0
def update(self):
self.current_frame += 1
return self.current_frame < self.frames
def draw(self):
radius = self.radius * (1 - self.current_frame / self.frames)
pygame.draw.circle(screen, RED, (int(self.x), int(self.y)), int(radius))
pygame.draw.circle(screen, PURPLE, (int(self.x), int(self.y)), int(radius * 0.8))
pygame.draw.circle(screen, WHITE, (int(self.x), int(self.y)), int(radius * 0.5))
# 게임 상태
class Game:
def __init__(self):
self.state = "menu" # menu, playing, game_over, level_complete
self.level = 1
self.reset_game()
def reset_game(self):
self.player = Player()
self.aliens = []
self.player_missiles = []
self.alien_missiles = []
self.barriers = []
self.explosions = []
self.alien_direction = 1 # 1: 오른쪽, -1: 왼쪽
self.alien_descent_speed = 20 * self.level # 레벨에 따라 속도 증가
self.alien_shoot_chance = 0.01 + (self.level * 0.005) # 레벨에 따라 발사 확률 증가
self.alien_move_timer = 0
self.alien_move_delay = max(30 - (self.level * 3), 5) # 레벨에 따라 이동 속도 증가
# 4개의 보호벽 생성
for i in range(4):
self.barriers.append(Barrier(WIDTH * (i + 1) / 5))
# 외계인 그리드 생성 (5행 11열)
for row in range(5):
for col in range(11):
alien_type = min(row // 2, 2) # 상단 2줄, 중간 2줄, 하단 1줄로 타입 구분
x = 100 + col * 60
y = 80 + row * 50
self.aliens.append(Alien(x, y, alien_type))
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if self.state == "menu" and event.key == pygame.K_SPACE:
self.state = "playing"
if self.state == "game_over" and event.key == pygame.K_SPACE:
self.level = 1
self.reset_game()
self.state = "playing"
if self.state == "level_complete" and event.key == pygame.K_SPACE:
self.level += 1
self.reset_game()
self.state = "playing"
if self.state == "playing" and event.key == pygame.K_SPACE:
missile = self.player.shoot()
if missile:
self.player_missiles.append(missile)
def update(self):
if self.state != "playing":
return
# 플레이어 업데이트
self.player.update()
# 키보드 입력 처리
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.player.move("left")
if keys[pygame.K_RIGHT]:
self.player.move("right")
# 외계인 이동
self.alien_move_timer += 1
if self.alien_move_timer >= self.alien_move_delay:
self.alien_move_timer = 0
move_down = False
for alien in self.aliens:
# 외계인이 화면 가장자리에 도달했는지 확인
if (alien.x + alien.width//2 + 5 >= WIDTH and self.alien_direction > 0) or \
(alien.x - alien.width//2 - 5 <= 0 and self.alien_direction < 0):
self.alien_direction *= -1
move_down = True
break
for alien in self.aliens:
alien.x += self.alien_direction * 10
if move_down:
alien.y += self.alien_descent_speed
# 외계인 미사일 발사
if self.aliens:
for alien in random.sample(self.aliens, min(len(self.aliens), 3)):
if random.random() < self.alien_shoot_chance:
self.alien_missiles.append(Missile(alien.x, alien.y + alien.height//2, 5))
# 미사일 업데이트
for missile in self.player_missiles[:]:
missile.update()
if not missile.active:
self.player_missiles.remove(missile)
for missile in self.alien_missiles[:]:
missile.update()
if not missile.active:
self.alien_missiles.remove(missile)
# 폭발 효과 업데이트
for explosion in self.explosions[:]:
if not explosion.update():
self.explosions.remove(explosion)
# 충돌 검사
self.check_collisions()
# 게임 상태 확인
self.check_game_state()
def check_collisions(self):
# 플레이어 미사일과 외계인 충돌
for missile in self.player_missiles[:]:
for alien in self.aliens[:]:
if (abs(missile.x - alien.x) < (alien.width//2 + missile.width//2) and
abs(missile.y - alien.y) < (alien.height//2 + missile.height//2) and
missile.active):
try:
invader_killed_sound.play()
except:
pass
self.explosions.append(Explosion(alien.x, alien.y))
self.player.score += alien.point_values[alien.type]
self.aliens.remove(alien)
missile.active = False
if missile in self.player_missiles:
self.player_missiles.remove(missile)
break
# 외계인 미사일과 플레이어 충돌
player_rect = pygame.Rect(
self.player.x - self.player.width//2,
self.player.y,
self.player.width,
self.player.height
)
for missile in self.alien_missiles[:]:
missile_rect = pygame.Rect(
missile.x - missile.width//2,
missile.y - missile.height//2,
missile.width,
missile.height
)
if player_rect.colliderect(missile_rect) and missile.active:
try:
explosion_sound.play()
except:
pass
self.explosions.append(Explosion(self.player.x, self.player.y))
self.player.lives -= 1
missile.active = False
self.alien_missiles.remove(missile)
# 미사일과 보호벽 충돌
for barrier in self.barriers:
for missile in self.player_missiles[:]:
if barrier.check_collision(missile) and missile in self.player_missiles:
missile.active = False
self.player_missiles.remove(missile)
for missile in self.alien_missiles[:]:
if barrier.check_collision(missile) and missile in self.alien_missiles:
missile.active = False
self.alien_missiles.remove(missile)
def check_game_state(self):
# 플레이어의 생명이 0이면 게임 오버
if self.player.lives <= 0:
self.state = "game_over"
# 모든 외계인이 제거되면 레벨 클리어
if not self.aliens:
self.state = "level_complete"
# 외계인이 너무 아래로 내려오면 게임 오버
for alien in self.aliens:
if alien.y + alien.height//2 > HEIGHT - 100:
self.state = "game_over"
break
def draw(self):
screen.fill(BLACK)
if self.state == "menu":
# 메뉴 화면
title = big_font.render("SPACE INVADERS", True, WHITE)
subtitle = font.render("Press SPACE to start", True, WHITE)
screen.blit(title, (WIDTH//2 - title.get_width()//2, HEIGHT//3))
screen.blit(subtitle, (WIDTH//2 - subtitle.get_width()//2, HEIGHT//2))
# 게임 설명
how_to_play = font.render("← → to move, SPACE to shoot", True, WHITE)
screen.blit(how_to_play, (WIDTH//2 - how_to_play.get_width()//2, HEIGHT//2 + 50))
elif self.state == "game_over":
# 게임 오버 화면
title = big_font.render("GAME OVER", True, RED)
subtitle = font.render(f"Final Score: {self.player.score}", True, WHITE)
restart = font.render("Press SPACE to play again", True, WHITE)
screen.blit(title, (WIDTH//2 - title.get_width()//2, HEIGHT//3))
screen.blit(subtitle, (WIDTH//2 - subtitle.get_width()//2, HEIGHT//2))
screen.blit(restart, (WIDTH//2 - restart.get_width()//2, HEIGHT//2 + 50))
elif self.state == "level_complete":
# 레벨 클리어 화면
title = big_font.render(f"LEVEL {self.level} COMPLETE!", True, GREEN)
subtitle = font.render(f"Score: {self.player.score}", True, WHITE)
next_level = font.render("Press SPACE for next level", True, WHITE)
screen.blit(title, (WIDTH//2 - title.get_width()//2, HEIGHT//3))
screen.blit(subtitle, (WIDTH//2 - subtitle.get_width()//2, HEIGHT//2))
screen.blit(next_level, (WIDTH//2 - next_level.get_width()//2, HEIGHT//2 + 50))
elif self.state == "playing":
# 게임 플레이 화면
# 플레이어, 외계인, 미사일, 보호벽 그리기
self.player.draw()
for alien in self.aliens:
alien.draw()
for missile in self.player_missiles:
missile.draw()
for missile in self.alien_missiles:
missile.draw()
for barrier in self.barriers:
barrier.draw()
for explosion in self.explosions:
explosion.draw()
# 점수와 생명 표시
score_text = font.render(f"Score: {self.player.score}", True, WHITE)
level_text = font.render(f"Level: {self.level}", True, WHITE)
lives_text = font.render(f"Lives: {self.player.lives}", True, WHITE)
screen.blit(score_text, (10, 10))
screen.blit(level_text, (WIDTH//2 - level_text.get_width()//2, 10))
screen.blit(lives_text, (WIDTH - lives_text.get_width() - 10, 10))
pygame.display.flip()
# 게임 실행
def main():
clock = pygame.time.Clock()
game = Game()
while True:
game.handle_events()
game.update()
game.draw()
clock.tick(60)
if __name__ == "__main__":
main()
게임 화면 📸
게임을 실행하면 다음과 같은 화면이 보입니다:
- 상단에는 점수, 레벨, 남은 목숨 정보가 표시됩니다.
- 화면 중앙에는 여러 줄로 배치된 외계인들이 있습니다.
- 화면 하단에는 플레이어의 우주선이 있습니다.
- 우주선과 외계인 사이에는 방어벽이 설치되어 있습니다.
화면 구성요소 코드 설명 📝
- 플레이어 우주선: 녹색 직사각형으로 표현되며, 좌/우 방향키로 움직일 수 있습니다.
# 플레이어 클래스
class Player:
def __init__(self):
self.x = WIDTH // 2
self.y = HEIGHT - 60
self.width = 50
self.height = 30
self.speed = 8
self.lives = 3
self.score = 0
self.cooldown = 0
self.cooldown_time = 15 # 발사 쿨다운 (프레임 단위)
def draw(self):
# 우주선 몸체
pygame.draw.rect(screen, GREEN, (self.x - self.width//2, self.y, self.width, self.height))
# 우주선 포탑
pygame.draw.rect(screen, GREEN, (self.x - 5, self.y - 10, 10, 10))
def move(self, direction):
if direction == "left" and self.x - self.width//2 > 0:
self.x -= self.speed
if direction == "right" and self.x + self.width//2 < WIDTH:
self.x += self.speed
def shoot(self):
if self.cooldown <= 0:
try:
shoot_sound.play()
except:
pass
self.cooldown = self.cooldown_time
return Missile(self.x, self.y - 10, -10) # -10은 위쪽 방향
return None
def update(self):
if self.cooldown > 0:
self.cooldown -= 1
2. 외계인: 색상이 다른 직사각형으로 표현되며, 각 색상마다 점수가 다릅니다.
# 외계인 클래스
class Alien:
def __init__(self, x, y, alien_type):
self.x = x
self.y = y
self.width = 40
self.height = 30
self.type = alien_type # 0, 1, 2 세 가지 타입 (점수 차등)
self.colors = [PURPLE, BLUE, RED] # 타입별 색상
self.point_values = [10, 20, 30] # 타입별 점수
def draw(self):
pygame.draw.rect(screen, self.colors[self.type],
(self.x - self.width//2, self.y - self.height//2,
self.width, self.height))
# 외계인 눈 그리기
eye_radius = 5
pygame.draw.circle(screen, WHITE,
(self.x - 10, self.y - 5), eye_radius)
pygame.draw.circle(screen, WHITE,
(self.x + 10, self.y - 5), eye_radius)
# 외계인 입 그리기
pygame.draw.rect(screen, WHITE,
(self.x - 15, self.y + 5, 30, 5))
3. 방어벽: 플레이어를 보호하는 녹색 직사각형으로, 체력이 줄어들면 색상이 변합니다.
# 보호벽 클래스
class Barrier:
def __init__(self, x):
self.x = x
self.y = HEIGHT - 150
self.width = 80
self.height = 50
self.blocks = []
# 10x6 그리드로 보호벽 구성
for row in range(6):
for col in range(10):
# 보호벽 상단을 둥글게 만들기 위한 조건
if not (row == 0 and (col == 0 or col == 9)) and not (row == 1 and (col == 0 or col == 9)):
block_x = self.x - self.width//2 + col * 8
block_y = self.y - self.height//2 + row * 8
self.blocks.append(pygame.Rect(block_x, block_y, 8, 8))
def draw(self):
for block in self.blocks:
pygame.draw.rect(screen, GREEN, block)
def check_collision(self, missile):
if not missile.active:
return False
missile_rect = pygame.Rect(
missile.x - missile.width//2,
missile.y - missile.height//2,
missile.width, missile.height
)
for i, block in enumerate(self.blocks):
if block.colliderect(missile_rect):
self.blocks.pop(i) # 블록 제거
return True
return False
게임 실행 방법 🚀
- 위의 전체 코드를 space_invaders.py 파일로 저장합니다.
- 사운드 효과를 위해 다음 파일들을 같은 폴더에 준비합니다:
- shoot.wav: 미사일 발사 효과음
- explosion.wav: 폭발 효과음
- invader.wav: 외계인 이동 효과음
- background.wav: 배경 음악
- 터미널에서 python space_invaders.py 명령으로 게임을 실행합니다.
게임 조작법:
- 좌/우 방향키: 우주선 이동
- 스페이스 바: 미사일 발사
- P 키: 게임 일시정지
- R 키: 게임 오버 시 재시작
게임에서 사용된 프로그래밍 개념 📚
- 객체 지향 프로그래밍(OOP): 각 게임 요소들을 클래스로 구현했습니다.
- 스프라이트(Sprite): Pygame의 Sprite 클래스를 상속받아 게임 객체를 관리했습니다.
- 충돌 감지: pygame.sprite.groupcollide()와 pygame.sprite.spritecollide()를 사용해 충돌을 감지했습니다.
- 이벤트 처리: 키보드 입력을 감지하여 플레이어를 제어했습니다.
- 그룹 관리: 여러 스프라이트를 그룹으로 관리하여 코드의 가독성과 성능을 향상시켰습니다.
알고리즘 설명 🧮
외계인 이동 알고리즘
외계인들은 좌우로 움직이다가 화면 경계에 닿으면 한 줄 아래로 내려옵니다:
# 외계인 이동
self.alien_move_timer += 1
if self.alien_move_timer >= self.alien_move_delay:
self.alien_move_timer = 0
move_down = False
for alien in self.aliens:
# 외계인이 화면 가장자리에 도달했는지 확인
if (alien.x + alien.width//2 + 5 >= WIDTH and self.alien_direction > 0) or \
(alien.x - alien.width//2 - 5 <= 0 and self.alien_direction < 0):
self.alien_direction *= -1
move_down = True
break
for alien in self.aliens:
alien.x += self.alien_direction * 10
if move_down:
alien.y += self.alien_descent_speed
# 외계인 미사일 발사
if self.aliens:
for alien in random.sample(self.aliens, min(len(self.aliens), 3)):
if random.random() < self.alien_shoot_chance:
self.alien_missiles.append(Missile(alien.x, alien.y + alien.height//2, 5))
레벨 시스템
모든 외계인을 처치하면 다음 레벨로 진행되며, 외계인의 속도가 증가합니다:
# 레벨 변경 함수
def change_level():
global level
level += 1
create_enemies()
def update(self):
self.rect.x += self.direction * (self.base_speed + level * 0.5)
다음 포스팅 예고 🔮
다음 포스팅에서는 클래식 RPG 게임의 기본인 '간단한 던전 크롤러(Simple Dungeon Crawler)' 게임을 구현해보겠습니다. 타일 기반 맵 생성, 캐릭터 스탯 시스템, 전투 메커니즘, 아이템 시스템 등 RPG 게임의 핵심 요소들을 파이썬으로 구현하는 방법을 배워보겠습니다.
파이썬 게임 개발 여정, 계속 지켜봐 주세요! 궁금한 점이나 제안사항이 있으시면 댓글로 남겨주세요. 행복한 코딩 되세요! 🐍✨
'파이썬 기초문법 > 파이썬 게임 만들기' 카테고리의 다른 글
파이썬 게임 만들기 - 플랫포머 게임(Platformer Game) 🏃♂️🌟 (1) | 2025.03.09 |
---|---|
파이썬 게임 만들기 - 간단한 던전 크롤러(Simple Dungeon Crawler) 🏰🗡️ (0) | 2025.03.09 |
파이썬 게임 만들기 - 퐁(Pong) 게임 🏓🏓 (0) | 2025.03.09 |
파이썬 게임 만들기 - 브레이크아웃 게임 🧱🏓 (1) | 2025.03.08 |
파이썬 게임 만들기 - 스네이크 게임 🐍🍎 (0) | 2025.03.08 |