주석추가

This commit is contained in:
2026-05-19 10:51:56 +09:00
parent e01feec160
commit adf6750bc8
12 changed files with 577 additions and 214 deletions

View File

@@ -3,41 +3,63 @@
using System.Threading;
using UnityEngine;
// ============================================================================
// WaveManager
// ----------------------------------------------------------------------------
// 시간 제한 웨이브 시스템. 각 웨이브를 순서대로 진행하며, 시간 내 클리어 못 하면 패배.
//
// 흐름:
// StartWaves() → for each wave:
// - StartDelay 대기
// - OnWaveStart 발화
// - 적 스폰 시작 (백그라운드 비동기)
// - 타이머 시작
// - 매 프레임: 적 다 잡았는지 / 타이머 만료됐는지 체크
// - 클리어 → OnWaveCleared → IntermissionDuration 대기 → 다음 웨이브
// - 타임아웃 → OnDefeat → 살아있는 적 정리 → 종료
// 모든 웨이브 클리어 → OnAllWavesCleared
//
// UI 연동: 외부 시스템이 4개 이벤트 구독하여 UI/사운드 트리거.
// ============================================================================
public class WaveManager : MonoBehaviour
{
// ─── 웨이브 데이터 ───────────────────────────────────────────────────
[Header("Waves")]
[SerializeField] private List<WaveData> _waves = new();
[SerializeField] private float _intermissionDuration = 2f;
[SerializeField] private bool _startOnAwake = true;
[SerializeField] private List<WaveData> _waves = new(); // 순서대로 진행할 웨이브들
[SerializeField] private float _intermissionDuration = 2f; // 웨이브 사이 대기 시간 ("Wave Clear!" 표시 시간)
[SerializeField] private bool _startOnAwake = true; // Start에서 자동 시작
// ─── 스폰 위치 설정 (모든 웨이브 공통) ───────────────────────────────
[Header("Spawn Position")]
[SerializeField] private Transform[] _spawnPoints;
[SerializeField] private float _spawnRadius = 0f;
[SerializeField] private Transform _enemyParent;
[SerializeField] private Transform[] _spawnPoints; // 비워두면 자기 위치
[SerializeField] private float _spawnRadius = 0f; // 각 spawn point 주변 무작위 분산
[SerializeField] private Transform _enemyParent; // 스폰된 적을 묶을 부모 (Hierarchy 정리)
[Header("On Defeat")]
[SerializeField] private bool _destroyAliveEnemiesOnDefeat = true;
[SerializeField] private bool _destroyAliveEnemiesOnDefeat = true; // 패배 시 남은 적 정리
public event Action<int, WaveData> OnWaveStart;
public event Action<int> OnWaveCleared;
public event Action OnAllWavesCleared;
public event Action<int> OnDefeat;
// ─── 외부에서 구독할 이벤트들 (UI/사운드 연동용) ─────────────────────
public event Action<int, WaveData> OnWaveStart; // 웨이브 시작: (index, data)
public event Action<int> OnWaveCleared; // 웨이브 클리어: (index)
public event Action OnAllWavesCleared; // 모든 웨이브 끝 (승리)
public event Action<int> OnDefeat; // 패배: 어느 웨이브에서 실패했는지
// ─── 외부 읽기 전용 상태 (UI 연동용) ────────────────────────────────
public int CurrentWaveIndex { get; private set; }
public int TotalWaveCount => _waves != null ? _waves.Count : 0;
public WaveData CurrentWave =>
_waves != null && CurrentWaveIndex >= 0 && CurrentWaveIndex < _waves.Count
? _waves[CurrentWaveIndex]
: null;
public float TimeRemaining { get; private set; }
public int AliveCount => _aliveEnemies.Count;
public int RemainingToSpawn { get; private set; }
public bool IsRunning { get; private set; }
public bool IsDefeated { get; private set; }
public bool IsVictory { get; private set; }
public float TimeRemaining { get; private set; } // 현재 웨이브 남은 시간
public int AliveCount => _aliveEnemies.Count; // 살아있는 적 수
public int RemainingToSpawn { get; private set; } // 아직 스폰 안 한 적 수
public bool IsRunning { get; private set; } // 웨이브 진행 중
public bool IsDefeated { get; private set; } // 패배 상태
public bool IsVictory { get; private set; } // 모든 웨이브 클리어
private readonly List<Enemy> _aliveEnemies = new();
private CancellationTokenSource _waveCts;
private CancellationTokenSource _waveCts; // 전체 웨이브 진행 취소 토큰 (OnDestroy/StopWaves에서 취소)
private void Start()
{
@@ -50,18 +72,22 @@ private void OnDestroy()
_waveCts?.Dispose();
}
// 외부에서 호출해 웨이브 시작 (예: UI 버튼, 트리거).
public void StartWaves()
{
if (_waves == null || _waves.Count == 0) return;
RunAllWaves();
}
// 진행 중인 웨이브 강제 중단. 게임 오버/메뉴 진입 등에 사용.
public void StopWaves()
{
_waveCts?.Cancel();
IsRunning = false;
}
// 모든 웨이브를 순차 실행하는 메인 비동기 루프.
// try/catch로 토큰 취소 시 예외 흡수 → OnDestroy 시 깔끔하게 종료.
private async void RunAllWaves()
{
_waveCts?.Cancel();
@@ -106,12 +132,20 @@ private async void RunAllWaves()
catch (OperationCanceledException) { }
}
// 한 웨이브를 처리.
// 반환값: true = 클리어, false = 타임아웃(패배)
// 본 루프는 매 프레임:
// 1) 죽은 적 자동 정리
// 2) 모두 스폰 완료 + 살아있는 적 0 → 클리어
// 3) 타이머 0 도달 → 패배
// 4) 타이머 감소
private async Awaitable<bool> RunWave(WaveData wave, CancellationToken token)
{
IsRunning = true;
TimeRemaining = wave.TimeLimit;
_aliveEnemies.Clear();
// 이번 웨이브에서 스폰할 총 적 수 계산.
int totalToSpawn = 0;
foreach (var entry in wave.Spawns)
if (entry != null) totalToSpawn += Mathf.Max(entry.Count, 0);
@@ -125,6 +159,8 @@ private async Awaitable<bool> RunWave(WaveData wave, CancellationToken token)
token.ThrowIfCancellationRequested();
_aliveEnemies.RemoveAll(e => e == null);
// 클리어 조건: 모든 적이 스폰됐고 + 살아있는 적이 0.
// (스폰 중에 이미 죽었다고 클리어 인정하면 안 되니까 RemainingToSpawn=0도 같이 체크)
if (RemainingToSpawn == 0 && _aliveEnemies.Count == 0)
{
IsRunning = false;
@@ -135,13 +171,15 @@ private async Awaitable<bool> RunWave(WaveData wave, CancellationToken token)
if (TimeRemaining <= 0f)
{
IsRunning = false;
return false;
return false; // 패배
}
await Awaitable.NextFrameAsync(token);
}
}
// 적을 SpawnInterval 간격으로 스폰. 비동기로 흘려보내고 본 루프와 병렬 실행.
// (스폰 중에도 본 루프의 타이머 카운트다운 + 클리어 체크가 계속 돌아감)
private async void SpawnWaveEnemiesAsync(WaveData wave, CancellationToken token)
{
try
@@ -208,6 +246,7 @@ private void DestroyAliveEnemies()
_aliveEnemies.Clear();
}
// 디버그용 OnGUI 표시. 실제 게임 UI는 별도로 구성하고 이건 빠르게 확인용.
private void OnGUI()
{
GUIStyle style = new GUIStyle(GUI.skin.box)