Files
bansonic_beta_main/Assets/_globalNotices/BansonicSceneTransition.cs
T
2026-06-30 21:21:30 +08:00

507 lines
16 KiB
C#

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<GameObject>(prefabResourcePath);
if (prefab != null)
{
GameObject prefabInstance = Object.Instantiate(prefab);
prefabInstance.name = prefab.name;
Object.DontDestroyOnLoad(prefabInstance);
BansonicSceneTransitionOverlay overlayFromPrefab = prefabInstance.GetComponent<BansonicSceneTransitionOverlay>();
if (overlayFromPrefab == null)
{
overlayFromPrefab = prefabInstance.AddComponent<BansonicSceneTransitionOverlay>();
}
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<BansonicSceneTransitionOverlay>(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<RectTransform>();
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>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = short.MaxValue;
canvas.pixelPerfect = false;
CanvasScaler scaler = root.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.matchWidthOrHeight = 0.5f;
Image image = root.GetComponent<Image>();
image.color = Color.black;
image.raycastTarget = true;
CanvasGroup group = root.GetComponent<CanvasGroup>();
group.alpha = 0f;
group.interactable = false;
group.blocksRaycasts = false;
Object.DontDestroyOnLoad(root);
BansonicSceneTransitionOverlay overlay = root.GetComponent<BansonicSceneTransitionOverlay>();
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<gTransBlack>();
if (transitionBlack == null)
{
transitionBlack = GetComponentInChildren<gTransBlack>(true);
}
}
if (canvasGroup == null)
{
if (transitionBlack != null && transitionBlack.CanvasGroup != null)
{
canvasGroup = transitionBlack.CanvasGroup;
}
else
{
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
{
canvasGroup = GetComponentInChildren<CanvasGroup>(true);
}
}
}
if (blackoutImage == null)
{
blackoutImage = GetComponent<Image>();
if (blackoutImage == null)
{
blackoutImage = GetComponentInChildren<Image>(true);
}
}
Canvas canvas = transitionBlack != null && transitionBlack.Canvas != null
? transitionBlack.Canvas
: GetComponentInChildren<Canvas>(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;
}
}
}