using System.Collections; using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; public class UI_SongsSelect_EnterAnim : MonoBehaviour { [Header("Scene")] [SerializeField] private string requiredSceneName = "Songs_Select"; [SerializeField] private bool playOnEnable = true; [SerializeField] private bool useUnscaledTime = true; [Header("Roots")] [SerializeField] private string[] rootNames = { "artworks", "teamSelector", "Songs_Select" }; [SerializeField] private string[] excludeNames = { "bg" }; [Header("Animation")] [SerializeField] private float startDelay = 0.02f; [SerializeField] private float itemDuration = 0.26f; [SerializeField] private float itemStagger = 0.03f; [SerializeField] private float fromOffsetY = 42f; [SerializeField] private Ease moveEase = Ease.OutCubic; [SerializeField] private Ease fadeEase = Ease.OutCubic; private Sequence enterSequence; private readonly List nodeStates = new List(); private readonly List selectables = new List(); private bool restorePending; private bool playRequested; private class NodeState { public RectTransform rect; public Vector2 basePos; public CanvasGroup canvasGroup; public float baseAlpha; public bool createdCanvasGroup; public bool restoreTransform; } private class SelectableState { public Selectable selectable; public bool interactable; } private void OnEnable() { if (playOnEnable && IsAllowedScene()) Play(); } private void OnDisable() { KillTweens(); RestoreAll(); } public void Play() { if (!IsAllowedScene()) { playRequested = false; return; } if (playRequested) return; playRequested = true; KillTweens(); StartCoroutine(PlayRoutine()); } private IEnumerator PlayRoutine() { yield return null; List roots = FindRoots(); if (roots.Count == 0) { playRequested = false; yield break; } List targets = CollectTargets(roots); if (targets.Count == 0) { playRequested = false; yield break; } nodeStates.Clear(); selectables.Clear(); restorePending = true; LockSelectables(roots); enterSequence = DOTween.Sequence().SetUpdate(useUnscaledTime).SetDelay(startDelay); for (int i = 0; i < targets.Count; i++) { RectTransform rt = targets[i]; if (rt == null) continue; CanvasGroup group = rt.GetComponent(); bool created = false; if (group == null) { group = rt.gameObject.AddComponent(); created = true; } Vector2 basePos = rt.anchoredPosition; float baseAlpha = group.alpha <= 0f ? 1f : group.alpha; bool layoutSensitive = UI_OrderedEntryAnimator.IsLayoutSensitive(rt); nodeStates.Add(new NodeState { rect = rt, basePos = basePos, canvasGroup = group, baseAlpha = baseAlpha, createdCanvasGroup = created, restoreTransform = !layoutSensitive }); rt.DOKill(); group.DOKill(); if (!layoutSensitive) { rt.anchoredPosition = basePos - new Vector2(0f, Mathf.Abs(fromOffsetY)); } group.alpha = 0f; float start = i * Mathf.Max(0f, itemStagger); if (!layoutSensitive) { enterSequence.Insert(start, rt.DOAnchorPos(basePos, Mathf.Max(0.01f, itemDuration)) .SetEase(moveEase) .SetUpdate(useUnscaledTime)); } enterSequence.Insert(start, group.DOFade(baseAlpha, Mathf.Max(0.01f, itemDuration)) .SetEase(fadeEase) .SetUpdate(useUnscaledTime)); } float total = enterSequence.Duration(false); StartCoroutine(FailSafeRestore(total + 0.1f)); if (enterSequence.Duration(false) > 0.001f) { enterSequence.OnComplete(RestoreAll); enterSequence.OnKill(RestoreAll); enterSequence.Play(); } else { RestoreAll(); } } private IEnumerator FailSafeRestore(float delay) { float elapsed = 0f; while (elapsed < delay) { elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime; yield return null; } if (restorePending) RestoreAll(); } private void LockSelectables(List roots) { HashSet seen = new HashSet(); for (int r = 0; r < roots.Count; r++) { Transform root = roots[r]; if (root == null) continue; Selectable[] all = root.GetComponentsInChildren(true); for (int i = 0; i < all.Length; i++) { Selectable s = all[i]; if (s == null || seen.Contains(s)) continue; seen.Add(s); selectables.Add(new SelectableState { selectable = s, interactable = s.interactable }); s.interactable = false; } } } private List CollectTargets(List roots) { List list = new List(); HashSet seen = new HashSet(); for (int r = 0; r < roots.Count; r++) { Transform root = roots[r]; if (root == null) continue; CollectUnder(root, list, seen); } list.Sort((a, b) => { if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; Vector3 pa = a.position; Vector3 pb = b.position; int yOrder = -pa.y.CompareTo(pb.y); // top -> bottom if (yOrder != 0) return yOrder; return pa.x.CompareTo(pb.x); // left -> right }); return list; } private void CollectUnder(Transform parent, List list, HashSet seen) { if (parent == null) return; for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if (child == null || !child.gameObject.activeInHierarchy) continue; if (ShouldSkip(child)) continue; RectTransform rt = child as RectTransform; if (rt != null && IsPanelLike(child) && !seen.Contains(rt)) { seen.Add(rt); list.Add(rt); } CollectUnder(child, list, seen); } } private bool IsPanelLike(Transform tr) { if (tr == null) return false; if (tr.GetComponent() != null) return true; if (tr.GetComponent