본문 바로가기

[Pygame] ✈️ 전투기 슈팅 게임 만들기 12강 | 적 타입 & 이동 패턴 추가하기

@도마22026. 1. 22. 19:00
728x90


1. 이번 강의 목표

이번 강의에서는 적 전투기를 두 종류로 확장합니다.

  • NORMAL 적: 기존처럼 직선 하강 (이미지: enemy.png)
  • ZIGZAG 적: 좌우로 흔들리며 하강 (이미지: enemy1.png)
  • 생성 비율은 NORMAL : ZIGZAG = 7 : 3

2. 준비물: 적 스프라이트 2장

폴더 구조에 아래 파일을 추가합니다.

images/
 ├─ enemy.png    (NORMAL)
 └─ enemy1.png   (ZIGZAG)

두 이미지 모두 투명 배경 PNG를 권장합니다.
크기는 기존과 동일하게 40x40으로 스케일해서 사용합니다.


3. 왜 적을 “Rect만” 저장하면 불편할까요?

지금까지는 적을 Rect만 저장해도 충분했지만,
적 종류가 늘어나면 “이 적은 어떤 타입인가?”, “속도는?”, “좌우 움직임은?” 같은 정보가 필요해집니다.

그래서 이번 강의부터 적은 아래처럼 딕셔너리 형태로 저장합니다.

  • rect: 위치/충돌
  • img: 그릴 이미지
  • type: NORMAL / ZIGZAG
  • speed: 내려오는 속도
  • vx: 좌우 속도(ZIGZAG 전용)

4. 적 스프라이트 2개 로드하기

기존 enemy.png에 더해 enemy1.png를 추가로 로드합니다.

enemy_img = pygame.image.load("images/enemy.png").convert_alpha()
enemy1_img = pygame.image.load("images/enemy1.png").convert_alpha()

enemy_img = pygame.transform.scale(enemy_img, ENEMY_SIZE)
enemy1_img = pygame.transform.scale(enemy1_img, ENEMY_SIZE)

5. 적 생성 로직: 7:3 비율로 타입 선택

random.random()을 사용하면 확률 분기가 쉽습니다.

  • 0.0 ~ 0.7 미만 → NORMAL
  • 0.7 이상 → ZIGZAG

ZIGZAG는 좌우 속도 vx를 랜덤 방향으로 줍니다.

r = random.random()
if r < 0.7:
    enemy_type = "NORMAL"
    img = enemy_img
    speed = 3
    vx = 0
else:
    enemy_type = "ZIGZAG"
    img = enemy1_img
    speed = 3
    vx = random.choice([-2, 2])

6. 적 이동 패턴 만들기

  • NORMAL: y만 증가
  • ZIGZAG: y 증가 + x 이동, 벽에 닿으면 방향 반전
for enemy in enemies:
    rect = enemy["rect"]
    rect.y += enemy["speed"]

    if enemy["type"] == "ZIGZAG":
        rect.x += enemy["vx"]
        if rect.left <= 0 or rect.right >= SCREEN_WIDTH:
            enemy["vx"] *= -1

7. 충돌/제거/그리기 코드도 타입 구조에 맞게 수정

이제 적은 딕셔너리이므로 enemy["rect"], enemy["img"]를 사용합니다.

  • 총알 vs 적
  • 플레이어 vs 적
  • 화면 밖 적 제거
  • 적 출력(blit)

8. 마무리

이번 강의에서는 다음을 구현했습니다.

  • 적 타입 2종 추가 (NORMAL / ZIGZAG)
  • NORMAL은 enemy.png, ZIGZAG는 enemy1.png 사용
  • 생성 비율 7:3 적용
  • ZIGZAG 좌우 이동 + 벽 반사 패턴 구현
  • 충돌/제거/그리기 로직을 타입 구조에 맞게 수정

이제 적이 다양해져서 게임이 훨씬 덜 심심해집니다.


전체 코드

더보기
import pygame
import sys
import random

pygame.init()

# 화면 설정
SCREEN_WIDTH = 480
SCREEN_HEIGHT = 640
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Shooting Game")

# FPS 설정
clock = pygame.time.Clock()
FPS = 60

