Akashic Records

[파이썬 크래시 코스 예제] 외계인 침공 -2 본문

Python for Beginners

[파이썬 크래시 코스 예제] 외계인 침공 -2

Andrew's Akashic Records 2024. 6. 7. 15:07
728x90

AlienInvasion

 
책 "파이썬 크래스 코드"의 실전 예제 코드 2단계
이전 코드에서 탄환 발사 및 외계인 코드가 포함되었습니다.

"""설정 클래스 settings.py"""
class Settings:
    """외계인 침공의 설정을 저장하는 클래스"""
    def __init__(self):
        """게임 설정 초기화"""
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_color = (230,230,230)

        # 우주선 설정
        self.ship_speed = 2.5
        self.ship_limit = 3

        # 탄환 설정
        self.bullet_speed = 5.0
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = (60,60,60)
        self.bullet_allowed = 10

        # 외계인 설정
        self.alien_speed = 1.0
        self.fleet_drop_speed = 5
        # 1은 오른쪽, -1은 왼쪽입니다.
        self.fleet_direction = 1
        # 외계인 군단 최대 row 
        self.fleet_row = 3

 
Settings 클래스:

  • 게임의 설정을 저장합니다. 예를 들어, 화면 크기, 배경색, 우주선의 이동 속도 등을 설정합니다.
  • 한번의 전투에 투입할 우주선 수, 탄환 관련 설정, 외계인 관련 설정이 추가 되어 있습니다.
"""alien_invasion.py"""
import  sys
import pygame
from time import sleep

from settings import Settings
from game_stats import GameStats
from ship import Ship
from bullet import Bullet
from alien import Alien

