728x90

7강에서는 마우스 클릭을 감지하고 클릭 좌표를 얻는 구조를 만들었습니다.
이번 8강에서는 그 구조를 그대로 활용해 타워를 설치합니다.
이번 강의 목표는 다음 3가지입니다.
- 타워(Tower) 클래스 만들기
- 클릭한 위치에 타워 설치하기
- 경로(Path) 위에는 설치하지 못하게 막기(기본 판정)
1. 타워 기본 클래스 만들기
이번 강의에서는 타워 공격 기능 없이 설치 및 출력만 구현합니다.
class Tower:
def __init__(self, pos):
self.x, self.y = pos
self.radius = TOWER_RADIUS
def draw(self, screen):
pygame.draw.circle(screen, (80, 160, 255), (self.x, self.y), self.radius)
pygame.draw.circle(screen, (30, 30, 30), (self.x, self.y), self.radius, 3)
2. 설치 비용(Gold) 추가
타워 설치 비용을 상수로 관리합니다.
TOWER_COST = 50
3. 경로 위 설치 금지(타워 크기 포함)
경로는 굵기 40px로 그려지고 있으므로,
경로 중심선에서 PATH_WIDTH/2 안쪽은 기본적으로 경로입니다.
타워는 반지름이 있으므로, 설치 판정은 다음처럼 처리합니다.
- 경로 반폭 + 타워 반지름 안쪽이면 설치 불가
threshold = PATH_WIDTH / 2 + TOWER_RADIUS
4. 타워끼리 겹침 방지
새로 설치하려는 타워 중심점과, 기존 타워 중심점의 거리를 계산합니다.
- 거리 < (반지름 + 반지름) 이면 겹침
- 타워 크기가 동일하므로 기본 기준은 2 * TOWER_RADIUS
def is_overlapping_tower(pos, towers):
px, py = pos
min_dist = TOWER_RADIUS * 2
for t in towers:
dx = px - t.x
dy = py - t.y
if (dx * dx + dy * dy) ** 0.5 < min_dist:
return True
return False
5. 클릭 이벤트에서 설치 판정 적용
설치 조건은 아래 순서로 처리합니다.
- 경로 위인지 확인
- 기존 타워와 겹치는지 확인
- 골드가 충분한지 확인
- 설치 및 골드 차감
6. 실행 결과

