AI/잡다한개발노트

타워디펜스 - 맵 에디터 × Battle Simulator 통합 강화 플랜

blacknabis 2026. 3. 2. 22:05
반응형
시뮬레이터 결과화면


작성일: 2026-03-02
최종 보완: 2026-03-02 (라운드 3)
상태: 플랜 (코드 작업 전)
아키텍처: A안 — 흡수 통합 (BattleSimWindow를 MapEditorWindow에 통합)

1. 현재 상태

도구 상태 한계
MapEditorWindow ✅ 완료 경로/슬롯 편집만. 3개 모드(기본/경로/슬롯)
WaveBalanceWindow ✅ 완료 수치 편집만. 결과 예측 불가
BattleSimWindow ✅ 기본 독립 창. 타워 위치/경로 미반영, 전체 슬롯 동일 배치만 가능

문제: 세 도구가 분리되어 있어 "경로 수정 → 타워 배치 → 시뮬 → 결과 확인"이 창 3개를 오가며 진행됨.

2. 목표 아키텍처 (A안: 흡수 통합)

BattleSimWindow를 제거하고 MapEditorWindow에 모든 시뮬 기능을 통합한다.

MapEditorWindow (통합 모드)
├── [📍 기본 정보]     ← 기존: StageId, MapName, Bounds 등
├── [🛤 경로 편집]     ← 기존: SceneView 핸들
├── [🏗 슬롯 편집]     ← 기존: SceneView 핸들
├── [🏰 타워 배치]     ← 신규: 슬롯별 TowerConfig+Level 지정
└── [⚔ 시뮬레이션]    ← 신규: 실행 버튼 + 결과 표시 + SceneView 오버레이

UI 레이아웃 (통합 후)

┌─ Map Editor ───────────────────────────────────────┐
│  MapConfig: [Stage_1_MapConfig ▼]                   │
│  WaveConfig: [Stage_1_WaveConfig ▼]  ← 신규 슬롯   │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │  [기본] [경로] [슬롯] [🏰배치] [⚔시뮬]     │   │  ← 모드 탭 (5개)
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  ─── 모드별 Inspector 패널 ───                      │
│  (경로 모드면 경로 리스트,                           │
│   배치 모드면 슬롯별 타워 드롭다운,                   │
│   시뮬 모드면 실행 버튼 + 결과 테이블)                │
│                                                     │
│  ─── SceneView (공유) ───                           │
│  경로+슬롯+타워+시뮬결과 오버레이가                    │
│  현재 모드에 따라 다르게 표시                          │
└─────────────────────────────────────────────────────┘

데이터 흐름

MapConfigData (SO)          WaveConfig (SO)
     │                          │
     ├── Paths[]                ├── Waves[]
     ├── TowerSlotPositions[]   ├── InitialGold/Lives
     └── GameplayBounds         └── StarThresholds
                 │                     │
                 └───────┬─────────────┘
                         ▼
            TowerPlacementPreset (SO 또는 세션 데이터)
               └── SlotPlacements[slotIdx → (TowerConfig, Level)]
                         │
                         ▼
               BattleSimEngine.Run(SimConfig)
                         │
                         ▼
               BattleSimResult
               ├── 승패/별등급/라이프/골드
               ├── WaveResult[]
               ├── TowerContribution[]
               └── EnemyDeathPositions[]
                         │
                         ▼
               ┌─ Inspector 패널 결과 표시
               └─ SceneView 오버레이 (SimOverlayRenderer)

3. 모드별 기능 상세

3.1 기존 모드 (변경 최소화)

모드 변경 사항
기본 정보 WaveConfig SO 슬롯 추가 (StageId 기반 자동 탐색)
경로 편집 변경 없음
슬롯 편집 변경 없음

3.2 타워 배치 모드 (신규)

Inspector 패널

🏰 타워 배치
─────────────────────────────
빠른 전체 배치:
  타워: [Archer ▼]  레벨: [Lv.1 ▼]  [전체 적용]

슬롯별 개별 배치:
  Slot 0: [Archer ▼]  [Lv.2 ▼]    [✕]
  Slot 1: [Mage   ▼]  [Lv.1 ▼]    [✕]
  Slot 2: [없음       ]             [✕]
  Slot 3: [Barracks▼]  [Lv.1 ▼]    [✕]
  ...

[프리셋 저장]  [프리셋 불러오기]  [전체 초기화]