class AlienInvasion:
    """게임 자원과 동작을 전체적으로 관리하는 클래스"""

    def __init__(self):
        """게임을 초기화 하고 자원을 만듭니다."""
        pygame.init()

        self.clock = pygame.time.Clock()
        self.settings = Settings()

        # 전체화면으로 실행하기
        # self.screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN)
        # self.settings.screen_width = self.screen.get_rect().width
        # self.settings.screen_height = self.screen.get_rect().height

        self.screen = pygame.display.set_mode((self.settings.screen_width, self.settings.screen_height))
        pygame.display.set_caption("Alien Invasion")
        # 게임 기록을 저장할 인스턴스를 만든다.
        self.stats = GameStats(self)
        self.ship = Ship(self)
        self.bullets = pygame.sprite.Group()
        self.aliens = pygame.sprite.Group()

        self._create_fleet()
        # 게임을 활성화 상태로 시작합니다.
        self.game_active = True

    def run_game(self):
        """게임의 메인 루프를 시작합니다."""

        while True:
            self._check_events()
            
            if self.game_active:
                self.ship.update()
                self._update_bullets()
                self._update_alien()
            
            self._update_screen()

            # 게임 프레임 속도를 인수로 받는다. 60은 초다 60회 실행되도록 조정한다.
            self.clock.tick(60)

    def _check_events(self):
        """키입력과 마우스 이벤트에 응답합니다."""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                self._check_keydown_events(event)
            elif event.type == pygame.KEYUP:
                self._check_keyup_events(event)
    def _check_keydown_events(self, event) -> None:
        """키를 누를때 실행됩니다."""
        if event.key == pygame.K_RIGHT:
            # 우주선을 오른쪽으로 이동시킵니다.
            self.ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            # 우주선을 왼쪽으로 이동시킵니다.
            self.ship.moving_left = True
        elif event.key == pygame.K_q:
            sys.exit()
        elif event.key == pygame.K_SPACE:
            self._fire_bullet()

    def _check_keyup_events(self, event) -> None:
        """키에서 손을 뗄 때 응답힙니다."""
        if event.key == pygame.K_RIGHT:
            # 우주선을 오른쪽으로 이동중지합니다.
            self.ship.moving_right = False
        elif event.key == pygame.K_LEFT:
            # 우주선을 왼쪽으로 이동중지합니다.
            self.ship.moving_left = False

    def _fire_bullet(self):
        """새 탄환을 만들어 탄환 그룹에 추가합니다."""
        if len(self.bullets) < self.settings.bullet_allowed:
            new_bullet = Bullet(self)
            self.bullets.add(new_bullet)

    def _update_bullets(self):
        """탄환 위치를 업데이트하고 사라진 탄환을 제거 합니다."""
        # 탄호나 위치를 업데이트 합니다.
        self.bullets.update()

        # 사라진 탄환을 제거합니다.
        for bullet in self.bullets.copy():
            if bullet.rect.bottom <= 0:
                self.bullets.remove(bullet)
        self._check_bullet_alien_collisions()

        # 현재 화면에 있는 탄환 수 로그
        print(len(self.bullets))

    def _check_bullet_alien_collisions(self):
        # 외계인을 맞힌 탄환이 있는지 확인합니다.
        # 맞흰 탄환이 있으면 탄환과 외계인을 제거합니다.

        collisions = pygame.sprite.groupcollide(
            self.bullets, self.aliens, True, True)

        if not self.aliens:
            # 남아 있는 탄환을 제거하고 함대를 새로 만듭니다.
            self.bullets.empty()
            self._create_fleet()

    def _ship_hit(self):
        """외계인이 우주선에 충돌할 때 할 작업"""

        if self.stats.ships_left > 0:
            # ships_left에서 1을 뺍니다
            self.stats.ships_left -= 1  # 1

            # 남아 있는 탄환과 외계인을 모두 제거합니다
            self.bullets.empty()  # 2
            self.aliens.empty()

            # 함대를 새로 만들고 우주선을 화면 하단 중앙으로 이동시킵니다
            self._create_fleet()
            self.ship.center_ship()

            # 일시 중지
            sleep(0.5)  # 4
        else:
            self.game_active = False

    def _update_alien(self):
        """함대에 속한 외계인의 위치를 모두 업데이트 한다."""
        self._check_fleet_edges()
        self.aliens.update()

        # 외계인과 우주선의 충돌 검색
        if pygame.sprite.spritecollideany(self.ship,self.aliens):
            self._ship_hit()
        # 화면 하단에 도달한 외계인을 찾습니다.
        self._check_aliens_bottom()

    def _update_screen(self):
        """화면의 이미지를 업데이트 하고 화면을 새로 그립니다."""
        # 루프를 반복할 때마다 화면을 다시 그린다.
        self.screen.fill(self.settings.bg_color)
        for bullet in self.bullets.sprites():
            bullet.draw_bullet()
        self.ship.blitme()
        self.aliens.draw(self.screen)

        # 가장 최근 그린 화면을 표시합니다.
        pygame.display.flip()

    def _create_fleet(self):
        """외계인 함대를 만듭니다."""
        # 외계인 하나를 만들고, 공간이 없을 때까지 계속 추가합니다.
        # 외계인 사이으이 공간은 외계인의 너비와 높이와 같습니다.
        alien = Alien(self)
        alien_with, alien_height = alien.rect.size

        current_x, current_y = alien_with, alien_height
        current_fleet_row = 0
        while (current_y < (self.settings.screen_height -(3 * alien_height) - self.ship.rect.height)
               and current_fleet_row < self.settings.fleet_row):

            while current_x < (self.settings.screen_width -2 * alien_with):
                self._create_alien(current_x, current_y)
                current_x +=2 * alien_with
            # 한줄이 끝나으니 x 값을 초기화 하고 y 값은 늘립니다.
            current_x = alien_with
            current_y += 2*alien_height
            current_fleet_row += 1

    def _create_alien(self, x_position, y_position):
        """외계인 하나를 만들어 배치합니다."""
        new_alien = Alien(self)
        new_alien.x = x_position
        new_alien.rect.x = x_position
        new_alien.rect.y = y_position
        self.aliens.add(new_alien)

    def _check_fleet_edges(self):
        """"외계인이 화면 경계에 도달했을 때 반응합니다"""
        for alien in self.aliens.sprites(): # 0
            if alien.check_edges():
                self._change_fleet_direction() # 2
                break

    def _change_fleet_direction(self):
        """"함대 전체를 한 줄 내리고 좌우 방향을 바꿉니다"""
        for alien in self.aliens.sprites():
            alien.rect.y += self.settings.fleet_drop_speed # 3
        self.settings.fleet_direction *= -1

    def _check_aliens_bottom(self):
        """화면 하단에 도달한 외계인이 있는지 확인합니다. """
        for alien in self.aliens.sprites():
            if alien.rect.bottom >= self.settings.screen_height:
                # 우주선에 외계인이 충돌할 때와 똑같이 반응한다.
                self._ship_hit()
                break

