using UnityEngine; using UnityEngine.Playables; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class LocalManager : MonoBehaviour { [SerializeField] private string _nextSceneName; [SerializeField] private AudioClip _caffebeneBGM; // 흑백 전환에 걸리는 시간(초) [SerializeField, Min(0f)] private float _grayscaleFadeTime = 1.5f; // BGM 재생 시작 후 씬 전환까지 대기 시간(초) [SerializeField, Min(0f)] private float _caffebeneSceneChangeDelay = 13f; private VolumeProfile _grayscaleProfile; public void NextScene(int delay) { _ = Util.RunDelayed((float)delay,()=>SceneLoadManager.Instance.RequestSceneChange(_nextSceneName)); } public void CaffebeneNextSceneChange() { _ = CaffebeneSequence(); } // 모든 애니메이션 정지 → 시야 흑백 페이드 → BGM 재생 → 대기 후 씬 전환 private async Awaitable CaffebeneSequence() { try { PauseAllAnimations(); await FadeToGrayscale(_grayscaleFadeTime); SoundManager.Instance.PlayOverrideBGM(_caffebeneBGM); await Awaitable.WaitForSecondsAsync(_caffebeneSceneChangeDelay, destroyCancellationToken); SceneLoadManager.Instance.RequestSceneChange(_nextSceneName); } catch (System.OperationCanceledException) { // 씬 전환 등으로 자신이 파괴되어 취소됨 } } // 씬 안의 Animator / 타임라인 / 파티클을 전부 일시정지 // (Time.timeScale은 건드리지 않는다 — BGM 페이드와 씬 전환 딜레이가 scaled time 기준이라 함께 멈춰버림) private static void PauseAllAnimations() { foreach (var animator in FindObjectsByType()) animator.speed = 0f; foreach (var director in FindObjectsByType()) director.Pause(); foreach (var particle in FindObjectsByType()) particle.Pause(); } // 런타임 전용 글로벌 볼륨을 만들어 채도를 0 → -100으로 페이드 (완전 흑백) private async Awaitable FadeToGrayscale(float duration) { var volumeObj = new GameObject("Grayscale Volume (Runtime)"); var volume = volumeObj.AddComponent(); volume.isGlobal = true; volume.priority = 100f; _grayscaleProfile = ScriptableObject.CreateInstance(); var colorAdjustments = _grayscaleProfile.Add(); colorAdjustments.saturation.Override(0f); volume.profile = _grayscaleProfile; // 카메라에 포스트 프로세싱이 꺼져 있으면 켠다 var mainCam = Camera.main; if (mainCam != null) mainCam.GetUniversalAdditionalCameraData().renderPostProcessing = true; float timer = 0f; while (timer < duration) { timer += Time.deltaTime; colorAdjustments.saturation.value = Mathf.Lerp(0f, -100f, Mathf.Clamp01(timer / duration)); await Awaitable.NextFrameAsync(destroyCancellationToken); } colorAdjustments.saturation.value = -100f; } private void OnDestroy() { // 런타임 생성한 프로필은 씬 언로드로 자동 파괴되지 않으므로 직접 정리 if (_grayscaleProfile != null) Destroy(_grayscaleProfile); } }