타워 배치 데이터 영속성

  • 세션 내: 배치 데이터는 MapEditorWindow의 인스턴스 변수로 유지 (창 닫으면 초기화)
  • 저장: "프리셋 저장" 시 TowerPlacementPreset SO로 디스크 저장
  • 불러오기: SO에서 슬롯별 배치를 복원
  • ⚠ 배치 데이터는 MapConfigData에 포함하지 않음 (맵 데이터 ≠ 밸런스 테스트 배치)

SceneView 표시

요소 시각
배치된 타워 타워 타입별 색상 원 (Archer=초록, Mage=파랑, Barracks=주황, Artillery=빨강) + "Lv.N" 라벨
사거리 원 해당 레벨의 실제 Range 값으로 반투명 원
빈 슬롯 회색 점선 원 + "Empty"
사각지대 경고 어느 타워 사거리에도 안 들어가는 경로 구간을 빨간색 굵은 선으로 표시

SceneView 조작

동작 방법
타워 배치 빈 슬롯 클릭 → 현재 선택 타워로 배치
타워 교체 배치된 슬롯 클릭 → 현재 선택 타워로 교체
타워 제거 배치된 슬롯 우클릭 → "타워 제거"
레벨 변경 배치된 슬롯 우클릭 → "Lv.1/2/3"

모든 SceneView 배치 조작에 Undo/Redo 지원 (Undo.RecordObject)

3.3 시뮬레이션 모드 (신규)

Inspector 패널

⚔ 전투 시뮬레이션
─────────────────────────────
[▶ 시뮬레이션 실행]       [📋 이전 결과와 비교]

━━━ 결과 요약 ━━━
✅ 승리 ★★★
남은 라이프: 18/20
총 골드: 3200  |  시뮬 시간: 45.3s

━━━ 웨이브별 상세 ━━━
 W1 | 일반 | 적12 | 처치12/돌파0 | 골드180 | 5.0~12.3s
 W2 | 일반 | 적15 | 처치13/돌파2 | 골드210 | 14.3~25.1s
 W3 | 보스 | 적 3 | 처치 3/돌파0 | 골드450 | 27.1~45.3s

━━━ 타워별 기여도 ━━━
 Slot0 Archer Lv2: 데미지 2400 (28%)  처치 8
 Slot1 Mage   Lv1: 데미지 1800 (21%)  처치 6
 Slot3 Barracks   : 블록 14회, 데미지 800 (9%)  처치 3
 ...

━━━ 골드 추이 차트 ━━━
 ▓▓▓▓▓▓▓▓
 ▓▓▓▓▓▓▓▓▓▓▓
 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓

[📝 로그 펼치기]

결과 비교 기능

  • "이전 결과와 비교" 버튼: 마지막 2회 시뮬 결과를 나란히 비교
  • 비교 항목: 라이프 차이, 골드 차이, 웨이브별 돌파 수 증감
  • 용도: 밸런스 조정 → 시뮬 → 수정 전후 비교 확인

SceneView 표시 (시뮬 후 오버레이)

오버레이 설명
🔴 사망 마커 적이 사망한 경로상 위치에 빨간 X 마커
🟡 돌파 마커 적이 도착점까지 뚫은 경우 경로 끝에 노란 마커 + 수량
🟢 타워 기여도 타워 원 크기가 기여도에 비례하여 변화
🔥 경로 열지도 경로 구간별 "적이 가장 오래 머문 구간" = 두꺼운 주황, 빠르게 지나간 구간 = 얇은 초록
⚠ 사각지대 타워 사거리에 안 걸리는 경로 구간 = 빨간 점선

에러 처리 / 검증

상황 동작
MapConfig 미연결 "MapConfigData를 연결하세요" 경고 + 실행 비활성화
WaveConfig 미연결 "WaveConfig를 연결하세요" 경고 + 실행 비활성화
타워 0개 배치 "타워 없이 시뮬합니다 (모든 적 돌파 예상)" 안내 + 실행 허용
경로 0개 "유효한 경로가 없습니다" 경고 + 실행 비활성화
경로 Waypoint < 2 "PathId=N 의 Waypoint가 부족합니다" 경고
스폰 엔트리의 PathId가 MapConfig에 없음 "PathId=N 이 맵에 존재하지 않습니다" 경고

4. 정밀 시뮬 엔진 개선

4.1 2D 위치 보간

// 경로 Waypoint 리스트 + 이동 거리 → 2D 좌표 계산
Vector3 GetPositionOnPath(List<Vector3> waypoints, float distanceTraveled)

4.2 거리 기반 사거리 판정