if __name__ == '__main__':
    # 게임 인스턴스를 만들고 게임을 실행합니다.
    ai = AlienInvasion()
    ai.run_game()

 
AlienInvasion 클래스:

모듈 임포트
게임에 필요한 모듈과 클래스를 임포트합니다. `pygame`은 게임 개발을 위한 모듈이며, `sys`와 `sleep`는 시스템 작업과 일시 중지 기능을 위해 사용됩니다. `Settings`, `GameStats`, `Ship`, `Bullet`, `Alien` 클래스는 게임의 다양한 설정, 통계, 우주선, 탄환, 외계인 객체를 관리합니다.

이 클래스는 게임의 주요 로직을 처리합니다. 게임의 자원과 동작을 전체적으로 관리하며, 주요 메소드는 다음과 같습니다:
 - `__init__`: 게임을 초기화하고, 화면 설정, 게임 상태, 우주선, 탄환, 외계인 그룹을 초기화합니다.
 - `run_game`: 게임의 메인 루프를 실행합니다. 이 루프 안에서 게임의 이벤트를 체크하고, 객체의 상태를 업데이트하며, 화면을 갱신합니다.

1. 이벤트 처리

  • `_check_events`: 키 입력과 마우스 이벤트를 처리합니다.
  • `_check_keydown_events`와 `_check_keyup_events`: 키를 누르고 떼는 이벤트에 대응하여 우주선의 움직임을 제어하거나, 게임을 종료합니다.

2. 게임 로직

  • `_fire_bullet`: 탄환을 발사합니다.
  • `_update_bullets`: 탄환의 위치를 업데이트하고 화면 밖으로 나간 탄환을 제거합니다.
  • `_check_bullet_alien_collisions`: 탄환과 외계인의 충돌을 검사합니다.
  • `_ship_hit`: 우주선이 외계인과 충돌했을 때의 로직을 처리합니다.
  • `_update_alien`: 외계인의 위치를 업데이트하고, 우주선과의 충돌을 검사합니다.
  • `_update_screen`: 게임 화면을 업데이트하고 새로 그립니다.

3. 외계인 함대 관리

  • `_create_fleet`: 외계인 함대를 생성합니다.
  • `_create_alien`: 개별 외계인을 생성하고 위치를 지정합니다.
  • `_check_fleet_edges`와 `_change_fleet_direction`: 외계인이 화면 경계에 도달했을 때 방향을 전환합니다.
"""ship.py"""
import pygame

class Ship:
    """ 우주선을 관리하는 클래스"""

    def __init__(self, ai_game):
        """우주선을 초기화하고 시작 위치를 설정합니다."""
        self.screen = ai_game.screen
        self.settings = ai_game.settings
        self.screen_rect = ai_game.screen.get_rect()

        # 우주선 이미지를 불러오고 사각형을 가져옵니다.
        self.image = pygame.image.load('images/ship.png')
        self.rect = self.image.get_rect()

        # 우주선의 초기 위치는 화면 하단 중앙입니다.
        self.rect.midbottom = self.screen_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.screen_rect.right:
            self.x += self.settings.ship_speed

        if self.moving_left and self.rect.left > 0:
            self.x -= self.settings.ship_speed

        # self.x 를 통해 rect 객체를 업테이트 합니다.
        self.rect.x = self.x
    def blitme(self):
        """우주선을 현재 위치에 그립니다."""
        self.screen.blit(self.image, self.rect)

    def center_ship(self):
        """우주선을 화면 상단 중앙으로 이동시킵니다."""
        self.rect.midbottom = self.screen_rect.midbottom
        self.x = float(self.rect.x)

images/ship.png

 

