Pygame 기반 우주 전투 게임 핵심 모듈 구성

이 글에서는 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

태그: Pygame python game-development 2d-game space-shooter

8월 12일 14:21에 게시됨