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()
- 본 강의에서 사용한 이미지는 이해를 돕기 위해 함께 공유합니다.
- 직접 사용해도 좋고, 연습용으로 자유롭게 수정해도 됩니다.
728x90
'⚙️ Python > 🎮 Pygame 실전' 카테고리의 다른 글
| [Pygame] 🏰 2D 타워 디펜스 게임 만들기 1강 | 개발 환경 준비 (0) | 2026.02.01 |
|---|---|
| [Pygame] ✈️ 전투기 슈팅 게임 만들기 13강 | 아이템 생성 & 회복 시스템 구현 (0) | 2026.01.23 |
| [Pygame] ✈️ 전투기 슈팅 게임 만들기 11강 | 움직이는 배경(세로 스크롤) 구현하기 (0) | 2026.01.21 |
| [Pygame] ✈️ 전투기 슈팅 게임 만들기 10강 | 게임 오버 & 재시작 구현하기 (0) | 2026.01.20 |
| [Pygame] ✈️ 전투기 슈팅 게임 만들기 9강 | 플레이어 체력 구현하기 (0) | 2026.01.19 |