# ===== 배경 로드 =====
bg_img = pygame.image.load("images/background.png").convert()
bg_img = pygame.transform.scale(bg_img, (SCREEN_WIDTH, SCREEN_HEIGHT))

bg_speed = 2
bg_y1 = 0
bg_y2 = -SCREEN_HEIGHT

# ===== 스프라이트 로드 =====
player_img = pygame.image.load("images/player.png").convert_alpha()
enemy_img = pygame.image.load("images/enemy.png").convert_alpha()     # NORMAL
enemy1_img = pygame.image.load("images/enemy1.png").convert_alpha()   # ZIGZAG
bullet_img = pygame.image.load("images/bullet.png").convert_alpha()

# ===== 스프라이트 크기 조정 =====
PLAYER_SIZE = (40, 40)
ENEMY_SIZE = (40, 40)
BULLET_SIZE = (6, 16)

player_img = pygame.transform.scale(player_img, PLAYER_SIZE)
enemy_img = pygame.transform.scale(enemy_img, ENEMY_SIZE)
enemy1_img = pygame.transform.scale(enemy1_img, ENEMY_SIZE)
bullet_img = pygame.transform.scale(bullet_img, BULLET_SIZE)

# 폰트
font = pygame.font.SysFont(None, 36)
big_font = pygame.font.SysFont(None, 72)

# ===== 게임 리셋 함수 =====
def reset_game():
    player_rect = player_img.get_rect()
    player_rect.centerx = SCREEN_WIDTH // 2
    player_rect.bottom = SCREEN_HEIGHT - 20

    bullets = []
    enemies = []  # 이제 적은 dict로 저장합니다.

    score = 0
    max_hp = 3
    hp = max_hp

    enemy_timer = 0

    last_fire_time = 0
    last_hit_time = 0

    game_over = False

    return player_rect, bullets, enemies, score, max_hp, hp, enemy_timer, last_fire_time, last_hit_time, game_over


# ===== 기본 설정 값 =====
player_speed = 5

bullet_speed = 8
fire_delay = 150

enemy_spawn_delay = 60  # 프레임 단위 (60이면 약 1초)
enemy_timer = 0

hit_delay = 1000  # ms

# 게임 초기화
player_rect, bullets, enemies, score, max_hp, hp, enemy_timer, last_fire_time, last_hit_time, game_over = reset_game()