// 기존: 경로 진행도 기반 (부정확)
// 개선: 타워 좌표 ↔ 적 좌표 실제 거리
float dist = Vector3.Distance(tower.Position, enemy.CurrentPosition);
if (dist <= tower.Range) { /* 공격 */ }

4.3 타워 타겟팅 규칙

규칙 설명
기본 우선순위 사거리 내에서 경로 진행도가 가장 높은(=도착에 가장 가까운) 적 타겟
다중 경로 경로 무관하게 사거리 내 모든 적을 후보로 포함
타겟 제한 TowerConfig.CanTarget(isFlying) — Flying 적은 Ground 전용 타워에게 무시됨
Artillery AoE 주 타겟 주변 반경(ExplosionRadius) 내 적에게 감소된 데미지

4.4 Barracks 블록 규칙

float dist = Vector3.Distance(rallyPoint, enemy.CurrentPosition);
if (dist <= rallyRange && enemy.CanBeBlocked) { /* 블록 */ }
규칙 설명
블록 가능 CanBeBlocked == true (Flying, Boss 제외)
Rally 반경 BarracksData.RallyRange > 0 ? RallyRange : tower.Range
병사 리스폰 BarracksData.SoldierRespawnSec (기본 5초)
1 병사 = 1 적 한 병사가 한 적만 블록, 적이 죽으면 해제 후 새 적 탐색

4.5 사각지대 정적 분석 (시뮬 전 즉시)

경로의 모든 구간을 0.5 단위로 샘플링
→ 각 샘플 지점에서 모든 타워까지 거리 계산
→ min(거리) > max(Range) → 사각지대
→ SceneView에 빨간 경로 표시

4.6 적 사망 위치 / 타워별 기여도 추적

// BattleSimResult에 추가
public List<Vector3> EnemyDeathPositions;     // 사망 위치
public List<TowerContribution> TowerStats;    // 타워별 누적 데미지·처치수

public struct TowerContribution {
    public int SlotIndex;
    public string TowerName;    // 식별 편의
    public float TotalDamage;
    public int KillCount;
    public int BlockCount;      // Barracks
}

5. 프리셋 데이터 구조

[CreateAssetMenu(fileName = "TowerPreset", menuName = "Kingdom/Editor/Tower Placement Preset")]
public class TowerPlacementPreset : ScriptableObject
{
    public string PresetName;
    public int StageId;           // 어느 맵 기준인지
    public List<SlotPlacement> Placements;
}

[Serializable]
public struct SlotPlacement
{
    public int SlotIndex;
    public TowerConfig Tower;     // null이면 빈 슬롯
    public int Level;             // 0-indexed
}

저장 경로: Assets/Resources/Kingdom/Configs/TowerPresets/

6. 수정/생성 파일

파일 변경
MapEditorWindow.cs WaveConfig 슬롯 추가, "타워 배치"/"시뮬" 모드 탭 추가, 결과 패널 통합
BattleSimEngine.cs GetPositionOnPath(), 거리 기반 판정, 기여도 추적, 사망 위치 기록
BattleSimResult.cs TowerContribution, EnemyDeathPositions, 사각지대 데이터 추가
BattleSimWindow.cs 삭제 (Phase A step 5에서 제거. 삭제 전 MapEditorWindow 시뮬 탭 완성 선행)
TowerPlacementEditor.cs 신규 — 슬롯별 타워 배치 SceneView 핸들
SimOverlayRenderer.cs 신규 — SceneView 시뮬 결과 오버레이 렌더링
CoverageAnalyzer.cs 신규 — 사각지대 정적 분석
TowerPlacementPreset.cs 신규 — 타워 배치 프리셋 SO
WaveBalanceWindow.cs "시뮬 실행" 버튼 + 인라인 결과 추가

7. 구현 단계

Phase A: 정밀 시뮬 + 타워 개별 배치

  1. BattleSimEngine — 2D 위치 보간 + 거리 기반 판정 + 기여도 추적
  2. BattleSimResult — 기여도/사망 위치/비교 데이터 필드 추가
  3. MapEditorWindow — "타워 배치" 모드 탭 추가 (Inspector만)
  4. MapEditorWindow — "시뮬" 모드 탭 추가 (결과 테이블 + 차트 + 비교)
  5. BattleSimWindow.cs 삭제 (step 4 완성 후)

완료 기준:

  • MapEditorWindow 단독으로 타워 배치 → 시뮬 실행 → 결과 확인 가능
  • 경로 옆 배치 vs 먼 곳 배치 시 결과 차이 발생 확인
  • BattleSimWindow 메뉴 항목 제거 완료

