超大量的更新 修复很多问题,gameplay特效初步
This commit is contained in:
@@ -7,58 +7,51 @@ 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] 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" };
|
||||
[SerializeField] private string[] rootNames = { "artworks", "teamSelector", "Songs_Select" };
|
||||
[SerializeField] private string[] excludeNames = { "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;
|
||||
[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;
|
||||
|
||||
Sequence enter_Sequence;
|
||||
readonly List<NodeState> cached_Nodes = new List<NodeState>();
|
||||
readonly List<SelectableState> cached_Selectables = new List<SelectableState>();
|
||||
bool restorePending;
|
||||
bool playRequested;
|
||||
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;
|
||||
|
||||
class NodeState
|
||||
private class NodeState
|
||||
{
|
||||
public Transform tr;
|
||||
public bool isRect;
|
||||
public Vector2 anchoredPos;
|
||||
public Vector3 localPos;
|
||||
public Vector3 localScale;
|
||||
public Quaternion localRot;
|
||||
public RectTransform rect;
|
||||
public Vector2 basePos;
|
||||
public CanvasGroup canvasGroup;
|
||||
public float baseAlpha;
|
||||
public bool createdCanvasGroup;
|
||||
}
|
||||
|
||||
class SelectableState
|
||||
private class SelectableState
|
||||
{
|
||||
public Selectable s;
|
||||
public Selectable selectable;
|
||||
public bool interactable;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
private void OnEnable()
|
||||
{
|
||||
if (play_OnEnable && IsAllowedScene())
|
||||
{
|
||||
if (playOnEnable && IsAllowedScene())
|
||||
Play();
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
private void OnDisable()
|
||||
{
|
||||
KillTweens();
|
||||
RestoreAll();
|
||||
@@ -72,17 +65,16 @@ public class UI_SongsSelect_EnterAnim : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
if (playRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
playRequested = true;
|
||||
KillTweens();
|
||||
StartCoroutine(PlayRoutine());
|
||||
}
|
||||
|
||||
IEnumerator PlayRoutine()
|
||||
private IEnumerator PlayRoutine()
|
||||
{
|
||||
yield return null; // allow layout settle
|
||||
yield return null;
|
||||
|
||||
List<Transform> roots = FindRoots();
|
||||
if (roots.Count == 0)
|
||||
@@ -90,28 +82,73 @@ public class UI_SongsSelect_EnterAnim : MonoBehaviour
|
||||
playRequested = false;
|
||||
yield break;
|
||||
}
|
||||
if (reset_Hidden_CanvasGroups)
|
||||
|
||||
List<RectTransform> targets = CollectTargets(roots);
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
ResetHiddenCanvasGroups(roots);
|
||||
playRequested = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
cached_Nodes.Clear();
|
||||
cached_Selectables.Clear();
|
||||
nodeStates.Clear();
|
||||
selectables.Clear();
|
||||
restorePending = true;
|
||||
|
||||
enter_Sequence = DOTween.Sequence().SetUpdate(use_Unscaled).SetDelay(start_Delay);
|
||||
LockSelectables(roots);
|
||||
|
||||
CollectSelectables(roots);
|
||||
AnimateTransforms(roots);
|
||||
enterSequence = DOTween.Sequence().SetUpdate(useUnscaledTime).SetDelay(startDelay);
|
||||
|
||||
float duration = Mathf.Max(move_Time, scale_Time);
|
||||
StartCoroutine(FailSafeRestore(duration + 0.1f));
|
||||
|
||||
if (enter_Sequence.Duration(false) > 0.001f)
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
enter_Sequence.OnComplete(RestoreAll);
|
||||
enter_Sequence.OnKill(RestoreAll);
|
||||
enter_Sequence.Play();
|
||||
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;
|
||||
|
||||
nodeStates.Add(new NodeState
|
||||
{
|
||||
rect = rt,
|
||||
basePos = basePos,
|
||||
canvasGroup = group,
|
||||
baseAlpha = baseAlpha,
|
||||
createdCanvasGroup = created
|
||||
});
|
||||
|
||||
rt.DOKill();
|
||||
group.DOKill();
|
||||
|
||||
rt.anchoredPosition = basePos - new Vector2(0f, Mathf.Abs(fromOffsetY));
|
||||
group.alpha = 0f;
|
||||
|
||||
float start = i * Mathf.Max(0f, itemStagger);
|
||||
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
|
||||
{
|
||||
@@ -119,311 +156,244 @@ public class UI_SongsSelect_EnterAnim : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator FailSafeRestore(float delay)
|
||||
private IEnumerator FailSafeRestore(float delay)
|
||||
{
|
||||
float t = 0f;
|
||||
while (t < delay)
|
||||
float elapsed = 0f;
|
||||
while (elapsed < delay)
|
||||
{
|
||||
t += use_Unscaled ? Time.unscaledDeltaTime : Time.deltaTime;
|
||||
elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (restorePending)
|
||||
{
|
||||
RestoreAll();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimateTransforms(List<Transform> roots)
|
||||
{
|
||||
HashSet<Transform> animated = new HashSet<Transform>();
|
||||
for (int r = 0; r < roots.Count; r++)
|
||||
{
|
||||
Transform root = roots[r];
|
||||
if (root == null) continue;
|
||||
AnimateChildrenRespectingExcludes(root, animated);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimateChildrenRespectingExcludes(Transform parent, HashSet<Transform> 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<Canvas>() != 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<LayoutGroup>() != null || current.GetComponent<ContentSizeFitter>() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CollectSelectables(List<Transform> roots)
|
||||
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[] selects = root.GetComponentsInChildren<Selectable>(true);
|
||||
for (int i = 0; i < selects.Length; i++)
|
||||
if (root == null)
|
||||
continue;
|
||||
|
||||
Selectable[] all = root.GetComponentsInChildren<Selectable>(true);
|
||||
for (int i = 0; i < all.Length; i++)
|
||||
{
|
||||
Selectable s = selects[i];
|
||||
if (s == null || seen.Contains(s)) continue;
|
||||
Selectable s = all[i];
|
||||
if (s == null || seen.Contains(s))
|
||||
continue;
|
||||
|
||||
seen.Add(s);
|
||||
cached_Selectables.Add(new SelectableState { s = s, interactable = s.interactable });
|
||||
selectables.Add(new SelectableState { selectable = s, interactable = s.interactable });
|
||||
s.interactable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResetHiddenCanvasGroups(List<Transform> roots)
|
||||
private List<RectTransform> CollectTargets(List<Transform> roots)
|
||||
{
|
||||
HashSet<CanvasGroup> seen = new HashSet<CanvasGroup>();
|
||||
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;
|
||||
CanvasGroup[] groups = root.GetComponentsInChildren<CanvasGroup>(true);
|
||||
for (int i = 0; i < groups.Length; i++)
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
seen.Add(rt);
|
||||
list.Add(rt);
|
||||
}
|
||||
|
||||
CollectUnder(child, list, seen);
|
||||
}
|
||||
}
|
||||
|
||||
void CacheBase(Transform tr, bool isRect, Vector2 basePos, Vector3 baseScale, Quaternion baseRot)
|
||||
private bool IsPanelLike(Transform tr)
|
||||
{
|
||||
if (tr == null) return;
|
||||
cached_Nodes.Add(new NodeState
|
||||
{
|
||||
tr = tr,
|
||||
isRect = isRect,
|
||||
anchoredPos = basePos,
|
||||
localPos = tr.localPosition,
|
||||
localScale = baseScale,
|
||||
localRot = baseRot
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
void RestoreAll()
|
||||
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 < cached_Nodes.Count; i++)
|
||||
|
||||
for (int i = 0; i < nodeStates.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;
|
||||
NodeState st = nodeStates[i];
|
||||
if (st == null || st.rect == null)
|
||||
continue;
|
||||
|
||||
st.rect.DOKill();
|
||||
if (st.canvasGroup != null)
|
||||
st.canvasGroup.DOKill();
|
||||
|
||||
st.rect.anchoredPosition = st.basePos;
|
||||
if (st.canvasGroup != null)
|
||||
st.canvasGroup.alpha = st.baseAlpha;
|
||||
}
|
||||
for (int i = 0; i < cached_Selectables.Count; i++)
|
||||
|
||||
for (int i = 0; i < selectables.Count; i++)
|
||||
{
|
||||
SelectableState ss = cached_Selectables[i];
|
||||
if (ss == null || ss.s == null) continue;
|
||||
ss.s.interactable = ss.interactable;
|
||||
SelectableState ss = selectables[i];
|
||||
if (ss == null || ss.selectable == null)
|
||||
continue;
|
||||
ss.selectable.interactable = ss.interactable;
|
||||
}
|
||||
cached_Nodes.Clear();
|
||||
cached_Selectables.Clear();
|
||||
|
||||
nodeStates.Clear();
|
||||
selectables.Clear();
|
||||
}
|
||||
|
||||
void KillTweens()
|
||||
private void KillTweens()
|
||||
{
|
||||
if (enter_Sequence != null)
|
||||
if (enterSequence != null)
|
||||
{
|
||||
enter_Sequence.Kill();
|
||||
enter_Sequence = null;
|
||||
enterSequence.Kill();
|
||||
enterSequence = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAllowedScene()
|
||||
private 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;
|
||||
if (own.IsValid() && own.name == requiredSceneName)
|
||||
return true;
|
||||
|
||||
Scene active = SceneManager.GetActiveScene();
|
||||
return active.IsValid() && active.name == requiredSceneName;
|
||||
}
|
||||
|
||||
List<Transform> FindRoots()
|
||||
private List<Transform> FindRoots()
|
||||
{
|
||||
List<Transform> roots = new List<Transform>();
|
||||
Scene activeScene = SceneManager.GetActiveScene();
|
||||
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;
|
||||
GameObject go = FindInScene(activeScene, name);
|
||||
if (go == null) continue;
|
||||
roots.Add(go.transform);
|
||||
}
|
||||
if (roots.Count > 1)
|
||||
{
|
||||
roots = FilterNestedRoots(roots);
|
||||
if (string.IsNullOrEmpty(name))
|
||||
continue;
|
||||
|
||||
Transform found = FindInSceneByName(scene, name);
|
||||
if (found != null)
|
||||
roots.Add(found);
|
||||
}
|
||||
|
||||
if (roots.Count == 0)
|
||||
{
|
||||
var canvases = Object.FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
Canvas[] canvases = UnityEngine.Object.FindObjectsByType<Canvas>(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);
|
||||
Canvas c = canvases[i];
|
||||
if (c == null || c.gameObject.scene != scene)
|
||||
continue;
|
||||
roots.Add(c.transform);
|
||||
}
|
||||
}
|
||||
|
||||
if (roots.Count == 0)
|
||||
{
|
||||
roots.Add(transform);
|
||||
}
|
||||
return roots;
|
||||
|
||||
return FilterNestedRoots(roots);
|
||||
}
|
||||
|
||||
GameObject FindInScene(Scene scene, string name)
|
||||
private Transform FindInSceneByName(Scene scene, string objectName)
|
||||
{
|
||||
if (!scene.IsValid() || !scene.isLoaded) return null;
|
||||
var all = Resources.FindObjectsOfTypeAll<Transform>();
|
||||
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++)
|
||||
{
|
||||
var t = all[i];
|
||||
if (t == null) continue;
|
||||
if (t.gameObject.scene != scene) continue;
|
||||
if (t.name == name) return t.gameObject;
|
||||
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;
|
||||
}
|
||||
|
||||
List<Transform> FilterNestedRoots(List<Transform> roots)
|
||||
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 isChild = false;
|
||||
if (candidate == null)
|
||||
continue;
|
||||
|
||||
bool childOfOther = false;
|
||||
for (int j = 0; j < roots.Count; j++)
|
||||
{
|
||||
if (i == j) continue;
|
||||
@@ -431,14 +401,12 @@ public class UI_SongsSelect_EnterAnim : MonoBehaviour
|
||||
if (other == null) continue;
|
||||
if (candidate.IsChildOf(other))
|
||||
{
|
||||
isChild = true;
|
||||
childOfOther = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isChild)
|
||||
{
|
||||
if (!childOfOther)
|
||||
filtered.Add(candidate);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
@@ -446,71 +414,69 @@ public class UI_SongsSelect_EnterAnim : MonoBehaviour
|
||||
|
||||
static class UI_SongsSelect_EnterAnimBootstrap
|
||||
{
|
||||
static bool installed;
|
||||
private static bool installed;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void Reset()
|
||||
private static void Reset()
|
||||
{
|
||||
installed = false;
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void Install()
|
||||
private static void Install()
|
||||
{
|
||||
if (installed) return;
|
||||
installed = true;
|
||||
|
||||
GameObject runner = GameObject.Find("__SongsSelectEnterAnimRunner");
|
||||
if (runner == null)
|
||||
{
|
||||
runner = new GameObject("__SongsSelectEnterAnimRunner");
|
||||
Object.DontDestroyOnLoad(runner);
|
||||
UnityEngine.Object.DontDestroyOnLoad(runner);
|
||||
}
|
||||
if (runner.GetComponent<SongsSelectEnterAnimRunner>() == null)
|
||||
{
|
||||
runner.AddComponent<SongsSelectEnterAnimRunner>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SongsSelectEnterAnimRunner : MonoBehaviour
|
||||
{
|
||||
Coroutine playRoutine;
|
||||
private Coroutine playRoutine;
|
||||
|
||||
void OnEnable()
|
||||
private void OnEnable()
|
||||
{
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
SceneManager.activeSceneChanged += OnActiveSceneChanged;
|
||||
TryPlay(SceneManager.GetActiveScene());
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
private void OnDisable()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
SceneManager.activeSceneChanged -= OnActiveSceneChanged;
|
||||
}
|
||||
|
||||
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
TryPlay(scene);
|
||||
}
|
||||
|
||||
void OnActiveSceneChanged(Scene from, Scene to)
|
||||
private void OnActiveSceneChanged(Scene from, Scene to)
|
||||
{
|
||||
TryPlay(to);
|
||||
}
|
||||
|
||||
void TryPlay(Scene scene)
|
||||
private void TryPlay(Scene scene)
|
||||
{
|
||||
if (scene.name != "Songs_Select") return;
|
||||
if (scene.name != "Songs_Select")
|
||||
return;
|
||||
|
||||
if (playRoutine != null)
|
||||
{
|
||||
StopCoroutine(playRoutine);
|
||||
playRoutine = null;
|
||||
}
|
||||
playRoutine = StartCoroutine(PlayWhenReady(scene));
|
||||
}
|
||||
|
||||
IEnumerator PlayWhenReady(Scene scene)
|
||||
private IEnumerator PlayWhenReady(Scene scene)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
@@ -521,79 +487,61 @@ class SongsSelectEnterAnimRunner : MonoBehaviour
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
yield return new WaitForSecondsRealtime(0.08f);
|
||||
TryAttachAndPlay(scene);
|
||||
playRoutine = null;
|
||||
}
|
||||
|
||||
bool TryAttachAndPlay(Scene scene)
|
||||
private bool TryAttachAndPlay(Scene scene)
|
||||
{
|
||||
UI_SongsSelect_EnterAnim anim = FindAnimInScene(scene);
|
||||
if (anim == null)
|
||||
{
|
||||
GameObject host = FindInScene(scene, "Songs_Select");
|
||||
GameObject host = FindInScene(scene, "__SongsSelectEnterAnimHost");
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
UI_SongsSelect_EnterAnim FindAnimInScene(Scene scene)
|
||||
private UI_SongsSelect_EnterAnim FindAnimInScene(Scene scene)
|
||||
{
|
||||
var all = Resources.FindObjectsOfTypeAll<UI_SongsSelect_EnterAnim>();
|
||||
UI_SongsSelect_EnterAnim[] all = UnityEngine.Object.FindObjectsByType<UI_SongsSelect_EnterAnim>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < all.Length; i++)
|
||||
{
|
||||
var anim = all[i];
|
||||
UI_SongsSelect_EnterAnim anim = all[i];
|
||||
if (anim == null) continue;
|
||||
if (anim.gameObject.scene == scene) return anim;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject FindInScene(Scene scene, string name)
|
||||
private GameObject FindInScene(Scene scene, string objectName)
|
||||
{
|
||||
if (!scene.IsValid() || !scene.isLoaded) return null;
|
||||
var all = Resources.FindObjectsOfTypeAll<Transform>();
|
||||
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;
|
||||
}
|
||||
if (!scene.IsValid() || !scene.isLoaded)
|
||||
return null;
|
||||
|
||||
Canvas FindCanvasInScene(Scene scene)
|
||||
{
|
||||
var all = Resources.FindObjectsOfTypeAll<Canvas>();
|
||||
Transform[] all = UnityEngine.Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < all.Length; i++)
|
||||
{
|
||||
var c = all[i];
|
||||
if (c == null) continue;
|
||||
if (c.gameObject.scene != scene) continue;
|
||||
return c;
|
||||
Transform t = all[i];
|
||||
if (t == null || t.gameObject.scene != scene)
|
||||
continue;
|
||||
if (t.name == objectName)
|
||||
return t.gameObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,692 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class UI_SongsSelect_RuntimeFeatures : MonoBehaviour
|
||||
{
|
||||
[Header("Scene")]
|
||||
[SerializeField] private string requiredSceneName = "Songs_Select";
|
||||
[SerializeField] private string gameplaySceneName = "gamePlay_gamePlay";
|
||||
|
||||
[Header("Targets")]
|
||||
[SerializeField] private string enterButtonName = "bottom";
|
||||
[SerializeField] private string enterButtonParentName = "enterBar";
|
||||
[SerializeField] private string autoPlayToggleName = "enableAutoPlayToggle";
|
||||
[SerializeField] private string difficultiesRootName = "horizontalDifficultiesLayouts";
|
||||
|
||||
[Header("Difficulty Indicator Pop")]
|
||||
[SerializeField] private float diffPopScaleFrom = 1.25f;
|
||||
[SerializeField] private float diffPopTime = 0.26f;
|
||||
[SerializeField] private float diffPopFadeInTime = 0.08f;
|
||||
[SerializeField] private Ease diffPopEase = Ease.OutBack;
|
||||
|
||||
[Header("Difficulty ID Roll")]
|
||||
[SerializeField] private string difficultyIdName = "difficultyID";
|
||||
[SerializeField] private string difficultyIdGroundName = "difficultyIDGround";
|
||||
[SerializeField] private float difficultyIdStepDelayFast = 0.03f;
|
||||
[SerializeField] private float difficultyIdStepDelaySlow = 0.11f;
|
||||
[SerializeField] private bool difficultyIdUseUnscaledTime = true;
|
||||
|
||||
private Button enterButton;
|
||||
private UnityAction enterAction;
|
||||
private Toggle autoPlayToggle;
|
||||
private UnityAction<bool> autoPlayToggleAction;
|
||||
private Coroutine bindRoutine;
|
||||
private Coroutine bgmKeepAliveRoutine;
|
||||
private Coroutine difficultyIdRollCoroutine;
|
||||
private readonly Dictionary<Button, UnityAction> diffActions = new Dictionary<Button, UnityAction>();
|
||||
private readonly Dictionary<Toggle, UnityAction<bool>> diffToggleActions = new Dictionary<Toggle, UnityAction<bool>>();
|
||||
private readonly List<Text> difficultyIdDisplayTexts = new List<Text>();
|
||||
private int difficultyIdDisplayed = int.MinValue;
|
||||
private float nextDifficultyIdRefreshTime = 0f;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
var bgm = BgmPlaybackManager.Instance ?? BgmPlaybackManager.EnsureInstance();
|
||||
if (bgm != null)
|
||||
{
|
||||
bgm.autoPlay = true;
|
||||
if (bgm.audioSource != null)
|
||||
{
|
||||
bgm.audioSource.mute = false;
|
||||
if (bgm.audioSource.volume <= 0.001f)
|
||||
bgm.audioSource.volume = 1f;
|
||||
}
|
||||
bgm.EnsurePlaying();
|
||||
}
|
||||
|
||||
if (bindRoutine != null)
|
||||
{
|
||||
StopCoroutine(bindRoutine);
|
||||
bindRoutine = null;
|
||||
}
|
||||
|
||||
if (bgmKeepAliveRoutine != null)
|
||||
StopCoroutine(bgmKeepAliveRoutine);
|
||||
bgmKeepAliveRoutine = StartCoroutine(EnsureBgmAliveRoutine());
|
||||
|
||||
RebindSoon();
|
||||
ResolveDifficultyIdDisplayTexts();
|
||||
RefreshDifficultyIdDisplay(forceInstant: true);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (bindRoutine != null)
|
||||
{
|
||||
StopCoroutine(bindRoutine);
|
||||
bindRoutine = null;
|
||||
}
|
||||
if (bgmKeepAliveRoutine != null)
|
||||
{
|
||||
StopCoroutine(bgmKeepAliveRoutine);
|
||||
bgmKeepAliveRoutine = null;
|
||||
}
|
||||
if (difficultyIdRollCoroutine != null)
|
||||
{
|
||||
StopCoroutine(difficultyIdRollCoroutine);
|
||||
difficultyIdRollCoroutine = null;
|
||||
}
|
||||
Unbind();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsInRequiredScene())
|
||||
return;
|
||||
|
||||
if (Time.unscaledTime < nextDifficultyIdRefreshTime)
|
||||
return;
|
||||
|
||||
nextDifficultyIdRefreshTime = Time.unscaledTime + 0.06f;
|
||||
RefreshDifficultyIdDisplay(forceInstant: false);
|
||||
}
|
||||
|
||||
private IEnumerator EnsureBgmAliveRoutine()
|
||||
{
|
||||
while (enabled)
|
||||
{
|
||||
if (IsInRequiredScene())
|
||||
{
|
||||
var bgm = BgmPlaybackManager.Instance ?? BgmPlaybackManager.EnsureInstance();
|
||||
if (bgm != null)
|
||||
{
|
||||
bgm.autoPlay = true;
|
||||
if (bgm.audioSource != null)
|
||||
{
|
||||
bgm.audioSource.mute = false;
|
||||
if (bgm.audioSource.volume <= 0.001f)
|
||||
bgm.audioSource.volume = 1f;
|
||||
}
|
||||
bgm.EnsurePlaying();
|
||||
}
|
||||
}
|
||||
yield return new WaitForSecondsRealtime(0.25f);
|
||||
}
|
||||
bgmKeepAliveRoutine = null;
|
||||
}
|
||||
|
||||
public void RebindSoon()
|
||||
{
|
||||
if (bindRoutine != null)
|
||||
StopCoroutine(bindRoutine);
|
||||
bindRoutine = StartCoroutine(BindRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator BindRoutine()
|
||||
{
|
||||
// Wait a few frames so scene-internal scripts finish wiring first.
|
||||
yield return null;
|
||||
TryBindAll();
|
||||
yield return null;
|
||||
TryBindAll();
|
||||
yield return null;
|
||||
TryBindAll();
|
||||
bindRoutine = null;
|
||||
}
|
||||
|
||||
private void TryBindAll()
|
||||
{
|
||||
if (!IsInRequiredScene())
|
||||
return;
|
||||
|
||||
DisableDecorativeDifficultyRaycasts();
|
||||
ResolveDifficultyIdDisplayTexts();
|
||||
BindEnterButton();
|
||||
BindAutoPlayToggle();
|
||||
BindDifficultyButtons();
|
||||
RefreshDifficultyIdDisplay(forceInstant: false);
|
||||
}
|
||||
|
||||
private bool IsInRequiredScene()
|
||||
{
|
||||
Scene scene = gameObject.scene;
|
||||
if (!scene.IsValid() || !scene.isLoaded)
|
||||
scene = SceneManager.GetActiveScene();
|
||||
|
||||
return string.IsNullOrEmpty(requiredSceneName) || scene.name == requiredSceneName;
|
||||
}
|
||||
|
||||
private void BindEnterButton()
|
||||
{
|
||||
Button next = FindEnterButton();
|
||||
if (next == null)
|
||||
return;
|
||||
|
||||
if (enterButton != null && enterAction != null && enterButton != next)
|
||||
enterButton.onClick.RemoveListener(enterAction);
|
||||
|
||||
enterButton = next;
|
||||
if (enterAction == null)
|
||||
enterAction = OnEnterGameplayClicked;
|
||||
enterButton.onClick.RemoveListener(enterAction);
|
||||
enterButton.onClick.AddListener(enterAction);
|
||||
}
|
||||
|
||||
private void BindAutoPlayToggle()
|
||||
{
|
||||
Transform tr = FindInCurrentSceneByName(autoPlayToggleName);
|
||||
if (tr == null)
|
||||
return;
|
||||
|
||||
Toggle toggle = tr.GetComponent<Toggle>();
|
||||
if (toggle == null)
|
||||
return;
|
||||
|
||||
if (autoPlayToggle != null && autoPlayToggleAction != null && autoPlayToggle != toggle)
|
||||
autoPlayToggle.onValueChanged.RemoveListener(autoPlayToggleAction);
|
||||
|
||||
autoPlayToggle = toggle;
|
||||
if (autoPlayToggleAction == null)
|
||||
autoPlayToggleAction = OnAutoPlayToggleChanged;
|
||||
|
||||
autoPlayToggle.onValueChanged.RemoveListener(autoPlayToggleAction);
|
||||
autoPlayToggle.onValueChanged.AddListener(autoPlayToggleAction);
|
||||
autoPlayToggle.SetIsOnWithoutNotify(GameConfig.autoPlayEnabled);
|
||||
EnsureSelectableClickable(autoPlayToggle);
|
||||
}
|
||||
|
||||
private void OnAutoPlayToggleChanged(bool isOn)
|
||||
{
|
||||
GameConfig.SetAutoPlayEnabled(isOn);
|
||||
}
|
||||
|
||||
private void BindDifficultyButtons()
|
||||
{
|
||||
foreach (var kv in diffActions)
|
||||
{
|
||||
if (kv.Key != null && kv.Value != null)
|
||||
kv.Key.onClick.RemoveListener(kv.Value);
|
||||
}
|
||||
diffActions.Clear();
|
||||
foreach (var kv in diffToggleActions)
|
||||
{
|
||||
if (kv.Key != null && kv.Value != null)
|
||||
kv.Key.onValueChanged.RemoveListener(kv.Value);
|
||||
}
|
||||
diffToggleActions.Clear();
|
||||
|
||||
Transform root = FindInCurrentSceneByName(difficultiesRootName);
|
||||
if (root == null)
|
||||
return;
|
||||
EnsureAncestorCanvasGroupsAllowInput(root);
|
||||
|
||||
Button[] buttons = root.GetComponentsInChildren<Button>(true);
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
Button btn = buttons[i];
|
||||
if (btn == null)
|
||||
continue;
|
||||
EnsureSelectableClickable(btn);
|
||||
|
||||
UnityAction action = () => StartCoroutine(PlayDifficultyPopDeferred(btn.transform));
|
||||
btn.onClick.RemoveListener(action);
|
||||
btn.onClick.AddListener(action);
|
||||
diffActions[btn] = action;
|
||||
}
|
||||
|
||||
Toggle[] toggles = root.GetComponentsInChildren<Toggle>(true);
|
||||
for (int i = 0; i < toggles.Length; i++)
|
||||
{
|
||||
Toggle tg = toggles[i];
|
||||
if (tg == null)
|
||||
continue;
|
||||
EnsureSelectableClickable(tg);
|
||||
|
||||
UnityAction<bool> action = isOn =>
|
||||
{
|
||||
if (isOn)
|
||||
StartCoroutine(PlayDifficultyPopDeferred(tg.transform));
|
||||
};
|
||||
tg.onValueChanged.RemoveListener(action);
|
||||
tg.onValueChanged.AddListener(action);
|
||||
diffToggleActions[tg] = action;
|
||||
}
|
||||
|
||||
// Explicitly unlock known difficulty buttons that users reported as unclickable.
|
||||
ForceEnableNamedSelectable(root, "HD");
|
||||
ForceEnableNamedSelectable(root, "IN");
|
||||
}
|
||||
|
||||
private IEnumerator PlayDifficultyPopDeferred(Transform buttonRoot)
|
||||
{
|
||||
yield return null;
|
||||
if (buttonRoot == null)
|
||||
yield break;
|
||||
|
||||
RectTransform target = FindDifficultyIndicator(buttonRoot);
|
||||
if (target == null)
|
||||
yield break;
|
||||
|
||||
Vector3 baseScale = target.localScale;
|
||||
CanvasGroup group = target.GetComponent<CanvasGroup>();
|
||||
if (group == null)
|
||||
group = target.gameObject.AddComponent<CanvasGroup>();
|
||||
|
||||
target.DOKill();
|
||||
group.DOKill();
|
||||
|
||||
float baseAlpha = group.alpha > 0f ? group.alpha : 1f;
|
||||
target.localScale = baseScale * Mathf.Max(1.01f, diffPopScaleFrom);
|
||||
group.alpha = 0f;
|
||||
|
||||
Sequence seq = DOTween.Sequence().SetUpdate(true);
|
||||
seq.Append(group.DOFade(baseAlpha, Mathf.Max(0.01f, diffPopFadeInTime)));
|
||||
seq.Join(target.DOScale(baseScale, Mathf.Max(0.01f, diffPopTime)).SetEase(diffPopEase));
|
||||
}
|
||||
|
||||
private RectTransform FindDifficultyIndicator(Transform buttonRoot)
|
||||
{
|
||||
if (buttonRoot == null)
|
||||
return null;
|
||||
|
||||
Transform candidate =
|
||||
FindChildByName(buttonRoot, "selected_bottom") ??
|
||||
FindChildByName(buttonRoot, "selected") ??
|
||||
FindChildByName(buttonRoot, "indicator") ??
|
||||
FindChildByName(buttonRoot, "mark") ??
|
||||
FindChildByName(buttonRoot, "bottom");
|
||||
|
||||
RectTransform rt = candidate as RectTransform;
|
||||
if (rt == null)
|
||||
rt = buttonRoot as RectTransform;
|
||||
return rt;
|
||||
}
|
||||
|
||||
private void OnEnterGameplayClicked()
|
||||
{
|
||||
SongData song = ResolveSelectedSong();
|
||||
if (song == null)
|
||||
{
|
||||
Debug.LogWarning("[Songs_Select] Enter gameplay cancelled: no selected song.");
|
||||
return;
|
||||
}
|
||||
|
||||
int difficulty = Mathf.Clamp(song.thisLevel_selectedDifficultyID, 0, 3);
|
||||
TextAsset chart = song.GetChartFile(difficulty);
|
||||
if (chart == null)
|
||||
{
|
||||
Debug.LogError($"[Songs_Select] Enter gameplay cancelled: chart missing for '{song.songName}' difficulty {difficulty}.");
|
||||
return;
|
||||
}
|
||||
|
||||
SongDataHolder.SelectedSongData = song;
|
||||
BeatmapManager.SetPendingSong(song, difficulty);
|
||||
|
||||
Time.timeScale = 1f;
|
||||
SceneManager.LoadScene(gameplaySceneName, LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private SongData ResolveSelectedSong()
|
||||
{
|
||||
if (SongDataHolder.SelectedSongData != null)
|
||||
return SongDataHolder.SelectedSongData;
|
||||
|
||||
SongButton[] buttons = UnityEngine.Object.FindObjectsByType<SongButton>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
SongButton sb = buttons[i];
|
||||
if (sb == null || sb.gameObject.scene != gameObject.scene)
|
||||
continue;
|
||||
|
||||
if (sb.selected_boarder != null && sb.selected_boarder.color.a > 0.5f)
|
||||
{
|
||||
SongData selected = sb.thisSong_so as SongData;
|
||||
if (selected != null)
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Button FindEnterButton()
|
||||
{
|
||||
Scene scene = gameObject.scene;
|
||||
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, enterButtonName, System.StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (!HasAncestorNamed(t, enterButtonParentName))
|
||||
continue;
|
||||
|
||||
Button btn = t.GetComponent<Button>();
|
||||
if (btn != null)
|
||||
return btn;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool HasAncestorNamed(Transform tr, string name)
|
||||
{
|
||||
Transform p = tr != null ? tr.parent : null;
|
||||
while (p != null)
|
||||
{
|
||||
if (string.Equals(p.name, name, System.StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
p = p.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Transform FindInCurrentSceneByName(string objectName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(objectName))
|
||||
return null;
|
||||
|
||||
Scene scene = gameObject.scene;
|
||||
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 Transform FindChildByName(Transform root, string name)
|
||||
{
|
||||
if (root == null || string.IsNullOrEmpty(name))
|
||||
return null;
|
||||
|
||||
Transform[] children = root.GetComponentsInChildren<Transform>(true);
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
Transform child = children[i];
|
||||
if (child != null && string.Equals(child.name, name, System.StringComparison.OrdinalIgnoreCase))
|
||||
return child;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void EnsureSelectableClickable(Selectable selectable)
|
||||
{
|
||||
if (selectable == null) return;
|
||||
selectable.interactable = true;
|
||||
|
||||
if (selectable.targetGraphic != null)
|
||||
selectable.targetGraphic.raycastTarget = true;
|
||||
|
||||
EnsureAncestorCanvasGroupsAllowInput(selectable.transform);
|
||||
}
|
||||
|
||||
private void EnsureAncestorCanvasGroupsAllowInput(Transform leaf)
|
||||
{
|
||||
Transform t = leaf;
|
||||
int safety = 0;
|
||||
while (t != null && safety++ < 16)
|
||||
{
|
||||
CanvasGroup cg = t.GetComponent<CanvasGroup>();
|
||||
if (cg != null && cg.alpha > 0.001f)
|
||||
{
|
||||
cg.interactable = true;
|
||||
cg.blocksRaycasts = true;
|
||||
}
|
||||
t = t.parent;
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceEnableNamedSelectable(Transform root, string objectName)
|
||||
{
|
||||
if (root == null || string.IsNullOrEmpty(objectName)) return;
|
||||
Transform target = FindChildByName(root, objectName);
|
||||
if (target == null) return;
|
||||
|
||||
Button btn = target.GetComponent<Button>();
|
||||
if (btn != null) EnsureSelectableClickable(btn);
|
||||
|
||||
Toggle tg = target.GetComponent<Toggle>();
|
||||
if (tg != null) EnsureSelectableClickable(tg);
|
||||
}
|
||||
|
||||
private void DisableDecorativeDifficultyRaycasts()
|
||||
{
|
||||
Scene scene = gameObject.scene;
|
||||
Text[] allTexts = UnityEngine.Object.FindObjectsByType<Text>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < allTexts.Length; i++)
|
||||
{
|
||||
Text txt = allTexts[i];
|
||||
if (txt == null || txt.gameObject.scene != scene) continue;
|
||||
string n = txt.name;
|
||||
if (!string.Equals(n, "difficultyID", System.StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(n, "difficultyIDGround", System.StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
txt.raycastTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveDifficultyIdDisplayTexts()
|
||||
{
|
||||
difficultyIdDisplayTexts.Clear();
|
||||
|
||||
Scene scene = gameObject.scene;
|
||||
Text[] allTexts = UnityEngine.Object.FindObjectsByType<Text>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < allTexts.Length; i++)
|
||||
{
|
||||
Text txt = allTexts[i];
|
||||
if (txt == null || txt.gameObject.scene != scene)
|
||||
continue;
|
||||
|
||||
string n = txt.name;
|
||||
if (!string.Equals(n, difficultyIdName, System.StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(n, difficultyIdGroundName, System.StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
txt.raycastTarget = false;
|
||||
difficultyIdDisplayTexts.Add(txt);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshDifficultyIdDisplay(bool forceInstant)
|
||||
{
|
||||
int target = GetCurrentDifficultyDisplayValue();
|
||||
|
||||
if (difficultyIdDisplayTexts.Count == 0)
|
||||
ResolveDifficultyIdDisplayTexts();
|
||||
if (difficultyIdDisplayTexts.Count == 0)
|
||||
return;
|
||||
|
||||
if (forceInstant || difficultyIdDisplayed == int.MinValue)
|
||||
{
|
||||
difficultyIdDisplayed = target;
|
||||
ApplyDifficultyIdDisplayValue(target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == difficultyIdDisplayed)
|
||||
{
|
||||
ApplyDifficultyIdDisplayValue(target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (difficultyIdRollCoroutine != null)
|
||||
{
|
||||
StopCoroutine(difficultyIdRollCoroutine);
|
||||
difficultyIdRollCoroutine = null;
|
||||
}
|
||||
|
||||
difficultyIdRollCoroutine = StartCoroutine(RollDifficultyIdDisplay(difficultyIdDisplayed, target));
|
||||
}
|
||||
|
||||
private int GetCurrentDifficultyDisplayValue()
|
||||
{
|
||||
SongData song = ResolveSelectedSong();
|
||||
if (song == null)
|
||||
return 0;
|
||||
|
||||
int difficulty = Mathf.Clamp(song.thisLevel_selectedDifficultyID, 0, 3);
|
||||
float level = GetDifficultyLevel(song, difficulty);
|
||||
int rounded = Mathf.RoundToInt(level);
|
||||
return Mathf.Max(0, rounded);
|
||||
}
|
||||
|
||||
private float GetDifficultyLevel(SongData song, int difficultyId)
|
||||
{
|
||||
if (song == null)
|
||||
return 0f;
|
||||
|
||||
if (song.chartFiles != null)
|
||||
{
|
||||
for (int i = 0; i < song.chartFiles.Count; i++)
|
||||
{
|
||||
ChartFileEntry entry = song.chartFiles[i];
|
||||
if (entry == null || entry.difficulty != difficultyId)
|
||||
continue;
|
||||
return entry.difficultyLEVEL;
|
||||
}
|
||||
}
|
||||
|
||||
if (song.difficultyNumberMap != null && song.difficultyNumberMap.ContainsKey(difficultyId))
|
||||
return song.difficultyNumberMap[difficultyId];
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
private void ApplyDifficultyIdDisplayValue(int value)
|
||||
{
|
||||
string text = value.ToString();
|
||||
for (int i = 0; i < difficultyIdDisplayTexts.Count; i++)
|
||||
{
|
||||
Text t = difficultyIdDisplayTexts[i];
|
||||
if (t == null)
|
||||
continue;
|
||||
|
||||
t.text = text;
|
||||
t.raycastTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator RollDifficultyIdDisplay(int from, int target)
|
||||
{
|
||||
int distance = Mathf.Abs(target - from);
|
||||
if (distance <= 0)
|
||||
{
|
||||
difficultyIdDisplayed = target;
|
||||
ApplyDifficultyIdDisplayValue(target);
|
||||
difficultyIdRollCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
int dir = target > from ? 1 : -1;
|
||||
int current = from;
|
||||
for (int i = 1; i <= distance; i++)
|
||||
{
|
||||
current += dir;
|
||||
difficultyIdDisplayed = current;
|
||||
ApplyDifficultyIdDisplayValue(current);
|
||||
|
||||
float progress = distance <= 1 ? 1f : (float)i / distance;
|
||||
float eased = progress * progress; // fast first, then slow
|
||||
float delay = Mathf.Lerp(difficultyIdStepDelayFast, difficultyIdStepDelaySlow, eased);
|
||||
if (difficultyIdUseUnscaledTime)
|
||||
yield return new WaitForSecondsRealtime(Mathf.Max(0.005f, delay));
|
||||
else
|
||||
yield return new WaitForSeconds(Mathf.Max(0.005f, delay));
|
||||
}
|
||||
|
||||
difficultyIdDisplayed = target;
|
||||
ApplyDifficultyIdDisplayValue(target);
|
||||
difficultyIdRollCoroutine = null;
|
||||
}
|
||||
|
||||
private void Unbind()
|
||||
{
|
||||
if (enterButton != null && enterAction != null)
|
||||
enterButton.onClick.RemoveListener(enterAction);
|
||||
if (autoPlayToggle != null && autoPlayToggleAction != null)
|
||||
autoPlayToggle.onValueChanged.RemoveListener(autoPlayToggleAction);
|
||||
|
||||
foreach (var kv in diffActions)
|
||||
{
|
||||
if (kv.Key != null && kv.Value != null)
|
||||
kv.Key.onClick.RemoveListener(kv.Value);
|
||||
}
|
||||
diffActions.Clear();
|
||||
foreach (var kv in diffToggleActions)
|
||||
{
|
||||
if (kv.Key != null && kv.Value != null)
|
||||
kv.Key.onValueChanged.RemoveListener(kv.Value);
|
||||
}
|
||||
diffToggleActions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static class UI_SongsSelect_RuntimeFeaturesBootstrap
|
||||
{
|
||||
private const string SceneName = "Songs_Select";
|
||||
private const string HostName = "__SongsSelectRuntimeFeaturesHost";
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Init()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
|
||||
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
if (scene.name != SceneName)
|
||||
return;
|
||||
|
||||
GameObject host = FindHostInScene(scene, HostName);
|
||||
if (host == null)
|
||||
{
|
||||
host = new GameObject(HostName);
|
||||
SceneManager.MoveGameObjectToScene(host, scene);
|
||||
}
|
||||
|
||||
UI_SongsSelect_RuntimeFeatures features = host.GetComponent<UI_SongsSelect_RuntimeFeatures>();
|
||||
if (features == null)
|
||||
features = host.AddComponent<UI_SongsSelect_RuntimeFeatures>();
|
||||
|
||||
features.RebindSoon();
|
||||
}
|
||||
|
||||
private static GameObject FindHostInScene(Scene scene, string hostName)
|
||||
{
|
||||
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 == hostName)
|
||||
return t.gameObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd80adc518da2434faf6f1d4dfb33841
|
||||
Reference in New Issue
Block a user