`ship.py` 스크립트는 Pygame을 사용하여 "우주선" 객체를 관리하는 코드입니다. 이 클래스는 우주선의 행동과 렌더링을 처리하며, 게임 내에서 우주선의 동작을 정의합니다. 

이 클래스는 우주선의 움직임과 화면 렌더링을 관리하는 기본적인 메커니즘을 제공합니다. 키 입력에 반응하여 우주선을 좌우로 움직이고, 우주선의 위치가 화면의 경계를 넘지 않도록 제한하는 로직이 포함되어 있습니다. 이와 같은 구조는 게임 개발에서 객체 지향적 접근을 통해 게임 요소를 효과적으로 관리할 수 있도록 해줍니다.

1. `__init__` 메소드:

  • 이 메소드는 우주선의 초기화 작업을 담당합니다. `ai_game` 인자를 통해 게임의 메인 클래스에서 설정과 화면 정보를 가져옵니다.
  • 우주선의 이미지를 로드하고, Pygame의 `get_rect()` 메소드를 사용해 이미지의 사각형 영역(위치와 크기를 결정하는 데 사용됨)을 설정합니다.
  • 우주선을 화면의 하단 중앙에 배치합니다.
  • 우주선의 x 좌표는 부동 소수점으로 관리하여 더 세밀한 위치 조정이 가능하게 합니다.

2. `update` 메소드:

  • 이 메소드는 게임 루프에서 지속적으로 호출되며, 우주선의 위치를 업데이트합니다.
  • 우주선이 화면의 경계를 넘지 않도록 하면서, `moving_right` 또는 `moving_left` 플래그에 따라 우주선을 좌우로 움직이게 합니다.
  • 이동 속도는 `settings.ship_speed` 값에 의해 결정되며, 이는 게임의 다른 설정 파일에서 정의됩니다.
  • 우주선의 위치는 `self.x`를 기반으로 계산되어 `self.rect.x`에 반영됩니다.

3. `blitme` 메소드:

  • 이 메소드는 Pygame의 `blit()` 함수를 사용하여 우주선의 현재 위치에 이미지를 그립니다. 이는 화면을 갱신할 때마다 호출되어 우주선이 화면에 나타나게 합니다.

4. `center_ship` 메소드:

  • 이 메소드는 우주선을 게임 화면의 하단 중앙으로 다시 위치시킵니다. 이는 우주선이 외계인과 충돌하거나 다른 상황에서 초기 위치로 복귀해야 할 때 사용됩니다.
"""bullet.py"""
import pygame
from pygame.sprite import Sprite

class Bullet(Sprite):
    """우주선이 발사하는 탄환을 관리하는 클래스"""

    def __init__(self, ai_game):
        """우주선의 현재 위치에서 탄환 객체를 만듭니다."""
        super().__init__()
        self.screen = ai_game.screen
        self.settings = ai_game.settings
        self.color = self.settings.bullet_color

        # (0, 0)에 탄환 사각형을 만들고 위치를 설정합니다.
        self.rect = pygame.Rect(0,0,self.settings.bullet_width, self.settings.bullet_height)
        # 중앙에 맞춘다.
        self.rect.midtop = ai_game.ship.rect.midtop

        # 탄환 위치를 부동 소수점 숫자로 저장합니다.
        self.y = float(self.rect.y)

    def update(self):
        """탄환이 화면 위 방향으로 이동합니다."""
        # 탄환 위치를 정확히 업데이트 합니다.
        self.y -= self.settings.bullet_speed

        # 사각현 위치를 업데이트 합니다.
        self.rect.y = self.y

    def draw_bullet(self):
        """화면에 탄환을 그립니다."""
        pygame.draw.rect(self.screen, self.color,self.rect)


`Bullet` 클래스는 "우주선이 발사하는 탄환"을 관리하는 코드입니다. 이 클래스는 Pygame의 `Sprite` 클래스를 상속받아 구현되었으며, 주요 기능은 탄환의 초기화, 업데이트, 그리고 화면에 탄환을 그리는 것입니다.