Phase B: SceneView 통합

  1. TowerPlacementEditor — 슬롯별 타워 SceneView 핸들 (클릭 배치/우클릭 메뉴)
  2. CoverageAnalyzer — 사각지대 분석 + 경로 경고 SceneView 표시
  3. SimOverlayRenderer — 시뮬 결과 SceneView 오버레이 (사망 마커, 기여도 원, 열지도)

완료 기준:

  • SceneView에서 타워 배치 조작 (클릭/우클릭) 정상 동작 + Undo/Redo
  • 사각지대 빨간 경로 표시 정상 동작
  • 시뮬 후 사망/돌파 마커가 SceneView에 표시

Phase C: 연동 + 프리셋

  1. WaveBalanceWindow → 시뮬 연동 버튼 (현재 WaveConfig + 마지막 프리셋으로 시뮬)
  2. TowerPlacementPreset — 프리셋 SO 저장/로드

완료 기준:

  • WaveBalanceWindow에서 수치 수정 → 시뮬 버튼 → 결과 인라인 표시
  • 프리셋 저장/불러오기 정상 동작

Phase D: 영웅 + 고급 시각화

  1. 영웅 배치/시뮬 (HeroConfig 연동) — 고정 위치 타워로 추상화
  2. 경로 히트맵 (구간별 적 체류 시간 색상 표시)
  3. 타워 DPS 기여도 파이 차트

완료 기준:

  • 영웅 포함 시뮬 실행 가능
  • 히트맵 오버레이 SceneView 표시

8. 리스크 및 대응

리스크 대응
MapEditorWindow가 너무 커짐 모드별 로직을 별도 클래스(TowerPlacementEditor, SimOverlayRenderer)로 분리하여 MapEditorWindow는 모드 라우터 역할만
시뮬 실행 시간이 오래 걸리면 에디터 프리즈 최대 시뮬 시간 600초 제한, 진행바(EditorUtility.DisplayProgressBar) 표시. 50ms 이상 걸리면 경고 로그
SceneView 오버레이 성능 저하 오버레이 항목별 토글 제공, 대량 마커는 밀도 기반 클러스터링, Repaint 최소화
맵/웨이브 데이터 수정 후 시뮬 결과 stale 시뮬 결과 상단에 "⚠ 데이터가 수정되었습니다. 다시 실행하세요" 배너 표시. _target의 변경 감지(Hash 또는 dirty flag)
타워 배치가 맵 데이터와 혼동 배치는 MapConfigData에 저장하지 않음. 별도 SO(TowerPlacementPreset)으로 명확히 분리. Inspector에 안내 문구
다중 경로 시뮬 정확도 경로 간 적 분포가 실 플레이와 다를 수 있음. 시뮬 결과에 "추정치" 라벨 표시

9. 결정 필요 항목 (착수 전 확정)

  • 타워 배치 프리셋을 SO로 저장할지, EditorPrefs(JSON)로 경량화할지
  • 사각지대 분석을 "타워 배치" 모드에서 상시 표시할지, "시뮬" 모드 전용으로 할지
  • 영웅 시뮬(Phase D)을 1차 범위에 포함할지, 완전 2차로 넘길지
  • 시뮬 결과 비교를 최대 몇 회분까지 보관할지 (권장: 2회)

10. 기존 코드 이슈 (Phase A 선행 수정)

Codex 정적 리뷰(라운드 1)에서 발견된 기존 BattleSimEngine.cs / BattleSimWindow.cs 버그.
Phase A 정밀 시뮬 개선 시 함께 수정해야 한다.

