feat: 게임 오버 및 승리 UI 구현
- GameOverUI: 게임 오버 화면 표시 - VictoryUI: 보스 처치 시 승리 화면 표시 - VictoryEffect: 슬로우 모션, 카메라 연출, 이펙트 재생 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
8
Assets/Scripts/Effects.meta
Normal file
8
Assets/Scripts/Effects.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9598f5b3fb42a1945ab57c2dc55b2815
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
187
Assets/Scripts/Effects/VictoryEffect.cs
Normal file
187
Assets/Scripts/Effects/VictoryEffect.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Colosseum.Enemy;
|
||||
|
||||
namespace Colosseum.Effects
|
||||
{
|
||||
/// <summary>
|
||||
/// 보스 승리 연출 이펙트.
|
||||
/// 보스 사망 시 카메라 연출, 이펙트, 슬로우 모션 등을 처리합니다.
|
||||
/// GameManager에 의해 활성화됩니다.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 승리 연출 재생
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Effects/VictoryEffect.cs.meta
Normal file
2
Assets/Scripts/Effects/VictoryEffect.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7cbb78d0cea7014ba69a25271583954
|
||||
59
Assets/Scripts/UI/GameOverUI.cs
Normal file
59
Assets/Scripts/UI/GameOverUI.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace Colosseum.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 게임 오버 UI 컨트롤러.
|
||||
/// GameManager에 의해 활성화/비활성화됩니다.
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 카운트다운 텍스트 업데이트
|
||||
/// </summary>
|
||||
public void UpdateCountdown(int seconds)
|
||||
{
|
||||
if (countdownText != null)
|
||||
{
|
||||
countdownText.text = $"Restarting in {seconds}...";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/GameOverUI.cs.meta
Normal file
2
Assets/Scripts/UI/GameOverUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f87daf25f7fc5c4499b66d327b6c4cf2
|
||||
55
Assets/Scripts/UI/VictoryUI.cs
Normal file
55
Assets/Scripts/UI/VictoryUI.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using Colosseum.Enemy;
|
||||
|
||||
namespace Colosseum.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 승리 UI 컨트롤러.
|
||||
/// GameManager에 의해 활성화/비활성화됩니다.
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/VictoryUI.cs.meta
Normal file
2
Assets/Scripts/UI/VictoryUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 514ff17abf102744faf81dbad1251d86
|
||||
Reference in New Issue
Block a user