diff --git a/Assets/Scripts/Effects.meta b/Assets/Scripts/Effects.meta new file mode 100644 index 00000000..c5f59211 --- /dev/null +++ b/Assets/Scripts/Effects.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9598f5b3fb42a1945ab57c2dc55b2815 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Effects/VictoryEffect.cs b/Assets/Scripts/Effects/VictoryEffect.cs new file mode 100644 index 00000000..835efc94 --- /dev/null +++ b/Assets/Scripts/Effects/VictoryEffect.cs @@ -0,0 +1,187 @@ +using System.Collections; +using UnityEngine; +using Colosseum.Enemy; + +namespace Colosseum.Effects +{ + /// + /// 보스 승리 연출 이펙트. + /// 보스 사망 시 카메라 연출, 이펙트, 슬로우 모션 등을 처리합니다. + /// GameManager에 의해 활성화됩니다. + /// + public class VictoryEffect : MonoBehaviour + { + [Header("Victory Settings")] + [Tooltip("승리 시 슬로우 모션 배율")] + [SerializeField] private float slowMotionScale = 0.3f; + + [Tooltip("슬로우 모션 지속 시간")] + [SerializeField] private float slowMotionDuration = 2f; + + [Header("Effects")] + [Tooltip("승리 시 생성할 이펙트 프리팹")] + [SerializeField] private GameObject victoryEffectPrefab; + + [Tooltip("이펙트 생성 위치 오프셋")] + [SerializeField] private Vector3 effectOffset = Vector3.up * 2f; + + [Header("Audio")] + [Tooltip("승리 사운드")] + [SerializeField] private AudioClip victorySound; + + [Tooltip("사운드 볼륨")] + [SerializeField] private float soundVolume = 1f; + + [Header("Debug")] + [SerializeField] private bool debugMode = true; + + // 상태 + private bool isPlaying = false; + private float originalTimeScale; + private Camera mainCamera; + + private void Awake() + { + mainCamera = Camera.main; + } + + private void OnEnable() + { + PlayVictoryEffect(); + } + + private void OnDisable() + { + // 시간 스케일 복구 + if (isPlaying) + { + Time.timeScale = 1f; + isPlaying = false; + } + } + + /// + /// 승리 연출 재생 + /// + public void PlayVictoryEffect() + { + if (isPlaying) return; + + StartCoroutine(VictorySequence()); + } + + private IEnumerator VictorySequence() + { + isPlaying = true; + originalTimeScale = Time.timeScale; + + if (debugMode) + { + Debug.Log("[VictoryEffect] Starting victory sequence"); + } + + // 1. 슬로우 모션 + yield return StartCoroutine(PlaySlowMotion()); + + // 2. 카메라 연출 + yield return StartCoroutine(PlayCameraEffect()); + + // 3. 이펙트 생성 + SpawnVictoryEffect(); + + // 4. 사운드 재생 + PlayVictorySound(); + + // 5. 시간 복구 + Time.timeScale = originalTimeScale; + isPlaying = false; + + if (debugMode) + { + Debug.Log("[VictoryEffect] Victory sequence complete"); + } + } + + private IEnumerator PlaySlowMotion() + { + float elapsed = 0f; + + while (elapsed < slowMotionDuration) + { + elapsed += Time.unscaledDeltaTime; + float t = elapsed / slowMotionDuration; + + // 처음에는 슬로우, 나중에는 복구 + if (t < 0.5f) + { + Time.timeScale = Mathf.Lerp(originalTimeScale, slowMotionScale, t * 2f); + } + else + { + Time.timeScale = Mathf.Lerp(slowMotionScale, originalTimeScale, (t - 0.5f) * 2f); + } + + yield return null; + } + + Time.timeScale = slowMotionScale; + } + + private IEnumerator PlayCameraEffect() + { + if (mainCamera == null || BossEnemy.ActiveBoss == null) + yield break; + + // 보스 위치로 카메라 이동 + Transform bossTransform = BossEnemy.ActiveBoss.transform; + Vector3 targetPosition = bossTransform.position + effectOffset; + + float elapsed = 0f; + float duration = slowMotionDuration * 0.5f; + + while (elapsed < duration) + { + elapsed += Time.unscaledDeltaTime; + + // 카메라가 보스를 바라보도록 + mainCamera.transform.LookAt(targetPosition); + + yield return null; + } + } + + private void SpawnVictoryEffect() + { + if (victoryEffectPrefab == null) return; + + Vector3 spawnPosition = transform.position + effectOffset; + + if (BossEnemy.ActiveBoss != null) + { + spawnPosition = BossEnemy.ActiveBoss.transform.position + effectOffset; + } + + var effect = Instantiate(victoryEffectPrefab, spawnPosition, Quaternion.identity); + + // 일정 시간 후 제거 + Destroy(effect, 5f); + + if (debugMode) + { + Debug.Log("[VictoryEffect] Spawned victory effect"); + } + } + + private void PlayVictorySound() + { + if (victorySound == null) return; + + AudioSource.PlayClipAtPoint(victorySound, transform.position, soundVolume); + + if (debugMode) + { + Debug.Log("[VictoryEffect] Played victory sound"); + } + } + } +} diff --git a/Assets/Scripts/Effects/VictoryEffect.cs.meta b/Assets/Scripts/Effects/VictoryEffect.cs.meta new file mode 100644 index 00000000..a5297340 --- /dev/null +++ b/Assets/Scripts/Effects/VictoryEffect.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b7cbb78d0cea7014ba69a25271583954 \ No newline at end of file diff --git a/Assets/Scripts/UI/GameOverUI.cs b/Assets/Scripts/UI/GameOverUI.cs new file mode 100644 index 00000000..48f87212 --- /dev/null +++ b/Assets/Scripts/UI/GameOverUI.cs @@ -0,0 +1,59 @@ +using UnityEngine; +using UnityEngine.UI; +using TMPro; + +namespace Colosseum.UI +{ + /// + /// 게임 오버 UI 컨트롤러. + /// GameManager에 의해 활성화/비활성화됩니다. + /// + public class GameOverUI : MonoBehaviour + { + [Header("UI References")] + [Tooltip("게임 오버 텍스트")] + [SerializeField] private TMP_Text gameOverText; + + [Tooltip("재시작 카운트다운 텍스트")] + [SerializeField] private TMP_Text countdownText; + + [Tooltip("게임 오버 애니메이터")] + [SerializeField] private Animator animator; + + [Header("Settings")] + [Tooltip("게임 오버 텍스트")] + [SerializeField] private string gameOverMessage = "GAME OVER"; + + [Tooltip("텍스트 색상")] + [SerializeField] private Color textColor = Color.red; + + private void Start() + { + if (gameOverText != null) + { + gameOverText.text = gameOverMessage; + gameOverText.color = textColor; + } + } + + private void OnEnable() + { + // 애니메이션 재생 + if (animator != null) + { + animator.SetTrigger("Show"); + } + } + + /// + /// 카운트다운 텍스트 업데이트 + /// + public void UpdateCountdown(int seconds) + { + if (countdownText != null) + { + countdownText.text = $"Restarting in {seconds}..."; + } + } + } +} diff --git a/Assets/Scripts/UI/GameOverUI.cs.meta b/Assets/Scripts/UI/GameOverUI.cs.meta new file mode 100644 index 00000000..80c30e1d --- /dev/null +++ b/Assets/Scripts/UI/GameOverUI.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f87daf25f7fc5c4499b66d327b6c4cf2 \ No newline at end of file diff --git a/Assets/Scripts/UI/VictoryUI.cs b/Assets/Scripts/UI/VictoryUI.cs new file mode 100644 index 00000000..42b601c0 --- /dev/null +++ b/Assets/Scripts/UI/VictoryUI.cs @@ -0,0 +1,55 @@ +using UnityEngine; +using UnityEngine.UI; +using TMPro; +using Colosseum.Enemy; + +namespace Colosseum.UI +{ + /// + /// 승리 UI 컨트롤러. + /// GameManager에 의해 활성화/비활성화됩니다. + /// + public class VictoryUI : MonoBehaviour + { + [Header("UI References")] + [Tooltip("승리 텍스트")] + [SerializeField] private TMP_Text victoryText; + + [Tooltip("보스 이름 텍스트")] + [SerializeField] private TMP_Text bossNameText; + + [Tooltip("승리 애니메이터")] + [SerializeField] private Animator animator; + + [Header("Settings")] + [Tooltip("승리 텍스트")] + [SerializeField] private string victoryMessage = "VICTORY!"; + + [Tooltip("텍스트 색상")] + [SerializeField] private Color textColor = Color.yellow; + + private void Start() + { + if (victoryText != null) + { + victoryText.text = victoryMessage; + victoryText.color = textColor; + } + } + + private void OnEnable() + { + // 보스 이름 표시 + if (bossNameText != null && BossEnemy.ActiveBoss != null) + { + bossNameText.text = $"{BossEnemy.ActiveBoss.name} Defeated!"; + } + + // 애니메이션 재생 + if (animator != null) + { + animator.SetTrigger("Show"); + } + } + } +} diff --git a/Assets/Scripts/UI/VictoryUI.cs.meta b/Assets/Scripts/UI/VictoryUI.cs.meta new file mode 100644 index 00000000..5d7833ee --- /dev/null +++ b/Assets/Scripts/UI/VictoryUI.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 514ff17abf102744faf81dbad1251d86 \ No newline at end of file