561 lines
16 KiB
C#
561 lines
16 KiB
C#
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<NodeState> nodeStates = new List<NodeState>();
|
|
private readonly List<SelectableState> selectables = new List<SelectableState>();
|
|
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<Transform> roots = FindRoots();
|
|
if (roots.Count == 0)
|
|
{
|
|
playRequested = false;
|
|
yield break;
|
|
}
|
|
|
|
List<RectTransform> 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<CanvasGroup>();
|
|
bool created = false;
|
|
if (group == null)
|
|
{
|
|
group = rt.gameObject.AddComponent<CanvasGroup>();
|
|
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<Transform> roots)
|
|
{
|
|
HashSet<Selectable> seen = new HashSet<Selectable>();
|
|
for (int r = 0; r < roots.Count; r++)
|
|
{
|
|
Transform root = roots[r];
|
|
if (root == null)
|
|
continue;
|
|
|
|
Selectable[] all = root.GetComponentsInChildren<Selectable>(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<RectTransform> CollectTargets(List<Transform> roots)
|
|
{
|
|
List<RectTransform> list = new List<RectTransform>();
|
|
HashSet<RectTransform> seen = new HashSet<RectTransform>();
|
|
|
|
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<RectTransform> list, HashSet<RectTransform> 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<LayoutGroup>() != null)
|
|
return true;
|
|
if (tr.GetComponent<Button>() != null || tr.GetComponent<Toggle>() != null || tr.GetComponent<ScrollRect>() != null)
|
|
return true;
|
|
if (tr.GetComponent<CanvasGroup>() != null)
|
|
return true;
|
|
if (tr.childCount >= 2)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
private bool ShouldSkip(Transform tr)
|
|
{
|
|
if (tr == null || excludeNames == null)
|
|
return false;
|
|
|
|
for (int i = 0; i < excludeNames.Length; i++)
|
|
{
|
|
string name = excludeNames[i];
|
|
if (string.IsNullOrEmpty(name))
|
|
continue;
|
|
if (string.Equals(tr.name, name, System.StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void RestoreAll()
|
|
{
|
|
restorePending = false;
|
|
playRequested = false;
|
|
|
|
for (int i = 0; i < nodeStates.Count; i++)
|
|
{
|
|
NodeState st = nodeStates[i];
|
|
if (st == null || st.rect == null)
|
|
continue;
|
|
|
|
st.rect.DOKill();
|
|
if (st.canvasGroup != null)
|
|
st.canvasGroup.DOKill();
|
|
|
|
if (st.restoreTransform)
|
|
{
|
|
st.rect.anchoredPosition = st.basePos;
|
|
}
|
|
if (st.canvasGroup != null)
|
|
st.canvasGroup.alpha = st.baseAlpha;
|
|
}
|
|
|
|
for (int i = 0; i < selectables.Count; i++)
|
|
{
|
|
SelectableState ss = selectables[i];
|
|
if (ss == null || ss.selectable == null)
|
|
continue;
|
|
ss.selectable.interactable = ss.interactable;
|
|
}
|
|
|
|
nodeStates.Clear();
|
|
selectables.Clear();
|
|
}
|
|
|
|
private void KillTweens()
|
|
{
|
|
if (enterSequence != null)
|
|
{
|
|
enterSequence.Kill();
|
|
enterSequence = null;
|
|
}
|
|
}
|
|
|
|
private bool IsAllowedScene()
|
|
{
|
|
if (string.IsNullOrEmpty(requiredSceneName))
|
|
return true;
|
|
|
|
Scene own = gameObject.scene;
|
|
if (own.IsValid() && own.name == requiredSceneName)
|
|
return true;
|
|
|
|
Scene active = SceneManager.GetActiveScene();
|
|
return active.IsValid() && active.name == requiredSceneName;
|
|
}
|
|
|
|
private List<Transform> FindRoots()
|
|
{
|
|
List<Transform> roots = new List<Transform>();
|
|
Scene scene = gameObject.scene.IsValid() && gameObject.scene.isLoaded
|
|
? gameObject.scene
|
|
: SceneManager.GetActiveScene();
|
|
|
|
for (int i = 0; i < rootNames.Length; i++)
|
|
{
|
|
string name = rootNames[i];
|
|
if (string.IsNullOrEmpty(name))
|
|
continue;
|
|
|
|
Transform found = FindInSceneByName(scene, name);
|
|
if (found != null)
|
|
roots.Add(found);
|
|
}
|
|
|
|
if (roots.Count == 0)
|
|
{
|
|
Canvas[] canvases = UnityEngine.Object.FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
for (int i = 0; i < canvases.Length; i++)
|
|
{
|
|
Canvas c = canvases[i];
|
|
if (c == null || c.gameObject.scene != scene)
|
|
continue;
|
|
roots.Add(c.transform);
|
|
}
|
|
}
|
|
|
|
if (roots.Count == 0)
|
|
roots.Add(transform);
|
|
|
|
return FilterNestedRoots(roots);
|
|
}
|
|
|
|
private Transform FindInSceneByName(Scene scene, string objectName)
|
|
{
|
|
if (!scene.IsValid() || !scene.isLoaded || string.IsNullOrEmpty(objectName))
|
|
return null;
|
|
|
|
Transform[] all = UnityEngine.Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
Transform t = all[i];
|
|
if (t == null || t.gameObject.scene != scene)
|
|
continue;
|
|
if (string.Equals(t.name, objectName, System.StringComparison.OrdinalIgnoreCase))
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private List<Transform> FilterNestedRoots(List<Transform> roots)
|
|
{
|
|
List<Transform> filtered = new List<Transform>();
|
|
for (int i = 0; i < roots.Count; i++)
|
|
{
|
|
Transform candidate = roots[i];
|
|
if (candidate == null)
|
|
continue;
|
|
|
|
bool childOfOther = 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))
|
|
{
|
|
childOfOther = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!childOfOther)
|
|
filtered.Add(candidate);
|
|
}
|
|
return filtered;
|
|
}
|
|
}
|
|
|
|
static class UI_SongsSelect_EnterAnimBootstrap
|
|
{
|
|
private static bool installed;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
|
private static void Reset()
|
|
{
|
|
installed = false;
|
|
}
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
private static void Install()
|
|
{
|
|
if (installed) return;
|
|
installed = true;
|
|
|
|
GameObject runner = SceneObjectLookupCache.Find("__SongsSelectEnterAnimRunner");
|
|
if (runner == null)
|
|
{
|
|
runner = new GameObject("__SongsSelectEnterAnimRunner");
|
|
UnityEngine.Object.DontDestroyOnLoad(runner);
|
|
}
|
|
if (runner.GetComponent<SongsSelectEnterAnimRunner>() == null)
|
|
runner.AddComponent<SongsSelectEnterAnimRunner>();
|
|
}
|
|
}
|
|
|
|
class SongsSelectEnterAnimRunner : MonoBehaviour
|
|
{
|
|
private Coroutine playRoutine;
|
|
|
|
private void OnEnable()
|
|
{
|
|
SceneManager.sceneLoaded += OnSceneLoaded;
|
|
SceneManager.activeSceneChanged += OnActiveSceneChanged;
|
|
TryPlay(SceneManager.GetActiveScene());
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
SceneManager.sceneLoaded -= OnSceneLoaded;
|
|
SceneManager.activeSceneChanged -= OnActiveSceneChanged;
|
|
}
|
|
|
|
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
|
{
|
|
TryPlay(scene);
|
|
}
|
|
|
|
private void OnActiveSceneChanged(Scene from, Scene to)
|
|
{
|
|
TryPlay(to);
|
|
}
|
|
|
|
private void TryPlay(Scene scene)
|
|
{
|
|
if (scene.name != "Songs_Select")
|
|
return;
|
|
|
|
if (playRoutine != null)
|
|
StopCoroutine(playRoutine);
|
|
playRoutine = StartCoroutine(PlayWhenReady(scene));
|
|
}
|
|
|
|
private IEnumerator PlayWhenReady(Scene scene)
|
|
{
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
if (TryAttachAndPlay(scene))
|
|
{
|
|
playRoutine = null;
|
|
yield break;
|
|
}
|
|
yield return null;
|
|
}
|
|
|
|
yield return new WaitForSecondsRealtime(0.08f);
|
|
TryAttachAndPlay(scene);
|
|
playRoutine = null;
|
|
}
|
|
|
|
private bool TryAttachAndPlay(Scene scene)
|
|
{
|
|
UI_SongsSelect_EnterAnim anim = FindAnimInScene(scene);
|
|
if (anim == null)
|
|
{
|
|
GameObject host = FindInScene(scene, "__SongsSelectEnterAnimHost");
|
|
if (host == null)
|
|
{
|
|
host = new GameObject("__SongsSelectEnterAnimHost");
|
|
try { SceneManager.MoveGameObjectToScene(host, scene); } catch { }
|
|
}
|
|
|
|
anim = host.GetComponent<UI_SongsSelect_EnterAnim>();
|
|
if (anim == null)
|
|
anim = host.AddComponent<UI_SongsSelect_EnterAnim>();
|
|
}
|
|
|
|
if (!anim.isActiveAndEnabled)
|
|
anim.enabled = true;
|
|
|
|
anim.Play();
|
|
return true;
|
|
}
|
|
|
|
private UI_SongsSelect_EnterAnim FindAnimInScene(Scene scene)
|
|
{
|
|
UI_SongsSelect_EnterAnim[] all = UnityEngine.Object.FindObjectsByType<UI_SongsSelect_EnterAnim>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
UI_SongsSelect_EnterAnim anim = all[i];
|
|
if (anim == null) continue;
|
|
if (anim.gameObject.scene == scene) return anim;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private GameObject FindInScene(Scene scene, string objectName)
|
|
{
|
|
if (!scene.IsValid() || !scene.isLoaded)
|
|
return null;
|
|
|
|
Transform[] all = UnityEngine.Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
Transform t = all[i];
|
|
if (t == null || t.gameObject.scene != scene)
|
|
continue;
|
|
if (t.name == objectName)
|
|
return t.gameObject;
|
|
}
|
|
return null;
|
|
}
|
|
}
|