이 글에서는 pygame으로 제작하는 우주 전투 게임의 핵심 모듈들을 정리한다. 각 모듈은 단일 책임 원칙에 따라 분리되어 있으며, 게임 루프, 설정, 우주선, 탄환, 외계인, 통계 정보를 담당한다.
1. 메인 게임 루프 — invasion.py
import sys, pygame
from time import sleep
from config import Config
from fighter import Fighter
from laser import Laser
from invader import Invader
from scoreboard import Scoreboard
class Invasion:
"""게임 전체 자원과 동작을 관리하는 클래스"""
def __init__(self):
pygame.init()
self.cfg = Config()
self.screen = pygame.display.set_mode(
(self.cfg.width, self.cfg.height)
)
pygame.display.set_caption("Invasion")
self.stats = Scorehoard(self)
self.ship = Fighter(self)
self.lasers = pygame.sprite.Group()
self.swarm = pygame.sprite.Group()
self._spawn_swarm()
def run(self):
"""게임 루프"""
clock = pygame.time.Clock()
while True:
self._handle_events()
if self.stats.running:
self.ship.update()
self._update_lasers()
self._update_swarm()
self._draw()
clock.tick(60)
# ── 이벤트 처리 ──
def _handle_events(self):
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
sys.exit()
elif ev.type == pygame.KEYDOWN:
self._keydown(ev)
elif ev.type == pygame.KEYUP:
self._keyup(ev)
def _keydown(self, ev):
if ev.key == pygame.K_RIGHT:
self.ship.moving_right = True
elif ev.key == pygame.K_LEFT:
self.ship.moving_left = True
elif ev.key == pygame.K_SPACE:
self._fire_laser()
elif ev.key == pygame.K_q:
sys.exit()
def _keyup(self, ev):
if ev.key == pygame.K_RIGHT:
self.ship.moving_right = False
elif ev.key == pygame.K_LEFT:
self.ship.moving_left = False
# ── 탄환 ──
def _fire_laser(self):
if len(self.lasers) < self.cfg.max_lasers:
self.lasers.add(Laser(self))
def _update_lasers(self):
self.lasers.update()
for beam in self.lasers.copy():
if beam.rect.bottom < 0:
self.lasers.remove(beam)
pygame.sprite.groupcollide(self.lasers, self.swarm, True, True)
if not self.swarm:
self.lasers.empty()
self._spawn_swarm()
# ── 외계인 ──
def _spawn_swarm(self):
inv = Invader(self)
iw, ih = inv.rect.size
space_x = self.cfg.width - 2 * iw
cols = space_x // (2 * iw)
space_y = self.cfg.height - 12 * ih - self.ship.rect.height
rows = space_y // (2 * ih)
for r in range(rows):
for c in range(cols):
self._create_invader(c, r)
def _create_invader(self, col, row):
inv = Invader(self)
iw, ih = inv.rect.size
inv.x = iw + 2 * iw * col
inv.rect.x = inv.x
inv.rect.y = ih + 2 * ih * row
self.swarm.add(inv)
def _update_swarm(self):
self._check_edges()
self.swarm.update()
if pygame.sprite.spritecollideany(self.ship, self.swarm):
self._lose_life()
for inv in self.swarm:
if inv.rect.bottom >= self.cfg.height:
self._lose_life()
break
def _check_edges(self):
for inv in self.swarm:
if inv.at_edge():
self._shift_down()
break
def _shift_down(self):
for inv in self.swarm:
inv.rect.y += self.cfg.drop_speed
self.cfg.direction *= -1
def _lose_life(self):
if self.stats.lives > 0:
self.stats.lives -= 1
self.swarm.empty()
self.lasers.empty()
self._spawn_swarm()
self.ship.center()
sleep(0.5)
else:
self.stats.running = False
# ── 렌더링 ──
def _draw(self):
self.screen.fill(self.cfg.bg_color)
self.ship.blit()
for beam in self.lasers:
beam.draw()
self.swarm.draw(self.screen)
pygame.display.flip()
if __name__ == "__main__":
Invasion().run()
2. 설정 — config.py
class Config:
"""게임 전역 설정"""
def __init__(self):
self.width = 1200
self.height = 800
self.bg_color = (230, 230, 230)
# Fighter
self.ship_speed = 1.5
self.lives = 3
# Laser
self.laser_speed = 1.5
self.laser_width = 3
self.laser_height = 15
self.laser_color = (60, 60, 60)
self.max_lasers = 3
# Invader
self.invader_speed = 1.0
self.drop_speed = 10
self.direction = 1 # 1: right, -1: left
3. 우주선 — fighter.py
import pygame
class Fighter:
def __init__(self, game):
self.screen = game.screen
self.cfg = game.cfg
self.rect = pygame.image.load("images/ship.png").get_rect()
self.rect.midbottom = self.screen.get_rect().midbottom
self.x = float(self.rect.x)
self.moving_right = False
self.moving_left = False
def update(self):
if self.moving_right and self.rect.right < self.cfg.width:
self.x += self.cfg.ship_speed
if self.moving_left and self.rect.left > 0:
self.x -= self.cfg.ship_speed
self.rect.x = self.x
def blit(self):
self.screen.blit(pygame.image.load("images/ship.png"), self.rect)
def center(self):
self.rect.midbottom = self.screen.get_rect().midbottom
self.x = float(self.rect.x)
4. 탄환 — laser.py
import pygame
from pygame.sprite import Sprite
class Laser(Sprite):
def __init__(self, game):
super().__init__()
self.screen = game.screen
self.cfg = game.cfg
self.rect = pygame.Rect(0, 0, self.cfg.laser_width, self.cfg.laser_height)
self.rect.midtop = game.ship.rect.midtop
self.y = float(self.rect.y)
def update(self):
self.y -= self.cfg.laser_speed
self.rect.y = self.y
def draw(self):
pygame.draw.rect(self.screen, self.cfg.laser_color, self.rect)
5. 외계인 — invader.py
import pygame
from pygame.sprite import Sprite
class Invader(Sprite):
def __init__(self, game):
super().__init__()
self.screen = game.screen
self.cfg = game.cfg
self.image = pygame.image.load("images/alien.png")
self.rect = self.image.get_rect()
self.rect.x = self.rect.width
self.rect.y = self.rect.height
self.x = float(self.rect.x)
def at_edge(self):
screen_rect = self.screen.get_rect()
return self.rect.right >= screen_rect.right or self.rect.left <= 0
def update(self):
self.x += self.cfg.invader_speed * self.cfg.direction
self.rect.x = self.x
6. 통계 — scoreboard.py
class Scorehoard:
"""게임 진행 중 변하는 수치를 추적"""
def __init__(self, game):
self.cfg = game.cfg
self.reset()
self.running = True
def reset(self):
self.lives = self.cfg.lives