본문 바로가기

[Pygame] ✈️ 전투기 슈팅 게임 만들기 11강 | 움직이는 배경(세로 스크롤) 구현하기

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


1. 이번 강의 목표

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

  • background.png 배경 이미지 로드
  • 배경을 아래로 계속 이동시키는 세로 스크롤
  • 화면 밖으로 나간 배경을 위로 올려 무한 반복(loop)

이 기능이 들어가면 게임의 완성도가 체감으로 확 올라갑니다.


2. 준비물: 배경 이미지

아래 파일을 준비합니다.

images/background.png

권장 크기는 게임 화면과 같은 480x640입니다.
(크기가 달라도 코드에서 스케일로 맞춥니다.)


3. 배경 이미지 로드 & 크기 맞추기

스프라이트와 동일하게 배경도 로드합니다.

bg_img = pygame.image.load("images/background.png").convert()
bg_img = pygame.transform.scale(bg_img, (SCREEN_WIDTH, SCREEN_HEIGHT))

 

  • 배경은 투명 처리가 필요 없으므로 convert()를 사용합니다.
  • 화면 크기로 스케일을 맞춰줍니다.

4. 배경 스크롤 원리

배경을 1장만 움직이면 끝에 도달했을 때 빈 공간이 생깁니다.
그래서 배경을 2장으로 겹쳐서 사용합니다.

  • 첫 번째 배경: bg_y1
  • 두 번째 배경: bg_y2

처음 위치는 이렇게 둡니다.

bg_y1 = 0
bg_y2 = -SCREEN_HEIGHT

 

그리고 매 프레임 아래로 이동시킵니다.

bg_speed = 2
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

5. 배경 그리기 순서 

배경은 항상 가장 먼저 그려야 합니다.
그래야 플레이어/총알/적이 배경 위에 정상 출력됩니다.

screen.blit(bg_img, (0, bg_y1))
screen.blit(bg_img, (0, bg_y2))

6. 게임 오버 화면에도 배경을 적용할까?


이번 강의에서는 설명 단순화를 위해 게임 오버에서는 기존 검은 배경을 그대로 둡니다.

원하면 게임 오버 화면에서도 배경을 계속 움직이게 만들 수도 있습니다.


7. 마무리

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

  • 배경 이미지 로드 및 화면 크기 맞추기
  • 2장 배경을 이용한 세로 스크롤
  • 배경 무한 반복(loop)

이제 화면이 “정지된 화면”이 아니라 진짜 게임처럼 살아 움직입니다.


전체 코드

더보기
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()
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)
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 = []

    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_speed = 3
enemy_spawn_delay = 60

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)

    # ===== 적 생성 =====
    enemy_timer += 1
    if enemy_timer >= enemy_spawn_delay:
        enemy_timer = 0
        enemy_rect = enemy_img.get_rect()
        enemy_rect.x = random.randint(0, SCREEN_WIDTH - enemy_rect.width)
        enemy_rect.y = -enemy_rect.height
        enemies.append(enemy_rect)

    # 적 이동
    for e in enemies:
        e.y += enemy_speed

    # 적 제거
    for e in enemies[:]:
        if e.top > SCREEN_HEIGHT:
            enemies.remove(e)

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

    # ===== 플레이어 피격 처리 =====
    if now - last_hit_time >= hit_delay:
        for e in enemies[:]:
            if player_rect.colliderect(e):
                hp -= 1
                last_hit_time = now
                enemies.remove(e)
                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 e in enemies:
        screen.blit(enemy_img, e)

    # 점수 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()

background.zip
0.01MB

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

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

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

목차