# 심각도 파일 이슈 수정 방향
1 🔴 High BattleSimEngine 타워 레벨 데이터 null/empty 시 NRE/IndexOutOfRange 발생 (Levels[lvl] 무조건 접근) tc.Levels == null || lvl >= tc.Levels.Length 가드 추가
2 🔴 High BattleSimEngine Barracks 타워의 BarracksData null 체크 없이 필드 접근 → NRE tower.Config.BarracksData != null 가드 추가
3 🟡 Medium BattleSimEngine MaxSimTimeSec 초과로 루프 종료 시, 생존 적을 처치로 집계 (EnemiesKilled = Spawned - Leaked) 명시적으로 생존 적 수를 별도 추적, EnemiesAlive 필드 추가
4 🟡 Medium BattleSimWindow RunSimulation()에서 _isRunning 복구 없음 → Engine 예외 시 UI가 "실행 중" 고정 try/finally 패턴으로 _isRunning = false 보장
5 🟡 Medium BattleSimEngine Barracks RallyRange 계산하지만 미사용, 경로 진행도(0.15~0.9) 판정과 불일치 Phase A에서 2D 좌표 기반 판정으로 전면 교체 시 해소
6 🔴 High BattleSimEngine HandleAoeDamage()에서 HP를 깎지만 KillEnemy를 호출하지 않음 → HP≤0 적이 살아서 이동/누수 가능 AoE 데미지 후 HP <= 0 사망 처리 추가
7 🔴 Critical BattleSimEngine MaxSimTimeSec 타임아웃 시 IsCleared = lives > 0으로 판정 → 적/웨이브 잔존해도 승리 처리 타임아웃 시 IsCleared = false 또는 별도 TimedOut 상태 추가
8 🟡 Medium BattleSimEngine WaveConfig.Waves, MapConfig.TowerSlotPositions null 체크 없이 접근 → NRE 가능 시뮬 시작 전 입력 유효성 검증 단계(ValidateInputs) 추가

11. 시뮬 한계 및 면책 (Limitations)

이 시뮬레이터는 수치 기반 근사치이며, 실 플레이와 100% 동일하지 않다.
결과는 밸런스 방향성 확인용이며, 최종 검증은 실 플레이로 수행한다.

괴리 영역 설명 영향도
경제 시뮬 미포함 실 플레이에서는 웨이브 중 골드로 타워 추가 구매/업그레이드가 가능하지만, 시뮬은 "사전 배치 고정". 중반 이후 타워 업그레이드 효과를 반영하지 못함 🟡 중
타워 타겟팅 AI 실 게임의 타겟팅 로직(최근접, 최강, 최약 등)과 시뮬의 "경로 진행도 최고" 규칙이 다를 수 있음 🟡 중
투사체 이동 시간 실 게임에서 화살/마법 투사체는 이동 시간이 있으나, 시뮬은 즉시 적중 🟢 낮
Barracks 교전 동선 실 게임에서 병사와 적의 이동/교전 위치가 세밀하지만, 시뮬은 Rally 반경 내 즉시 블록 🟡 중
곡선 경로 현재 직선 Waypoint Lerp 보간만 지원. 곡선(Catmull-Rom 등)은 미지원 🟢 낮 (현 프로젝트 경로는 직선 Waypoint 기반)
영웅/스킬/버프 Phase D 이전에는 미반영 🟡 중

경제 시뮬 고려 (향후 확장)

현재: 모든 타워가 게임 시작 전에 배치된 상태로 시뮬.
한계: 초반에 Lv.1 타워 → 중반 골드로 Lv.3 업그레이드 같은 실 플레이 패턴을 반영 못함.

보완 방안 (Phase E 이후):

시뮬 옵션: [x] 경제 시뮬 활성화

초기 골드: 500
자동 업그레이드 정책:
  - 골드가 업그레이드 비용 이상이면 → 가장 DPS 기여도 높은 타워를 우선 업그레이드
  - 또는: 수동 "웨이브 N 후 슬롯 M을 Lv.2로 업그레이드" 스크립트 입력

→ 현재 Phase A~D에서는 경제 시뮬 미포함으로 진행하되, 결과에 "경제 시뮬 미반영" 라벨을 표시한다.

곡선 경로 판단

  • 현재 프로젝트의 PathData.Waypoints직선 연결 구간
  • 킹덤 러시 원작도 Waypoint 기반 직선/꺾임 경로 사용 (실제 곡선 스플라인 아님)
  • 결론: 현재 직선 Lerp 보간으로 충분. Catmull-Rom 등은 불필요

12. 프리셋 호환성 규칙

맵 수정으로 타워 슬롯 수가 변경될 때 기존 프리셋이 깨지는 문제 대응:

상황 처리
슬롯 추가 (3→5) 프리셋의 기존 슬롯은 유지, 신규 슬롯은 "빈 슬롯"으로 자동 채움
슬롯 삭제 (5→3) 프리셋에서 삭제된 SlotIndex는 무시하고 경고 표시: "⚠ Slot 3,4는 맵에 존재하지 않아 무시됨"
슬롯 위치 이동 SlotIndex 기반이므로 위치만 바뀌면 자동 대응. 인덱스가 바뀌면 경고
StageId 불일치 "⚠ 이 프리셋은 Stage_2용입니다. 현재 맵은 Stage_1입니다." 경고 + 적용 허용

→ 프리셋 로드 시 SlotIndex 범위 검증 + StageId 일치 확인을 수행한다.

반응형