using System; using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.UI; public static class UI_OrderedEntryAnimator { public static Sequence PlaySingle( GameObject target, int orderIndex = 0, float duration = 0.22f, float stagger = 0.018f, float fromYOffset = -18f, float fromScale = 0.96f, bool useUnscaledTime = true, bool forceFadeOnly = false) { if (target == null || !target.activeInHierarchy) return null; RectTransform rect = target.transform as RectTransform; if (rect == null) rect = target.GetComponent(); if (rect == null) return null; DOTween.Kill(target); CanvasGroup group = target.GetComponent(); if (group == null) group = target.AddComponent(); rect.DOKill(); group.DOKill(); float safeDuration = Mathf.Max(0.01f, duration); float delay = Mathf.Max(0f, stagger) * Mathf.Max(0, orderIndex); Vector2 basePos = rect.anchoredPosition; Vector3 baseScale = rect.localScale; float targetAlpha = group.alpha <= 0f ? 1f : group.alpha; bool layoutControlled = forceFadeOnly || IsLayoutSensitive(rect); if (!layoutControlled) { rect.anchoredPosition = basePos + new Vector2(0f, fromYOffset); rect.localScale = baseScale * Mathf.Max(0.01f, fromScale); } group.alpha = 0f; Sequence sequence = DOTween.Sequence().SetUpdate(useUnscaledTime).SetTarget(target); sequence.SetDelay(delay); if (!layoutControlled) { sequence.Join(rect.DOAnchorPos(basePos, safeDuration).SetEase(Ease.OutCubic).SetUpdate(useUnscaledTime)); sequence.Join(rect.DOScale(baseScale, safeDuration).SetEase(Ease.OutBack).SetUpdate(useUnscaledTime)); } sequence.Join(group.DOFade(targetAlpha, safeDuration).SetEase(Ease.OutCubic).SetUpdate(useUnscaledTime)); sequence.OnKill(() => Restore(rect, group, basePos, baseScale, targetAlpha, !layoutControlled)); sequence.OnComplete(() => Restore(rect, group, basePos, baseScale, targetAlpha, !layoutControlled)); return sequence; } public static Sequence PlayRefresh( GameObject target, float duration = 0.18f, float fromYOffset = -10f, float fromScale = 0.985f, bool useUnscaledTime = true) { return PlaySingle(target, 0, duration, 0f, fromYOffset, fromScale, useUnscaledTime); } public static Sequence PlayFadeOnly( GameObject target, int orderIndex = 0, float duration = 0.18f, float stagger = 0.012f, bool useUnscaledTime = true) { return PlaySingle(target, orderIndex, duration, stagger, 0f, 1f, useUnscaledTime, true); } public static Sequence PlayRefresh(Component target, float duration = 0.18f, float fromYOffset = -10f, float fromScale = 0.985f, bool useUnscaledTime = true) { return target != null ? PlayRefresh(target.gameObject, duration, fromYOffset, fromScale, useUnscaledTime) : null; } public static Sequence Play( Transform root, float itemDuration, float itemStagger, Vector2 fromOffset, bool useUnscaledTime, Predicate includePredicate = null) { if (root == null) return null; List targets = CollectTargets(root, includePredicate); if (targets.Count == 0) return null; targets.Sort(CompareByTopThenLeft); float duration = Mathf.Max(0.01f, itemDuration); float stagger = Mathf.Max(0f, itemStagger); Sequence sequence = DOTween.Sequence().SetUpdate(useUnscaledTime); List states = new List(targets.Count); for (int i = 0; i < targets.Count; i++) { RectTransform rt = targets[i]; if (rt == null || !rt.gameObject.activeInHierarchy) continue; CanvasGroup group = rt.GetComponent(); if (group == null) group = rt.gameObject.AddComponent(); rt.DOKill(); group.DOKill(); Vector2 basePos = rt.anchoredPosition; Vector3 baseScale = rt.localScale; float baseAlpha = group.alpha <= 0f ? 1f : group.alpha; bool layoutControlled = IsLayoutSensitive(rt); states.Add(new AnimState(rt, group, basePos, baseScale, baseAlpha, !layoutControlled)); if (!layoutControlled) { rt.anchoredPosition = basePos + fromOffset; } group.alpha = 0f; float startAt = i * stagger; if (!layoutControlled) { sequence.Insert(startAt, rt.DOAnchorPos(basePos, duration).SetEase(Ease.OutCubic).SetUpdate(useUnscaledTime)); } sequence.Insert(startAt, group.DOFade(baseAlpha, duration).SetEase(Ease.OutCubic).SetUpdate(useUnscaledTime)); } sequence.OnKill(() => RestoreAll(states)); sequence.OnComplete(() => RestoreAll(states)); return sequence; } private struct AnimState { public RectTransform Rect; public CanvasGroup Group; public Vector2 AnchoredPosition; public Vector3 Scale; public float Alpha; public bool RestoreTransform; public AnimState(RectTransform rect, CanvasGroup group, Vector2 anchoredPosition, Vector3 scale, float alpha, bool restoreTransform) { Rect = rect; Group = group; AnchoredPosition = anchoredPosition; Scale = scale; Alpha = alpha; RestoreTransform = restoreTransform; } } private static void RestoreAll(List states) { if (states == null) { return; } for (int i = 0; i < states.Count; i++) { AnimState state = states[i]; Restore(state.Rect, state.Group, state.AnchoredPosition, state.Scale, state.Alpha, state.RestoreTransform); } } private static void Restore(RectTransform rect, CanvasGroup group, Vector2 anchoredPosition, Vector3 scale, float alpha, bool restoreTransform) { if (restoreTransform && rect != null) { rect.anchoredPosition = anchoredPosition; rect.localScale = scale; } if (group != null) { group.alpha = alpha; } } public static bool IsLayoutSensitive(RectTransform rect) { if (rect == null) { return false; } if (rect.GetComponent() != null || rect.GetComponent() != null) { return true; } Transform current = rect; while (current != null && current.parent != null) { Transform parent = current.parent; if (parent.GetComponent() != null) { return true; } RectTransform currentRect = current as RectTransform; if (currentRect != null && currentRect.GetComponent() != null && parent.GetComponent() != null) { return true; } current = parent; } return false; } public static Sequence PlayKeyDownFlash( Transform keyDownRoot, bool useUnscaledTime, float fromYOffset = 36f, float itemStagger = 0.03f) { if (keyDownRoot == null) return null; List targets = CollectKeyDownRects(keyDownRoot); if (targets.Count == 0) return null; targets.Sort(CompareByTopThenLeft); Sequence sequence = DOTween.Sequence().SetUpdate(useUnscaledTime); float moveDuration = 0.1f; float stagger = Mathf.Max(0f, itemStagger); float offsetY = Mathf.Abs(fromYOffset); for (int i = 0; i < targets.Count; i++) { RectTransform rt = targets[i]; if (rt == null || !rt.gameObject.activeInHierarchy) continue; CanvasGroup group = rt.GetComponent(); if (group == null) group = rt.gameObject.AddComponent(); rt.DOKill(); group.DOKill(); Vector2 basePos = rt.anchoredPosition; rt.anchoredPosition = basePos - new Vector2(0f, offsetY); group.alpha = 0f; float startAt = i * stagger; sequence.Insert(startAt, rt.DOAnchorPos(basePos, moveDuration).SetEase(Ease.OutCubic).SetUpdate(useUnscaledTime)); sequence.Insert(startAt, group.DOFade(1f, 0.028f).SetEase(Ease.Linear).SetUpdate(useUnscaledTime)); sequence.Insert(startAt + 0.028f, group.DOFade(0.2f, 0.026f).SetEase(Ease.Linear).SetUpdate(useUnscaledTime)); sequence.Insert(startAt + 0.054f, group.DOFade(1f, 0.04f).SetEase(Ease.OutCubic).SetUpdate(useUnscaledTime)); } return sequence; } public static bool IsNamedOrChildOf(Transform target, string name) { if (target == null || string.IsNullOrEmpty(name)) return false; Transform it = target; while (it != null) { if (string.Equals(it.name, name, StringComparison.OrdinalIgnoreCase)) return true; it = it.parent; } return false; } private static List CollectTargets(Transform root, Predicate includePredicate) { List list = new List(128); HashSet seen = new HashSet(); Stack stack = new Stack(); stack.Push(root); while (stack.Count > 0) { Transform parent = stack.Pop(); if (parent == null) continue; for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if (child == null) continue; RectTransform rt = child as RectTransform; bool canConsider = rt != null && child.gameObject.activeInHierarchy; bool included = false; if (canConsider && (includePredicate == null || includePredicate(rt)) && IsPanelLike(child) && seen.Add(rt)) { list.Add(rt); included = true; } if (!included || !IsLayoutSensitive(rt)) { stack.Push(child); } } } return list; } private static List CollectKeyDownRects(Transform root) { List list = new List(16); RectTransform[] all = root.GetComponentsInChildren(true); for (int i = 0; i < all.Length; i++) { RectTransform rt = all[i]; if (rt == null || !rt.gameObject.activeInHierarchy) continue; if (rt == root) continue; string n = rt.name ?? string.Empty; if (n.IndexOf("sdw 3", StringComparison.OrdinalIgnoreCase) < 0) continue; list.Add(rt); } return list; } private static bool IsPanelLike(Transform tr) { if (tr == null) return false; if (tr.GetComponent() != null) return true; if (tr.GetComponent() != null) return true; if (tr.GetComponent() != null) return true; if (tr.GetComponent() != null) return true; return tr.childCount > 0; } private static int CompareByTopThenLeft(RectTransform a, RectTransform 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 xOrder = pa.x.CompareTo(pb.x); // left -> right if (xOrder != 0) return xOrder; return -pa.y.CompareTo(pb.y); // top -> bottom } }