using System.Collections; using UnityEngine; namespace Streamingle.Contents.BossRaid { /// /// 화면 전체 이펙트: 흔들림, 플래시, 히트스톱. /// 메인 카메라 기반으로 동작합니다. /// public class ScreenEffect : MonoBehaviour { #region Fields [Header("플래시 오버레이")] [SerializeField] [Tooltip("플래시용 CanvasGroup (ScreenSpace Overlay 하위). 없으면 자동 생성")] private CanvasGroup flashOverlay; private Coroutine _shakeCoroutine; private Coroutine _flashCoroutine; private Coroutine _hitStopCoroutine; private Camera _mainCamera; private Vector3 _cameraOriginalPos; private Transform _cameraTransform; #endregion #region Unity Messages private void Start() { _mainCamera = Camera.main; if (_mainCamera != null) { _cameraTransform = _mainCamera.transform; _cameraOriginalPos = _cameraTransform.localPosition; } EnsureFlashOverlay(); } #endregion #region Public Methods /// /// 화면 흔들림 /// public void PlayShake(float intensity, float duration) { if (_cameraTransform == null) return; if (_shakeCoroutine != null) StopCoroutine(_shakeCoroutine); _shakeCoroutine = StartCoroutine(ShakeCoroutine(intensity, duration)); } /// /// 화면 플래시 (흰색/색상 오버레이) /// public void PlayFlash(Color color, float duration) { if (_flashCoroutine != null) StopCoroutine(_flashCoroutine); _flashCoroutine = StartCoroutine(FlashCoroutine(color, duration)); } /// /// 히트스톱 (TimeScale 일시 정지) /// public void PlayHitStop(float duration) { if (duration <= 0f) return; if (_hitStopCoroutine != null) StopCoroutine(_hitStopCoroutine); _hitStopCoroutine = StartCoroutine(HitStopCoroutine(duration)); } #endregion #region Private Methods private void EnsureFlashOverlay() { if (flashOverlay != null) return; var canvasObj = new GameObject("BossRaid_FlashOverlay"); var canvas = canvasObj.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 999; var img = canvasObj.AddComponent(); img.color = new Color(1f, 1f, 1f, 0f); img.raycastTarget = false; flashOverlay = canvasObj.AddComponent(); flashOverlay.alpha = 0f; flashOverlay.interactable = false; flashOverlay.blocksRaycasts = false; canvasObj.transform.SetParent(transform); } private IEnumerator ShakeCoroutine(float intensity, float duration) { _cameraOriginalPos = _cameraTransform.localPosition; float elapsed = 0f; while (elapsed < duration) { elapsed += Time.unscaledDeltaTime; float t = 1f - (elapsed / duration); float x = Random.Range(-intensity, intensity) * t; float y = Random.Range(-intensity, intensity) * t; _cameraTransform.localPosition = _cameraOriginalPos + new Vector3(x, y, 0f); yield return null; } _cameraTransform.localPosition = _cameraOriginalPos; _shakeCoroutine = null; } private IEnumerator FlashCoroutine(Color color, float duration) { if (flashOverlay == null) yield break; var img = flashOverlay.GetComponent(); if (img != null) img.color = color; // 페이드 인 (빠르게) float fadeIn = duration * 0.2f; float fadeOut = duration * 0.8f; float elapsed = 0f; while (elapsed < fadeIn) { elapsed += Time.unscaledDeltaTime; flashOverlay.alpha = Mathf.Lerp(0f, 0.8f, elapsed / fadeIn); yield return null; } // 페이드 아웃 elapsed = 0f; while (elapsed < fadeOut) { elapsed += Time.unscaledDeltaTime; flashOverlay.alpha = Mathf.Lerp(0.8f, 0f, elapsed / fadeOut); yield return null; } flashOverlay.alpha = 0f; _flashCoroutine = null; } private IEnumerator HitStopCoroutine(float duration) { float prevTimeScale = Time.timeScale; Time.timeScale = 0f; yield return new WaitForSecondsRealtime(duration); Time.timeScale = prevTimeScale; _hitStopCoroutine = null; } #endregion } }