using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.UI; public class UI_SelectSong_EnterAnim : MonoBehaviour { [Header("Play")] [SerializeField] bool playOnEnable = true; [SerializeField] float delay = 0.05f; [Header("Timing")] [SerializeField] float panelTime = 0.55f; [SerializeField] float titleTime = 0.4f; [SerializeField] float listTime = 0.55f; [SerializeField] float buttonTime = 0.35f; [SerializeField] float stagger = 0.06f; [SerializeField] Ease ease = Ease.OutCubic; [Header("Offsets")] [SerializeField] float slideX = 180f; [SerializeField] float slideY = 120f; [SerializeField] float bgScaleFrom = 1.06f; [SerializeField] bool useUnscaledTime = true; [SerializeField] bool ensureVisibleOnComplete = true; RectTransform root; RectTransform bg; RectTransform leftDlc; RectTransform songScroll; RectTransform rightInfos; RectTransform songTitle; RectTransform dlcTitle; RectTransform quickEnter; RectTransform enterDetail; RectTransform difficultyGroup; readonly Dictionary basePos = new(); readonly Dictionary baseScale = new(); readonly List difficultyItems = new(); Sequence enterSeq; void Awake() { CacheRefs(); } void OnEnable() { if (playOnEnable) { Play(); } } public void Play() { CacheRefs(); KillTweens(); Prepare(); BuildSequence(); } public void SetDelay(float value) { delay = Mathf.Max(0f, value); } void CacheRefs() { if (root == null) { root = transform.parent as RectTransform; if (root == null) { Canvas canvas = SceneObjectLookupCache.FindAny(); if (canvas != null) { root = canvas.transform as RectTransform; } } } if (root == null) { return; } bg = bg != null ? bg : FindPreferredSelectSceneBg(); leftDlc = leftDlc != null ? leftDlc : FindRect(root, "left_select_dlc"); songScroll = songScroll != null ? songScroll : FindRect(root, "SongScrollView"); rightInfos = rightInfos != null ? rightInfos : FindRect(root, "rightInfos"); songTitle = songTitle != null ? songTitle : FindRect(root, "songSVtitle"); dlcTitle = dlcTitle != null ? dlcTitle : FindRect(root, "dlcSVtitle"); quickEnter = quickEnter != null ? quickEnter : FindRect(root, "quickEnterThisSong"); enterDetail = enterDetail != null ? enterDetail : FindRect(root, "enter detail page"); difficultyGroup = difficultyGroup != null ? difficultyGroup : FindRect(root, "difficultyBtnInfos"); difficultyItems.Clear(); if (difficultyGroup != null) { for (int i = 0; i < difficultyGroup.childCount; i++) { RectTransform child = difficultyGroup.GetChild(i) as RectTransform; if (child != null && child.gameObject.activeSelf) { difficultyItems.Add(child); } } difficultyItems.Sort((a, b) => a.anchoredPosition.x.CompareTo(b.anchoredPosition.x)); } } RectTransform FindRect(Transform rootTransform, string name) { if (rootTransform == null || string.IsNullOrEmpty(name)) { return null; } Transform direct = rootTransform.Find(name); if (direct != null) { return direct as RectTransform; } RectTransform[] rects = rootTransform.GetComponentsInChildren(true); for (int i = 0; i < rects.Length; i++) { if (rects[i] != null && rects[i].name == name) { return rects[i]; } } return null; } RectTransform FindPreferredSelectSceneBg() { // Prefer the scene root object named "bg" that owns a Canvas // (the one shown in your hierarchy screenshot), not the legacy inactive "BG". Transform[] all = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < all.Length; i++) { Transform t = all[i]; if (t == null) continue; GameObject go = t.gameObject; if (go == null || !go.scene.IsValid()) continue; if (!string.Equals(t.name, "bg", System.StringComparison.OrdinalIgnoreCase)) continue; if (go.GetComponent() == null) continue; return t as RectTransform; } // Fallbacks for older scene hierarchy. RectTransform fallback = FindRect(root, "BG"); if (fallback != null) return fallback; return FindRect(root, "bg"); } void CacheBase(RectTransform rect) { if (rect == null) { return; } if (!basePos.ContainsKey(rect)) { basePos[rect] = rect.anchoredPosition; } if (!baseScale.ContainsKey(rect)) { baseScale[rect] = rect.localScale; } } void Prepare() { if (bg != null && bg.gameObject.activeInHierarchy) { CacheBase(bg); bg.localScale = baseScale[bg] * bgScaleFrom; CanvasGroup group = EnsureCanvasGroup(bg); if (group != null) group.alpha = 0f; } PrepareSlide(leftDlc, Vector2.left * slideX); PrepareSlide(songScroll, Vector2.down * slideY); PrepareSlide(rightInfos, Vector2.right * slideX); PrepareSlide(songTitle, Vector2.up * (slideY * 0.6f)); PrepareSlide(dlcTitle, Vector2.left * (slideX * 0.6f)); PrepareScale(quickEnter, 0.92f); PrepareScale(enterDetail, 0.92f); for (int i = 0; i < difficultyItems.Count; i++) { PrepareSlide(difficultyItems[i], Vector2.down * (slideY * 0.35f)); } } void PrepareSlide(RectTransform rect, Vector2 offset) { if (rect == null) { return; } CacheBase(rect); if (!UI_OrderedEntryAnimator.IsLayoutSensitive(rect)) { rect.anchoredPosition = basePos[rect] + offset; } CanvasGroup group = EnsureCanvasGroup(rect); if (group != null) group.alpha = 0f; } void PrepareScale(RectTransform rect, float scaleMul) { if (rect == null) { return; } CacheBase(rect); if (!UI_OrderedEntryAnimator.IsLayoutSensitive(rect)) { rect.localScale = baseScale[rect] * scaleMul; } CanvasGroup group = EnsureCanvasGroup(rect); if (group != null) group.alpha = 0f; } void BuildSequence() { enterSeq = DOTween.Sequence().SetUpdate(useUnscaledTime); float t = delay; if (bg != null && bg.gameObject.activeInHierarchy) { CanvasGroup group = EnsureCanvasGroup(bg); if (group != null) { enterSeq.Insert(t, group.DOFade(1f, panelTime).SetEase(ease).SetUpdate(useUnscaledTime)); } if (baseScale.TryGetValue(bg, out Vector3 scale)) { enterSeq.Insert(t, bg.DOScale(scale, panelTime).SetEase(ease).SetUpdate(useUnscaledTime)); } } InsertSlide(leftDlc, t + 0.05f, panelTime); InsertSlide(dlcTitle, t + 0.08f, titleTime); InsertSlide(songTitle, t + 0.1f, titleTime); InsertSlide(songScroll, t + 0.12f, listTime); InsertSlide(rightInfos, t + 0.15f, panelTime); if (difficultyItems.Count > 0) { float start = t + 0.25f; for (int i = 0; i < difficultyItems.Count; i++) { InsertSlide(difficultyItems[i], start + i * stagger, buttonTime); } } InsertScale(enterDetail, t + 0.35f, buttonTime); InsertScale(quickEnter, t + 0.38f, buttonTime); if (ensureVisibleOnComplete) { enterSeq.OnComplete(SnapAllToVisibleFinalState); } } void InsertSlide(RectTransform rect, float at, float time) { if (rect == null) { return; } CacheBase(rect); bool layoutSensitive = UI_OrderedEntryAnimator.IsLayoutSensitive(rect); if (!layoutSensitive && basePos.TryGetValue(rect, out Vector2 pos)) { enterSeq.Insert(at, rect.DOAnchorPos(pos, time).SetEase(ease).SetUpdate(useUnscaledTime)); } CanvasGroup group = EnsureCanvasGroup(rect); if (group != null) { enterSeq.Insert(at, group.DOFade(1f, time).SetEase(ease).SetUpdate(useUnscaledTime)); } } void InsertScale(RectTransform rect, float at, float time) { if (rect == null) { return; } CacheBase(rect); bool layoutSensitive = UI_OrderedEntryAnimator.IsLayoutSensitive(rect); if (!layoutSensitive && baseScale.TryGetValue(rect, out Vector3 scale)) { enterSeq.Insert(at, rect.DOScale(scale, time).SetEase(ease).SetUpdate(useUnscaledTime)); } CanvasGroup group = EnsureCanvasGroup(rect); if (group != null) { enterSeq.Insert(at, group.DOFade(1f, time).SetEase(ease).SetUpdate(useUnscaledTime)); } } void SnapAllToVisibleFinalState() { SnapVisible(bg, applyScale: true); SnapVisible(leftDlc, applyScale: false); SnapVisible(songScroll, applyScale: false); SnapVisible(rightInfos, applyScale: false); SnapVisible(songTitle, applyScale: false); SnapVisible(dlcTitle, applyScale: false); SnapVisible(quickEnter, applyScale: true); SnapVisible(enterDetail, applyScale: true); for (int i = 0; i < difficultyItems.Count; i++) { SnapVisible(difficultyItems[i], applyScale: false); } } void SnapVisible(RectTransform rect, bool applyScale) { if (rect == null) return; if (!rect.gameObject.activeSelf) return; CacheBase(rect); bool layoutSensitive = UI_OrderedEntryAnimator.IsLayoutSensitive(rect); if (!layoutSensitive && basePos.TryGetValue(rect, out Vector2 pos)) { rect.anchoredPosition = pos; } if (!layoutSensitive && applyScale && baseScale.TryGetValue(rect, out Vector3 scale)) { rect.localScale = scale; } CanvasGroup group = EnsureCanvasGroup(rect); if (group != null) { group.alpha = 1f; group.interactable = true; group.blocksRaycasts = true; } } CanvasGroup EnsureCanvasGroup(RectTransform rect) { if (rect == null) { return null; } CanvasGroup group = rect.GetComponent(); if (group == null) { group = rect.gameObject.AddComponent(); } return group; } void KillTweens() { if (enterSeq != null) { enterSeq.Kill(); enterSeq = null; } } }