# 게임 루프
running = True
while running:
    now = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    keys = pygame.key.get_pressed()

    # ===== 게임 오버 상태 처리 =====
    if game_over:
        if keys[pygame.K_r]:
            player_rect, bullets, enemies, score, max_hp, hp, enemy_timer, last_fire_time, last_hit_time, game_over = reset_game()

        screen.fill((0, 0, 0))

        over_text = big_font.render("GAME OVER", True, (255, 255, 255))
        score_text = font.render(f"Score : {score}", True, (255, 255, 255))
        restart_text = font.render("Press R to Restart", True, (255, 255, 255))

        screen.blit(over_text, (SCREEN_WIDTH // 2 - over_text.get_width() // 2, 220))
        screen.blit(score_text, (SCREEN_WIDTH // 2 - score_text.get_width() // 2, 320))
        screen.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2, 370))

        pygame.display.update()
        clock.tick(FPS)
        continue

    # ===== 배경 스크롤 업데이트 =====
    bg_y1 += bg_speed
    bg_y2 += bg_speed

    if bg_y1 >= SCREEN_HEIGHT:
        bg_y1 = -SCREEN_HEIGHT
    if bg_y2 >= SCREEN_HEIGHT:
        bg_y2 = -SCREEN_HEIGHT

    # ===== 플레이어 이동 =====
    if keys[pygame.K_w]:
        player_rect.y -= player_speed
    if keys[pygame.K_s]:
        player_rect.y += player_speed
    if keys[pygame.K_a]:
        player_rect.x -= player_speed
    if keys[pygame.K_d]:
        player_rect.x += player_speed

    # 화면 밖 제한
    if player_rect.left < 0:
        player_rect.left = 0
    if player_rect.right > SCREEN_WIDTH:
        player_rect.right = SCREEN_WIDTH
    if player_rect.top < 0:
        player_rect.top = 0
    if player_rect.bottom > SCREEN_HEIGHT:
        player_rect.bottom = SCREEN_HEIGHT

    # ===== 총알 발사(연사 제한) =====
    if keys[pygame.K_SPACE]:
        if now - last_fire_time >= fire_delay:
            last_fire_time = now
            bullet_rect = bullet_img.get_rect()
            bullet_rect.centerx = player_rect.centerx
            bullet_rect.bottom = player_rect.top
            bullets.append(bullet_rect)

    # 총알 이동/제거
    for b in bullets:
        b.y -= bullet_speed
    for b in bullets[:]:
        if b.bottom < 0:
            bullets.remove(b)

    # ===== 적 생성(타입 2종, 7:3) =====
    enemy_timer += 1
    if enemy_timer >= enemy_spawn_delay:
        enemy_timer = 0

        r = random.random()
        if r < 0.7:
            enemy_type = "NORMAL"
            img = enemy_img
            speed = 3
            vx = 0
        else:
            enemy_type = "ZIGZAG"
            img = enemy1_img
            speed = 3
            vx = random.choice([-2, 2])

        rect = img.get_rect()
        rect.x = random.randint(0, SCREEN_WIDTH - rect.width)
        rect.y = -rect.height

        enemies.append({
            "rect": rect,
            "img": img,
            "type": enemy_type,
            "speed": speed,
            "vx": vx
        })

    # ===== 적 이동/제거 =====
    for enemy in enemies:
        rect = enemy["rect"]
        rect.y += enemy["speed"]

        if enemy["type"] == "ZIGZAG":
            rect.x += enemy["vx"]
            if rect.left <= 0 or rect.right >= SCREEN_WIDTH:
                enemy["vx"] *= -1

    for enemy in enemies[:]:
        if enemy["rect"].top > SCREEN_HEIGHT:
            enemies.remove(enemy)

    # ===== 충돌 처리 (총알 vs 적) =====
    for b in bullets[:]:
        for enemy in enemies[:]:
            if b.colliderect(enemy["rect"]):
                bullets.remove(b)
                enemies.remove(enemy)
                score += 10
                break

    # ===== 플레이어 피격 처리 (플레이어 vs 적) =====
    if now - last_hit_time >= hit_delay:
        for enemy in enemies[:]:
            if player_rect.colliderect(enemy["rect"]):
                hp -= 1
                last_hit_time = now
                enemies.remove(enemy)
                break

    # 게임 오버 체크
    if hp <= 0:
        game_over = True

    # ===== 화면 그리기 =====
    screen.blit(bg_img, (0, bg_y1))
    screen.blit(bg_img, (0, bg_y2))

    screen.blit(player_img, player_rect)

    for b in bullets:
        screen.blit(bullet_img, b)

    for enemy in enemies:
        screen.blit(enemy["img"], enemy["rect"])

    # 점수 UI
    score_ui = font.render(f"Score : {score}", True, (255, 255, 255))
    screen.blit(score_ui, (10, 10))

    # HP UI (숫자)
    hp_ui = font.render(f"HP : {hp}", True, (255, 255, 255))
    screen.blit(hp_ui, (10, 45))

    # HP UI (바)
    bar_w = 100
    bar_h = 12
    bar_x = 80
    bar_y = 53
    pygame.draw.rect(screen, (80, 80, 80), (bar_x, bar_y, bar_w, bar_h))
    current_w = int(bar_w * (hp / max_hp))
    pygame.draw.rect(screen, (255, 255, 255), (bar_x, bar_y, current_w, bar_h))

    pygame.display.update()
    clock.tick(FPS)

pygame.quit()
sys.exit()

images.zip
0.01MB

  • 본 강의에서 사용한 이미지는 이해를 돕기 위해 함께 공유합니다.
  • 직접 사용해도 좋고, 연습용으로 자유롭게 수정해도 됩니다.

728x90
도마2
@도마2 :: 도마의 코드노트

초보자를 위한 코딩 강의를 정리합니다. 파이썬부터 C#, Unity 게임 제작까지 차근차근 기록합니다. — 도마

목차