탄환의 위치 업데이트와 렌더링을 위해 Pygame의 스프라이트와 그래픽 기능을 활용하며, 클래스의 구조는 게임 개발에서 객체 지향적 접근을 통해 게임 요소를 관리하는 좋은 예를 보여줍니다. 이 클래스를 통해 개발자는 탄환의 동작과 표현을 쉽게 제어하고 조정할 수 있습니다.

1. `__init__` 메소드:

  • 이 메소드는 탄환 객체의 초기화를 담당합니다. `ai_game` 인자를 통해 게임의 주요 설정과 화면 정보를 받아옵니다.
  • `super().__init__()` 호출을 통해 `Sprite` 클래스의 생성자를 실행시켜 Pygame의 스프라이트 기능을 활성화합니다.
  • 탄환의 색상(`self.color`), 너비(`self.settings.bullet_width`), 높이(`self.settings.bullet_height`)는 `ai_game`의 설정에서 가져옵니다.
  • `pygame.Rect`를 사용해 탄환의 사각형 영역을 정의합니다. 이 영역은 탄환의 위치와 크기를 결정합니다.
  • 탄환의 시작 위치는 우주선의 중앙 상단(`midtop`) 위치에 설정됩니다.
  • 탄환의 y 좌표는 부동 소수점 숫자로 관리되어 탄환의 수직 이동을 더 정밀하게 제어할 수 있습니다.

2. `update` 메소드:

  • 이 메소드는 게임 루프에서 지속적으로 호출되어 탄환의 위치를 업데이트합니다.
  • 탄환은 설정된 속도(`self.settings.bullet_speed`)만큼 화면 위로 이동하며, 이동 속도는 설정 파일에서 정의됩니다.
  • `self.y` 값을 감소시켜 탄환을 상승시키고, 이를 `self.rect.y`에 반영하여 탄환의 실제 위치를 업데이트합니다.

3. `draw_bullet` 메소드:

  • 이 메소드는 Pygame의 `draw.rect` 함수를 사용하여 화면에 탄환을 그립니다. 탄환의 색상과 현재 위치의 사각형을 사용하여 탄환을 렌더링합니다.
"""alien.py"""

import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
    """함대에 속한 외계인 하나를 나타내는 클래스"""

    def __init__(self, ai_game):
        """외계인을 초기화하고 시작 위치를 설정합니다"""
        super().__init__()
        self.screen = ai_game.screen
        self.settings = ai_game.settings

        # 외계인 이미지를 불러와 rect 속성을 설정합니다
        self.image = pygame.image.load('images/alien.png')
        self.rect = self.image.get_rect()

        # 외계인은 화면 좌측 상단 근처에 만듭니다
        self.rect.x = self.rect.width # 1
        self.rect.y = self.rect.height

        # 외계인의 정확한 가로 위치를 저장합니다
        self.x = float(self.rect.x) # 2

    def check_edges(self):
        """외계인이 화면 경계에 도달하면 true을 반환한다."""
        screen_rec = self.screen.get_rect()
        return (self.rect.right >= screen_rec.right) or (self.rect.left <= 0)

    def update(self):
        """외계인을 오른쪽으로 움직입니다."""
        self.x += self.settings.alien_speed * self.settings.fleet_direction
        self.rect.x = self.x

images/alien.png

 

`alien.py` 스크립트는 Pygame을 사용하여 "외계인" 객체를 관리하는 클래스를 정의합니다. 이 클래스는 Pygame의 `Sprite` 클래스를 상속받아 외계인의 동작과 렌더링을 처리하며, 게임 내에서 외계인의 행동을 정의합니다.

`Alien` 클래스는 외계인의 이동과 경계 검사 로직을 효과적으로 관리합니다. 외계인이 화면 가장자리에 도달할 때 함대의 이동 방향을 변경하는 기능은 게임의 도전 요소를 증가시키며, Pygame의 객체 지향적 접근을 통해 게임 요소를 쉽게 확장하고 관리할 수 있도록 합니다. 이 클래스를 통해 개발자는 외계인의 동작을 자유롭게 제어하고 조정할 수 있습니다.

 