전체 코드
더보기
import pygame
import sys
pygame.init()
# -----------------------------
# 화면 설정
# -----------------------------
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Tower Defense")
clock = pygame.time.Clock()
FPS = 60
# -----------------------------
# 경로 설정
# -----------------------------
path_points = [
(0, 300),
(100, 300),
(100, 100),
(300, 100),
(300, 500),
(650, 500),
(650, 300),
(450, 300),
(450, 100),
(700, 100),
(700, 200),
(800, 200)
]
PATH_WIDTH = 40 # 경로 두께
TOWER_RADIUS = 16 # 타워 반지름
TOWER_COST = 50 # 타워 설치 비용
# -----------------------------
# 유틸: 점-선분 거리
# -----------------------------
def point_to_segment_distance(px, py, ax, ay, bx, by):
abx = bx - ax
aby = by - ay
apx = px - ax
apy = py - ay
ab_len_sq = abx * abx + aby * aby
if ab_len_sq == 0:
return ((px - ax) ** 2 + (py - ay) ** 2) ** 0.5
t = (apx * abx + apy * aby) / ab_len_sq
t = max(0.0, min(1.0, t))
cx = ax + abx * t
cy = ay + aby * t
dx = px - cx
dy = py - cy
return (dx * dx + dy * dy) ** 0.5
# -----------------------------
# 유틸: 경로 위(또는 너무 가까움) 판정
# 타워 반지름까지 포함해 경로 겹침 방지
# -----------------------------
def is_on_path(pos, path_points):
px, py = pos
threshold = PATH_WIDTH / 2 + TOWER_RADIUS
for i in range(len(path_points) - 1):
ax, ay = path_points[i]
bx, by = path_points[i + 1]
if point_to_segment_distance(px, py, ax, ay, bx, by) <= threshold:
return True
return False
# -----------------------------
# 유틸: 기존 타워와 겹침 판정
# -----------------------------
def is_overlapping_tower(pos, towers):
px, py = pos
min_dist = TOWER_RADIUS * 2
for t in towers:
dx = px - t.x
dy = py - t.y
if (dx * dx + dy * dy) ** 0.5 < min_dist:
return True
return False
# -----------------------------
# Enemy 클래스(기존 흐름 유지)
# -----------------------------
class Enemy:
def __init__(self, path_points, speed=2):
self.path_points = path_points
self.speed = speed
self.x, self.y = path_points[0]
self.target_index = 1
self.max_hp = 50
self.hp = self.max_hp
self.reward = 10
self.reached_end = False
self.is_dead = False
self.radius = 12
def update(self):
if self.reached_end or self.is_dead:
return
# 임시 데미지(추후 타워 공격으로 교체)
self.hp -= 0.05
if self.hp <= 0:
self.is_dead = True
return
if self.target_index >= len(self.path_points):
self.reached_end = True
return
tx, ty = self.path_points[self.target_index]
dx = tx - self.x
dy = ty - self.y
dist = (dx * dx + dy * dy) ** 0.5
if dist == 0:
self.target_index += 1
return
self.x += (dx / dist) * self.speed
self.y += (dy / dist) * self.speed
if dist <= self.speed:
self.x, self.y = tx, ty
self.target_index += 1
if self.target_index >= len(self.path_points):
self.reached_end = True
def draw(self, screen):
pygame.draw.circle(screen, (220, 80, 80), (int(self.x), int(self.y)), self.radius)
# -----------------------------
# Tower 클래스
# -----------------------------
class Tower:
def __init__(self, pos):
self.x, self.y = pos
self.radius = TOWER_RADIUS
def draw(self, screen):
pygame.draw.circle(screen, (80, 160, 255), (self.x, self.y), self.radius)
pygame.draw.circle(screen, (30, 30, 30), (self.x, self.y), self.radius, 3)
# -----------------------------
# 게임 변수
# -----------------------------
enemies = []
towers = []
spawn_interval = 1000
last_spawn_time = 0
base_hp = 20
gold = 100
font = pygame.font.SysFont(None, 32)
# -----------------------------
# 메인 루프
# -----------------------------
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 타워 설치(좌클릭)
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
click_pos = pygame.mouse.get_pos()
# 설치 판정: 경로 금지 + 타워 겹침 금지 + 골드 확인
if not is_on_path(click_pos, path_points) and not is_overlapping_tower(click_pos, towers):
if gold >= TOWER_COST:
towers.append(Tower(click_pos))
gold -= TOWER_COST
game_over = base_hp <= 0
if not game_over:
# 적 스폰
now = pygame.time.get_ticks()
if now - last_spawn_time >= spawn_interval:
enemies.append(Enemy(path_points))
last_spawn_time = now
# 적 업데이트
for enemy in enemies:
enemy.update()
# 도착한 적 처리(베이스 HP 감소)
arrived = [e for e in enemies if e.reached_end]
base_hp -= len(arrived)
# 죽은 적 처리(골드 획득)
dead = [e for e in enemies if e.is_dead]
gold += sum(e.reward for e in dead)
# 적 정리
enemies = [e for e in enemies if not e.reached_end and not e.is_dead]
# -----------------------------
# 화면 그리기
# -----------------------------
screen.fill((30, 30, 30))
pygame.draw.lines(screen, (200, 180, 100), False, path_points, PATH_WIDTH)
# 타워 출력
for tower in towers:
tower.draw(screen)
# 적 출력
for enemy in enemies:
enemy.draw(screen)
# UI
screen.blit(font.render(f"HP: {base_hp}", True, (255, 255, 255)), (10, 10))
screen.blit(font.render(f"Gold: {gold}", True, (255, 255, 0)), (10, 40))
screen.blit(font.render(f"Tower Cost: {TOWER_COST}", True, (200, 200, 200)), (10, 70))
if game_over:
over_text = font.render("GAME OVER", True, (255, 255, 255))
screen.blit(
over_text,
(SCREEN_WIDTH // 2 - over_text.get_width() // 2,
SCREEN_HEIGHT // 2)
)
pygame.display.update()
clock.tick(FPS)
728x90
'⚙️ Python > 🎮 Pygame 실전' 카테고리의 다른 글
| [Pygame] 🏰 2D 타워 디펜스 게임 만들기 10강 | 타워 공격 구현하기 (0) | 2026.02.10 |
|---|---|
| [Pygame] 🏰 2D 타워 디펜스 게임 만들기 9강 | 타워 사거리 & 타겟 선택 구현하기 (0) | 2026.02.09 |
| [Pygame] 🏰 2D 타워 디펜스 게임 만들기 7강 | 마우스 입력 처리 & 클릭 좌표 다루기 (0) | 2026.02.07 |
| [Pygame] 🏰 2D 타워 디펜스 게임 만들기 6강 | 적 HP & 골드 시스템 추가 (0) | 2026.02.06 |
| [Pygame] 🏰 2D 타워 디펜스 게임 만들기 5강 | HP 구성 & UI 표시 (0) | 2026.02.05 |