using System; using System.Collections; using DG.Tweening; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using Object = UnityEngine.Object; namespace Bansonic { public static class gTransition { private const string PrefabResourcePath = "gTransitionPrefab"; public static bool IsBusy { get { BansonicSceneTransitionOverlay instance = BansonicSceneTransitionOverlay.EnsureInstance(PrefabResourcePath); return instance != null && instance.IsBusy; } } public static void FadeOut(float duration = 0.25f) { BansonicSceneTransitionOverlay instance = BansonicSceneTransitionOverlay.EnsureInstance(PrefabResourcePath); if (instance == null) { return; } instance.FadeOut(duration); } public static void FadeIn(float duration = 0.25f) { BansonicSceneTransitionOverlay instance = BansonicSceneTransitionOverlay.EnsureInstance(PrefabResourcePath); if (instance == null) { return; } instance.FadeIn(duration); } public static bool LoadScene( string sceneName, LoadSceneMode mode = LoadSceneMode.Single, float fadeOutDuration = 0.25f, float fadeInDuration = 0.25f, float holdBlackDuration = 0f, Action onComplete = null) { if (string.IsNullOrWhiteSpace(sceneName)) { return false; } BansonicSceneTransitionOverlay instance = BansonicSceneTransitionOverlay.EnsureInstance(PrefabResourcePath); if (instance == null) { return false; } return instance.BeginLoadScene(sceneName, mode, fadeOutDuration, fadeInDuration, holdBlackDuration, onComplete); } public static bool ReloadCurrentScene( float fadeOutDuration = 0.25f, float fadeInDuration = 0.25f, float holdBlackDuration = 0f, Action onComplete = null) { Scene activeScene = SceneManager.GetActiveScene(); if (!activeScene.IsValid() || string.IsNullOrWhiteSpace(activeScene.name)) { return false; } return LoadScene(activeScene.name, LoadSceneMode.Single, fadeOutDuration, fadeInDuration, holdBlackDuration, onComplete); } public static bool Run( IEnumerator routine, float fadeOutDuration = 0.25f, float fadeInDuration = 0.25f, float holdBlackDuration = 0f, Action onComplete = null) { if (routine == null) { return false; } BansonicSceneTransitionOverlay instance = BansonicSceneTransitionOverlay.EnsureInstance(PrefabResourcePath); if (instance == null) { return false; } return instance.BeginWrappedRoutine(routine, fadeOutDuration, fadeInDuration, holdBlackDuration, onComplete); } } public sealed class BansonicSceneTransitionOverlay : MonoBehaviour { private const string RuntimeObjectName = "BansonicSceneTransitionOverlay_Runtime"; [SerializeField] private gTransBlack transitionBlack; [SerializeField] private CanvasGroup canvasGroup; [SerializeField] private Image blackoutImage; private Tween currentTween; private Coroutine transitionRoutine; public bool IsBusy { get { return transitionRoutine != null; } } public static BansonicSceneTransitionOverlay EnsureInstance(string prefabResourcePath) { BansonicSceneTransitionOverlay existing = FindExisting(); if (existing != null) { existing.EnsureSetup(); return existing; } GameObject prefab = Resources.Load(prefabResourcePath); if (prefab != null) { GameObject prefabInstance = Object.Instantiate(prefab); prefabInstance.name = prefab.name; Object.DontDestroyOnLoad(prefabInstance); BansonicSceneTransitionOverlay overlayFromPrefab = prefabInstance.GetComponent(); if (overlayFromPrefab == null) { overlayFromPrefab = prefabInstance.AddComponent(); } if (overlayFromPrefab != null) { overlayFromPrefab.EnsureSetup(); } return overlayFromPrefab; } return CreateRuntimeInstance(); } public void FadeOut(float duration) { EnsureSetup(); StartSingleTransition(true, duration); } public void FadeIn(float duration) { EnsureSetup(); StartSingleTransition(false, duration); } public bool BeginLoadScene( string sceneName, LoadSceneMode mode, float fadeOutDuration, float fadeInDuration, float holdBlackDuration, Action onComplete) { if (string.IsNullOrWhiteSpace(sceneName) || IsBusy) { return false; } transitionRoutine = StartCoroutine(LoadSceneRoutine(sceneName, mode, fadeOutDuration, fadeInDuration, holdBlackDuration, onComplete)); return true; } public bool BeginWrappedRoutine( IEnumerator routine, float fadeOutDuration, float fadeInDuration, float holdBlackDuration, Action onComplete) { if (routine == null || IsBusy) { return false; } transitionRoutine = StartCoroutine(WrappedRoutine(routine, fadeOutDuration, fadeInDuration, holdBlackDuration, onComplete)); return true; } private static BansonicSceneTransitionOverlay FindExisting() { BansonicSceneTransitionOverlay[] all = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); return all != null && all.Length > 0 ? all[0] : null; } private static BansonicSceneTransitionOverlay CreateRuntimeInstance() { GameObject root = new GameObject( RuntimeObjectName, typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(CanvasGroup), typeof(Image), typeof(BansonicSceneTransitionOverlay)); RectTransform rect = root.GetComponent(); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; rect.pivot = new Vector2(0.5f, 0.5f); Canvas canvas = root.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = short.MaxValue; canvas.pixelPerfect = false; CanvasScaler scaler = root.GetComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920f, 1080f); scaler.matchWidthOrHeight = 0.5f; Image image = root.GetComponent(); image.color = Color.black; image.raycastTarget = true; CanvasGroup group = root.GetComponent(); group.alpha = 0f; group.interactable = false; group.blocksRaycasts = false; Object.DontDestroyOnLoad(root); BansonicSceneTransitionOverlay overlay = root.GetComponent(); overlay.canvasGroup = group; overlay.blackoutImage = image; overlay.EnsureSetup(); return overlay; } private void Awake() { EnsureSetup(); Object.DontDestroyOnLoad(gameObject); } private void OnDestroy() { KillTween(); } private void EnsureSetup() { if (transitionBlack == null) { transitionBlack = GetComponent(); if (transitionBlack == null) { transitionBlack = GetComponentInChildren(true); } } if (canvasGroup == null) { if (transitionBlack != null && transitionBlack.CanvasGroup != null) { canvasGroup = transitionBlack.CanvasGroup; } else { canvasGroup = GetComponent(); if (canvasGroup == null) { canvasGroup = GetComponentInChildren(true); } } } if (blackoutImage == null) { blackoutImage = GetComponent(); if (blackoutImage == null) { blackoutImage = GetComponentInChildren(true); } } Canvas canvas = transitionBlack != null && transitionBlack.Canvas != null ? transitionBlack.Canvas : GetComponentInChildren(true); if (canvas != null) { if (canvas.renderMode == RenderMode.ScreenSpaceOverlay) { canvas.sortingOrder = short.MaxValue; } } if (transitionBlack != null) { transitionBlack.RefreshCanvasCamera(); } if (blackoutImage != null) { blackoutImage.color = Color.black; blackoutImage.raycastTarget = true; } if (canvasGroup != null) { canvasGroup.alpha = Mathf.Clamp01(canvasGroup.alpha); if (!IsBusy) { canvasGroup.alpha = 0f; } if (canvasGroup.alpha <= 0f) { canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } } } private void StartSingleTransition(bool fadeToBlack, float duration) { if (transitionRoutine != null) { StopCoroutine(transitionRoutine); transitionRoutine = null; } KillTween(); EnsureSetup(); gameObject.SetActive(true); if (canvasGroup == null) { return; } float safeDuration = Mathf.Max(0f, duration); float targetAlpha = fadeToBlack ? 1f : 0f; if (fadeToBlack) { canvasGroup.interactable = true; canvasGroup.blocksRaycasts = true; } if (safeDuration <= 0f) { canvasGroup.alpha = targetAlpha; if (!fadeToBlack) { canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } return; } currentTween = canvasGroup.DOFade(targetAlpha, safeDuration) .SetEase(Ease.Linear) .SetUpdate(true) .OnComplete(() => { if (canvasGroup == null) { currentTween = null; return; } if (!fadeToBlack) { canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } currentTween = null; }); } private IEnumerator LoadSceneRoutine( string sceneName, LoadSceneMode mode, float fadeOutDuration, float fadeInDuration, float holdBlackDuration, Action onComplete) { yield return WrappedRoutine(LoadSceneOperation(sceneName, mode), fadeOutDuration, fadeInDuration, holdBlackDuration, onComplete); transitionRoutine = null; } private IEnumerator WrappedRoutine( IEnumerator routine, float fadeOutDuration, float fadeInDuration, float holdBlackDuration, Action onComplete) { EnsureSetup(); yield return FadeRoutine(1f, Mathf.Max(0f, fadeOutDuration), true); if (holdBlackDuration > 0f) { yield return new WaitForSecondsRealtime(holdBlackDuration); } while (routine != null && routine.MoveNext()) { yield return routine.Current; } yield return null; yield return null; if (transitionBlack != null) { transitionBlack.RefreshCanvasCamera(); } Canvas.ForceUpdateCanvases(); yield return FadeRoutine(0f, Mathf.Max(0f, fadeInDuration), false); onComplete?.Invoke(); transitionRoutine = null; } private IEnumerator LoadSceneOperation(string sceneName, LoadSceneMode mode) { AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName, mode); while (operation != null && !operation.isDone) { yield return null; } } private IEnumerator FadeRoutine(float targetAlpha, float duration, bool blocking) { EnsureSetup(); gameObject.SetActive(true); if (canvasGroup == null) { yield break; } KillTween(); canvasGroup.interactable = blocking; canvasGroup.blocksRaycasts = blocking; if (duration <= 0f) { canvasGroup.alpha = targetAlpha; if (!blocking) { canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } yield break; } bool completed = false; currentTween = canvasGroup.DOFade(targetAlpha, duration) .SetEase(Ease.Linear) .SetUpdate(true) .OnComplete(() => { completed = true; currentTween = null; if (canvasGroup != null && !blocking) { canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } }); while (!completed) { yield return null; } } private void KillTween() { if (currentTween != null && currentTween.IsActive()) { currentTween.Kill(false); } currentTween = null; } } }