1. `__init__` 메소드:

  • 외계인 객체 초기화: 게임의 메인 클래스인 `ai_game`에서 화면과 설정을 가져옵니다. 이를 통해 외계인의 동작과 관련된 설정에 접근합니다.
  • 이미지 로딩: `pygame.image.load` 함수를 사용해 외계인의 이미지를 로드하고, `get_rect()` 메소드로 해당 이미지의 사각형 영역을 설정합니다. 이 영역은 외계인의 위치와 크기를 결정하는 데 사용됩니다.
  • 초기 위치 설정: 외계인은 화면의 좌측 상단 근처에 배치되며, 위치는 외계인 이미지의 너비와 높이를 이용하여 설정됩니다. 이렇게 하면 외계인이 화면 가장자리에 너무 밀착되지 않고 적절한 간격을 유지하며 배치됩니다.
  • 가로 위치 저장: 외계인의 x 좌표는 부동 소수점 숫자로 관리되어 이동을 더 정밀하게 제어할 수 있습니다.

2. `check_edges` 메소드:

  • 이 메소드는 외계인이 화면의 좌우 경계에 도달했는지 검사합니다. 만약 외계인의 오른쪽 끝이 화면의 오른쪽 끝에 도달하거나, 왼쪽 끝이 화면의 왼쪽 끝에 도달하면 `True`를 반환합니다. 이 정보는 함대의 이동 방향을 결정할 때 사용됩니다.

3. `update` 메소드:

  • 외계인 이동: `update` 메소드는 외계인을 설정된 속도(`alien_speed`)와 함대의 현재 이동 방향(`fleet_direction`)에 따라 가로로 이동시킵니다. `fleet_direction` 값에 따라 외계인은 오른쪽이나 왼쪽으로 이동하며, 이 값은 외계인이 화면 경계에 도달할 때 변경됩니다.
  • 위치 업데이트: 계산된 새 위치(`self.x`)는 외계인의 사각형 영역(`self.rect.x`)에 반영되어 외계인의 실제 화면 위치를 업데이트합니다.
"""game_stats.py"""

class GameStats:
    """'외계인 침공 게임 기록"""
    def __init__(self, ai_game):
        """기록 초기화"""
        self.settings = ai_game.settings
        self.reset_stats() # 1

    def reset_stats(self):
        """게임을 진행하는 동안 변하는 기록 초기화"""
        self.ships_left = self.settings.ship_limit

 

`GameStats` 클래스의 주된 목적은 게임의 진행 상태를 추적하고 게임 오버 조건을 관리하는 것입니다. 예를 들어, `ships_left`의 값이 0이 되면 게임이 종료될 수 있습니다. 이 클래스를 통해 게임의 중요한 상태 정보를 중앙에서 관리하고, 게임 로직과 사용자 인터페이스 간 일관된 데이터를 유지할 수 있습니다. 이로 인해 게임의 다른 부분들이 게임 상태에 따라 적절하게 반응하고, 게임의 진행 상황에 따라 사용자에게 적절한 피드백을 제공할 수 있습니다.


1. `__init__` 메소드:

 

  • 초기화: 게임의 메인 클래스(`ai_game`)에서 설정을 가져와서 인스턴스 변수로 저장합니다. 이 설정은 게임의 다양한 매개변수(예: 우주선의 수명, 탄환 속도 등)를 제공합니다.
  • 기록 초기화: `reset_stats()` 메소드를 호출하여 게임 진행 중에 변할 수 있는 통계를 초기화합니다. 이 초기화는 게임 시작 시, 또는 플레이어가 새 게임을 시작할 때마다 수행됩니다.

2. `reset_stats` 메소드:

  • 게임 중 변동 기록 초기화: 이 메소드는 게임 중 변동 가능한 여러 변수들을 초기 상태로 설정합니다. 가장 중요한 변수 중 하나는 `ships_left`로, 이는 플레이어가 게임 중 보유하고 있는 우주선의 수를 나타냅니다. 이 수는 `self.settings.ship_limit`에서 가져온 값으로 설정되며, 게임 설정에 따라 플레이어가 게임을 시작할 때 갖게 될 우주선의 최대 수를 결정합니다.
728x90
Comments