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("Roots")] [SerializeField] string[] rootNames = { "artworks", "teamSelector", "Songs_Select" }; [SerializeField] bool play_OnEnable = true; [SerializeField] string requiredSceneName = "Songs_Select"; [SerializeField] bool use_Unscaled = true; [SerializeField] float start_Delay = 0.02f; [Header("Excludes")] [SerializeField] string[] exclude_Names = { "bg" }; [Header("Timing")] [SerializeField] float move_Time = 0.45f; [SerializeField] float scale_Time = 0.45f; [SerializeField] float scale_From = 0.92f; [SerializeField] float move_Offset = 120f; [SerializeField] Ease move_Ease = Ease.OutCubic; [SerializeField] Ease scale_Ease = Ease.OutBack; [Header("Safety")] [SerializeField] bool reset_Hidden_CanvasGroups = true; [SerializeField] float hidden_Alpha_Threshold = 0.01f; [SerializeField] float force_Visible_Alpha = 1f; Sequence enter_Sequence; readonly List cached_Nodes = new List(); readonly List cached_Selectables = new List(); bool restorePending; bool playRequested; class NodeState { public Transform tr; public bool isRect; public Vector2 anchoredPos; public Vector3 localPos; public Vector3 localScale; public Quaternion localRot; } class SelectableState { public Selectable s; public bool interactable; } void OnEnable() { if (play_OnEnable && IsAllowedScene()) { Play(); } } void OnDisable() { KillTweens(); RestoreAll(); } public void Play() { if (!IsAllowedScene()) { playRequested = false; return; } if (playRequested) { return; } playRequested = true; KillTweens(); StartCoroutine(PlayRoutine()); } IEnumerator PlayRoutine() { yield return null; // allow layout settle List roots = FindRoots(); if (roots.Count == 0) { playRequested = false; yield break; } if (reset_Hidden_CanvasGroups) { ResetHiddenCanvasGroups(roots); } cached_Nodes.Clear(); cached_Selectables.Clear(); restorePending = true; enter_Sequence = DOTween.Sequence().SetUpdate(use_Unscaled).SetDelay(start_Delay); CollectSelectables(roots); AnimateTransforms(roots); float duration = Mathf.Max(move_Time, scale_Time); StartCoroutine(FailSafeRestore(duration + 0.1f)); if (enter_Sequence.Duration(false) > 0.001f) { enter_Sequence.OnComplete(RestoreAll); enter_Sequence.OnKill(RestoreAll); enter_Sequence.Play(); } else { RestoreAll(); } } IEnumerator FailSafeRestore(float delay) { float t = 0f; while (t < delay) { t += use_Unscaled ? Time.unscaledDeltaTime : Time.deltaTime; yield return null; } if (restorePending) { RestoreAll(); } } void AnimateTransforms(List roots) { HashSet animated = new HashSet(); for (int r = 0; r < roots.Count; r++) { Transform root = roots[r]; if (root == null) continue; AnimateChildrenRespectingExcludes(root, animated); } } void AnimateChildrenRespectingExcludes(Transform parent, HashSet animated) { if (parent == null) return; for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if (child == null) continue; if (!child.gameObject.activeInHierarchy) continue; if (ShouldSkip(child)) continue; if (child.GetComponent() != null) { AnimateChildrenRespectingExcludes(child, animated); continue; } if (ContainsExcludedDescendant(child)) { AnimateChildrenRespectingExcludes(child, animated); continue; } RectTransform rect = child as RectTransform; if (rect == null) continue; if (animated.Contains(child)) continue; animated.Add(child); AnimateNode(rect); } } bool ShouldSkip(Transform tr) { if (tr == null) return false; for (int i = 0; i < exclude_Names.Length; i++) { if (string.IsNullOrEmpty(exclude_Names[i])) continue; if (tr.name == exclude_Names[i]) return true; } return false; } bool ContainsExcludedDescendant(Transform tr) { if (tr == null || exclude_Names == null || exclude_Names.Length == 0) return false; for (int i = 0; i < exclude_Names.Length; i++) { string name = exclude_Names[i]; if (string.IsNullOrEmpty(name)) continue; Transform found = tr.Find(name); if (found != null) return true; } return false; } void AnimateNode(Transform node) { bool isRect = node is RectTransform; Vector2 basePos = isRect ? ((RectTransform)node).anchoredPosition : (Vector2)node.localPosition; Vector3 baseScale = node.localScale; Quaternion baseRot = node.localRotation; node.DOKill(); Vector2 offset = ComputeOffset(basePos, move_Offset); if (HasLayoutInParents(node)) { offset = Vector2.zero; } CacheBase(node, isRect, basePos, baseScale, baseRot); if (isRect) { ((RectTransform)node).anchoredPosition = basePos + offset; } else { node.localPosition = (Vector3)(basePos + offset); } node.localScale = baseScale * scale_From; node.localRotation = baseRot; if (offset.sqrMagnitude > 0.01f) { if (isRect) { enter_Sequence.Join(((RectTransform)node).DOAnchorPos(basePos, move_Time).SetEase(move_Ease)); } else { enter_Sequence.Join(node.DOLocalMove(basePos, move_Time).SetEase(move_Ease)); } } enter_Sequence.Join(node.DOScale(baseScale, scale_Time).SetEase(scale_Ease)); } Vector2 ComputeOffset(Vector2 basePos, float amount) { float xDir = basePos.x >= 0f ? 1f : -1f; float yDir = basePos.y >= 0f ? 1f : -1f; float yAmount = amount * 0.65f; return new Vector2(xDir * amount, yDir * yAmount); } bool HasLayoutInParents(Transform tr) { if (tr == null) return false; Transform current = tr; while (current != null) { if (current.GetComponent() != null || current.GetComponent() != null) { return true; } current = current.parent; } return false; } void CollectSelectables(List roots) { HashSet seen = new HashSet(); for (int r = 0; r < roots.Count; r++) { Transform root = roots[r]; if (root == null) continue; Selectable[] selects = root.GetComponentsInChildren(true); for (int i = 0; i < selects.Length; i++) { Selectable s = selects[i]; if (s == null || seen.Contains(s)) continue; seen.Add(s); cached_Selectables.Add(new SelectableState { s = s, interactable = s.interactable }); s.interactable = false; } } } void ResetHiddenCanvasGroups(List roots) { HashSet seen = new HashSet(); for (int r = 0; r < roots.Count; r++) { Transform root = roots[r]; if (root == null) continue; CanvasGroup[] groups = root.GetComponentsInChildren(true); for (int i = 0; i < groups.Length; i++) { CanvasGroup cg = groups[i]; if (cg == null || seen.Contains(cg)) continue; seen.Add(cg); if (cg.alpha <= hidden_Alpha_Threshold) { cg.alpha = force_Visible_Alpha; } } } } void CacheBase(Transform tr, bool isRect, Vector2 basePos, Vector3 baseScale, Quaternion baseRot) { if (tr == null) return; cached_Nodes.Add(new NodeState { tr = tr, isRect = isRect, anchoredPos = basePos, localPos = tr.localPosition, localScale = baseScale, localRot = baseRot }); } void RestoreAll() { restorePending = false; playRequested = false; for (int i = 0; i < cached_Nodes.Count; i++) { NodeState state = cached_Nodes[i]; if (state == null || state.tr == null) continue; state.tr.DOKill(); if (state.isRect) { RectTransform rt = state.tr as RectTransform; if (rt != null) rt.anchoredPosition = state.anchoredPos; } state.tr.localPosition = state.localPos; state.tr.localScale = state.localScale; state.tr.localRotation = state.localRot; } for (int i = 0; i < cached_Selectables.Count; i++) { SelectableState ss = cached_Selectables[i]; if (ss == null || ss.s == null) continue; ss.s.interactable = ss.interactable; } cached_Nodes.Clear(); cached_Selectables.Clear(); } void KillTweens() { if (enter_Sequence != null) { enter_Sequence.Kill(); enter_Sequence = null; } } bool IsAllowedScene() { if (string.IsNullOrEmpty(requiredSceneName)) return true; Scene active = SceneManager.GetActiveScene(); if (active.IsValid() && active.name == requiredSceneName) return true; Scene own = gameObject.scene; return own.IsValid() && own.name == requiredSceneName; } List FindRoots() { List roots = new List(); Scene activeScene = SceneManager.GetActiveScene(); for (int i = 0; i < rootNames.Length; i++) { string name = rootNames[i]; if (string.IsNullOrEmpty(name)) continue; GameObject go = FindInScene(activeScene, name); if (go == null) continue; roots.Add(go.transform); } if (roots.Count > 1) { roots = FilterNestedRoots(roots); } if (roots.Count == 0) { var canvases = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < canvases.Length; i++) { if (canvases[i] == null) continue; if (canvases[i].gameObject.scene != activeScene) continue; roots.Add(canvases[i].transform); } if (roots.Count > 1) { roots = FilterNestedRoots(roots); } } if (roots.Count == 0) { roots.Add(transform); } return roots; } GameObject FindInScene(Scene scene, string name) { if (!scene.IsValid() || !scene.isLoaded) return null; var all = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < all.Length; i++) { var t = all[i]; if (t == null) continue; if (t.gameObject.scene != scene) continue; if (t.name == name) return t.gameObject; } return null; } List FilterNestedRoots(List roots) { List filtered = new List(); for (int i = 0; i < roots.Count; i++) { Transform candidate = roots[i]; if (candidate == null) continue; bool isChild = false; for (int j = 0; j < roots.Count; j++) { if (i == j) continue; Transform other = roots[j]; if (other == null) continue; if (candidate.IsChildOf(other)) { isChild = true; break; } } if (!isChild) { filtered.Add(candidate); } } return filtered; } } static class UI_SongsSelect_EnterAnimBootstrap { static bool installed; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Reset() { installed = false; } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Install() { if (installed) return; installed = true; GameObject runner = GameObject.Find("__SongsSelectEnterAnimRunner"); if (runner == null) { runner = new GameObject("__SongsSelectEnterAnimRunner"); Object.DontDestroyOnLoad(runner); } if (runner.GetComponent() == null) { runner.AddComponent(); } } } class SongsSelectEnterAnimRunner : MonoBehaviour { Coroutine playRoutine; void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; TryPlay(SceneManager.GetActiveScene()); } void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.activeSceneChanged -= OnActiveSceneChanged; } void OnSceneLoaded(Scene scene, LoadSceneMode mode) { TryPlay(scene); } void OnActiveSceneChanged(Scene from, Scene to) { TryPlay(to); } void TryPlay(Scene scene) { if (scene.name != "Songs_Select") return; if (playRoutine != null) { StopCoroutine(playRoutine); playRoutine = null; } playRoutine = StartCoroutine(PlayWhenReady(scene)); } IEnumerator PlayWhenReady(Scene scene) { for (int i = 0; i < 6; i++) { if (TryAttachAndPlay(scene)) { playRoutine = null; yield break; } yield return null; } yield return new WaitForSeconds(0.1f); TryAttachAndPlay(scene); playRoutine = null; } bool TryAttachAndPlay(Scene scene) { UI_SongsSelect_EnterAnim anim = FindAnimInScene(scene); if (anim == null) { GameObject host = FindInScene(scene, "Songs_Select"); if (host == null) { host = FindInScene(scene, "teamSelector"); } if (host == null) { Canvas anyCanvas = FindCanvasInScene(scene); host = anyCanvas != null ? anyCanvas.gameObject : null; } if (host == null) { return false; } anim = host.GetComponent(); if (anim == null) { anim = host.AddComponent(); } } if (!anim.isActiveAndEnabled) { anim.enabled = true; } anim.Play(); return true; } UI_SongsSelect_EnterAnim FindAnimInScene(Scene scene) { var all = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < all.Length; i++) { var anim = all[i]; if (anim == null) continue; if (anim.gameObject.scene == scene) return anim; } return null; } GameObject FindInScene(Scene scene, string name) { if (!scene.IsValid() || !scene.isLoaded) return null; var all = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < all.Length; i++) { var t = all[i]; if (t == null) continue; if (t.gameObject.scene != scene) continue; if (t.name == name) return t.gameObject; } return null; } Canvas FindCanvasInScene(Scene scene) { var all = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < all.Length; i++) { var c = all[i]; if (c == null) continue; if (c.gameObject.scene != scene) continue; return c; } return null; } }