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