Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/GameplayTrackStartIntroAnimator.cs
T

970 lines
33 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/// <summary>
/// Gameplay start-time track entrance animation controller.
/// Triggered by readyLetsGo when 3-2-1-Go starts.
/// Uses unscaled time so it can run while Time.timeScale == 0.
/// </summary>
public class GameplayTrackStartIntroAnimator : MonoBehaviour
{
[Header("Scope")]
[SerializeField] private string requiredSceneName = "gamePlay_gamePlay";
[SerializeField] private bool playOnlyOncePerScene = true;
[Header("Timing")]
[SerializeField] private float stepStagger = 0.06f;
[SerializeField] private float moveDuration = 0.30f;
[SerializeField] private float flashDuration = 0.26f;
[SerializeField] private int flashTimes = 2;
[SerializeField] private float phaseGap = 0.06f;
[SerializeField] private float laneRectFlashDuration = 0.18f;
[SerializeField] private float allyFlashDuration = 0.16f;
[SerializeField] private float hudFlashDuration = 0.14f;
[SerializeField] private bool keepHiddenBeforeStart = true;
[Header("Preset Values")]
[SerializeField] private float keyDownStartY = -30f;
[SerializeField] private float keyDownTargetY = 5.3f;
[SerializeField] private float keyDownStartRotY = -30f;
[SerializeField] private float keyDownTargetRotY = 0f;
[SerializeField] private float gameObjectStartY = -10f;
[SerializeField] private float gameObjectTargetY = -4.523f;
[SerializeField] private float allyStartRotY = 130.8f;
[SerializeField] private float allyTargetRotY = 0f;
[Header("Ally Turn")]
[Tooltip("Use pseudo Y-turn on UI cards to avoid color/mask artifacts caused by true Y rotation on complex UI roots.")]
[SerializeField] private bool allyUsePseudoYTurnForUI = true;
[SerializeField] private float allyPseudoStartScaleX = 0.68f;
private bool playedOnce;
private bool refsCached;
private bool preStartPrepared;
private Coroutine playRoutine;
private Transform keyDown;
private Transform colorLineStatic;
private Transform enemyRoot;
private Transform progressLine;
private Transform score;
private Transform pause;
private Transform bgReplace;
private Transform totalEnemyTop;
private Transform totalEnemyFade;
private Transform enemyImg;
private Transform enemySpineSystem;
private Transform enemyListBall;
private Transform enemyStatics;
private readonly List<Transform> keyDownRects = new List<Transform>();
private readonly List<Transform> gameObjectRects = new List<Transform>();
private readonly List<Transform> colorRects = new List<Transform>();
private readonly List<Transform> allies = new List<Transform>();
private readonly Dictionary<Graphic, float> baseGraphicAlpha = new Dictionary<Graphic, float>();
private readonly Dictionary<SpriteRenderer, float> baseSpriteAlpha = new Dictionary<SpriteRenderer, float>();
private readonly Dictionary<CanvasGroup, float> baseCanvasGroupAlpha = new Dictionary<CanvasGroup, float>();
private readonly Dictionary<Transform, Vector3> baseLocalScale = new Dictionary<Transform, Vector3>();
private readonly Dictionary<Transform, CanvasGroup> rootCanvasGroups = new Dictionary<Transform, CanvasGroup>();
private void Awake()
{
if (!IsInRequiredScene()) return;
CacheRefsIfNeeded();
PreparePreStartState();
}
private void Start()
{
if (!IsInRequiredScene()) return;
StartCoroutine(PreparePreStartStateNextFrame());
}
private void LateUpdate()
{
if (!keepHiddenBeforeStart) return;
if (!IsInRequiredScene()) return;
if (playedOnce || playRoutine != null) return;
CacheRefsIfNeeded();
// Keep hidden state stable before player presses start.
PreparePreStartState(force: true);
}
public void PlayIfNeeded()
{
if (!IsInRequiredScene()) return;
if (playRoutine != null) return;
if (playOnlyOncePerScene && playedOnce) return;
CacheRefsIfNeeded();
playRoutine = StartCoroutine(PlayRoutine());
}
private IEnumerator PreparePreStartStateNextFrame()
{
// Re-apply after one frame so late UI initializers don't bring these elements visible before start.
yield return null;
if (playRoutine == null && !playedOnce)
{
PreparePreStartState(force: true);
}
}
public void ResetPlayState()
{
playedOnce = false;
if (playRoutine != null)
{
StopCoroutine(playRoutine);
playRoutine = null;
}
CacheRefsIfNeeded();
PreparePreStartState(force: true);
}
private bool IsInRequiredScene()
{
if (string.IsNullOrWhiteSpace(requiredSceneName)) return true;
Scene active = SceneManager.GetActiveScene();
return string.Equals(active.name, requiredSceneName, System.StringComparison.OrdinalIgnoreCase);
}
private void CacheRefsIfNeeded()
{
if (refsCached) return;
keyDown = FindSceneTransformByName("KEY_down");
colorLineStatic = FindSceneTransformByName("colorLineStatic");
enemyRoot = FindSceneTransformByName("enemyObject_toWiggle");
progressLine = FindSceneTransformByName("progressLine");
score = FindSceneTransformByName("score");
pause = FindPauseInScene();
bgReplace = FindSceneTransformByName("bg_replace");
totalEnemyTop = FindSceneTransformByName("TOTALenemy_healthbar_top");
totalEnemyFade = FindSceneTransformByName("TOTALenemy_healthbar_fade");
enemyImg = FindSceneTransformByNameUnder("img", enemyRoot);
enemySpineSystem = FindSceneTransformByNameUnder("spineSystem", enemyImg != null ? enemyImg : enemyRoot);
enemyListBall = FindSceneTransformByNameUnder("enemyListBall", enemyRoot);
enemyStatics = FindSceneTransformByNameUnder("statics", enemyRoot);
keyDownRects.Clear();
if (keyDown != null)
keyDownRects.AddRange(CollectDescendantsByNameContains(keyDown, "sdw 3"));
keyDownRects.Sort((a, b) => a.localPosition.x.CompareTo(b.localPosition.x));
FilterLaneRects(keyDownRects, keyDownTargetY, 5);
gameObjectRects.Clear();
List<Transform> byNamedRoots = CollectSdw3UnderNamedRoots("GameObject", keyDown);
if (byNamedRoots.Count > 0)
gameObjectRects.AddRange(byNamedRoots);
else
gameObjectRects.AddRange(CollectGameObjectSdw3Excluding(keyDown));
gameObjectRects.Sort((a, b) => a.localPosition.x.CompareTo(b.localPosition.x));
FilterLaneRects(gameObjectRects, gameObjectTargetY, 5);
colorRects.Clear();
colorRects.AddRange(CollectColorRects(colorLineStatic));
colorRects.Sort((a, b) => a.GetSiblingIndex().CompareTo(b.GetSiblingIndex()));
allies.Clear();
for (int i = 1; i <= 5; i++)
{
Transform ally = FindSceneTransformByName($"ally_0{i}");
if (ally != null) allies.Add(ally);
}
allies.Sort((a, b) => a.name.CompareTo(b.name));
refsCached = true;
}
private void PreparePreStartState(bool force = false)
{
if (preStartPrepared && !force) return;
if (bgReplace != null) SetAlphaNormalized(bgReplace, 0f, true);
if (colorLineStatic != null) SetAlphaNormalized(colorLineStatic, 0f, true);
if (enemyRoot != null) SetAlphaNormalized(enemyRoot, 0f, true);
if (enemyListBall != null) SetAlphaNormalized(enemyListBall, 0f, true);
if (enemyStatics != null) SetAlphaNormalized(enemyStatics, 0f, true);
if (enemySpineSystem != null) enemySpineSystem.gameObject.SetActive(false);
if (progressLine != null) SetAlphaNormalized(progressLine, 0f, true);
if (score != null) SetAlphaNormalized(score, 0f, true);
if (pause != null) SetAlphaNormalized(pause, 0f, true);
for (int i = 0; i < colorRects.Count; i++)
{
if (colorRects[i] == null) continue;
SetAlphaNormalized(colorRects[i], 0f, true);
}
for (int i = 0; i < keyDownRects.Count; i++)
{
Transform t = keyDownRects[i];
if (t == null) continue;
SetAlphaNormalized(t, 0f, true);
SetLocalY(t, keyDownStartY);
SetLocalRotY(t, keyDownStartRotY);
}
for (int i = 0; i < gameObjectRects.Count; i++)
{
Transform t = gameObjectRects[i];
if (t == null) continue;
SetAlphaNormalized(t, 0f, true);
SetLocalY(t, gameObjectStartY);
}
for (int i = 0; i < allies.Count; i++)
{
Transform ally = allies[i];
if (ally == null) continue;
SetRootCanvasAlpha(ally, 0f);
if (allyUsePseudoYTurnForUI)
{
SetPseudoTurnScaleX(ally, allyPseudoStartScaleX);
SetLocalRotY(ally, allyTargetRotY);
}
else
{
SetLocalRotY(ally, allyStartRotY);
}
}
if (totalEnemyTop != null) SetScaleY(totalEnemyTop, 0f);
if (totalEnemyFade != null) SetScaleY(totalEnemyFade, 0f);
preStartPrepared = true;
}
private IEnumerator PlayRoutine()
{
PreparePreStartState();
// 1) bg_replace flash in, then KEY_down sdw3 move/rotate in sequence
if (bgReplace != null)
{
yield return StartCoroutine(FlashIn(bgReplace, flashTimes, flashDuration, true));
}
yield return StartCoroutine(AnimateRectsYAndRotY(
keyDownRects,
keyDownStartY,
keyDownTargetY,
keyDownStartRotY,
keyDownTargetRotY,
moveDuration,
stepStagger));
yield return WaitRealtime(phaseGap);
// 2) GameObject sdw3 move in sequence
yield return StartCoroutine(AnimateRectsYOnlyWithFlash(
gameObjectRects,
gameObjectStartY,
gameObjectTargetY,
moveDuration,
stepStagger,
laneRectFlashDuration));
// 3) colorLineStatic enters
// 4) enemy enters (parallel)
// 5) progressLine enters (parallel)
// 6) allies then score/pause (parallel)
Coroutine colorStep = StartCoroutine(FlashInSequence(colorRects, flashTimes, flashDuration, stepStagger));
Coroutine enemyStep = StartCoroutine(PlayEnemyStep(enemyRoot, enemyListBall, enemyStatics, enemySpineSystem, totalEnemyTop, totalEnemyFade));
Coroutine progressStep = StartCoroutine(FlashIn(progressLine, flashTimes, flashDuration, true));
Coroutine alliesStep = StartCoroutine(PlayAlliesAndHudStep(allies, score, pause));
if (colorStep != null) yield return colorStep;
if (enemyStep != null) yield return enemyStep;
if (progressStep != null) yield return progressStep;
if (alliesStep != null) yield return alliesStep;
playedOnce = true;
playRoutine = null;
}
private IEnumerator PlayEnemyStep(
Transform enemyRootTr,
Transform enemyListBallTr,
Transform enemyStaticsTr,
Transform enemySpineSystemTr,
Transform totalEnemyTopTr,
Transform totalEnemyFadeTr)
{
if (enemyRootTr != null)
yield return StartCoroutine(FlashIn(enemyRootTr, flashTimes, flashDuration, true));
if (enemyListBallTr != null)
yield return StartCoroutine(FlashIn(enemyListBallTr, flashTimes, flashDuration, true));
if (enemyStaticsTr != null)
yield return StartCoroutine(FlashIn(enemyStaticsTr, flashTimes, flashDuration, true));
if (enemySpineSystemTr != null)
yield return StartCoroutine(FlashInByActiveToggle(enemySpineSystemTr, flashTimes, flashDuration));
if (totalEnemyTopTr != null) SetScaleY(totalEnemyTopTr, 0f);
if (totalEnemyFadeTr != null) SetScaleY(totalEnemyFadeTr, 0f);
float elapsed = 0f;
float dur = Mathf.Max(0.01f, moveDuration);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
if (totalEnemyTopTr != null) SetScaleY(totalEnemyTopTr, Mathf.LerpUnclamped(0f, 1f, t));
if (totalEnemyFadeTr != null) SetScaleY(totalEnemyFadeTr, Mathf.LerpUnclamped(0f, 1f, t));
yield return null;
}
if (totalEnemyTopTr != null) SetScaleY(totalEnemyTopTr, 1f);
if (totalEnemyFadeTr != null) SetScaleY(totalEnemyFadeTr, 1f);
}
private IEnumerator PlayAlliesAndHudStep(List<Transform> allyList, Transform scoreTr, Transform pauseTr)
{
for (int i = 0; i < allyList.Count; i++)
{
Transform ally = allyList[i];
if (ally == null) continue;
yield return StartCoroutine(FlashInCanvasGroup(ally, flashTimes, allyFlashDuration));
if (allyUsePseudoYTurnForUI)
yield return StartCoroutine(AnimatePseudoTurnScaleX(ally, allyPseudoStartScaleX, 1f, moveDuration));
else
yield return StartCoroutine(AnimateRotY(ally, allyStartRotY, allyTargetRotY, moveDuration));
if (i < allyList.Count - 1 && stepStagger > 0f)
yield return WaitRealtime(stepStagger);
}
if (scoreTr != null)
yield return StartCoroutine(FlashIn(scoreTr, flashTimes, hudFlashDuration, true));
if (pauseTr != null)
yield return StartCoroutine(FlashIn(pauseTr, flashTimes, hudFlashDuration, true));
}
private IEnumerator FlashInSequence(List<Transform> list, int flashes, float duration, float stagger)
{
if (list == null || list.Count == 0) yield break;
for (int i = 0; i < list.Count; i++)
{
Transform t = list[i];
if (t == null) continue;
yield return StartCoroutine(FlashIn(t, flashes, duration, true));
if (i < list.Count - 1 && stagger > 0f)
yield return WaitRealtime(stagger);
}
}
private IEnumerator AnimateRectsYAndRotY(List<Transform> list, float fromY, float toY, float fromRotY, float toRotY, float duration, float stagger)
{
if (list == null || list.Count == 0) yield break;
for (int i = 0; i < list.Count; i++)
{
Transform t = list[i];
if (t == null) continue;
SetAlphaNormalized(t, 1f, true);
SetLocalY(t, fromY);
SetLocalRotY(t, fromRotY);
StartCoroutine(AnimateYAndRotY(t, fromY, toY, fromRotY, toRotY, duration));
if (i < list.Count - 1 && stagger > 0f)
yield return WaitRealtime(stagger);
}
yield return WaitRealtime(duration);
}
private IEnumerator AnimateRectsYOnly(List<Transform> list, float fromY, float toY, float duration, float stagger)
{
if (list == null || list.Count == 0) yield break;
for (int i = 0; i < list.Count; i++)
{
Transform t = list[i];
if (t == null) continue;
SetAlphaNormalized(t, 1f, true);
SetLocalY(t, fromY);
StartCoroutine(AnimateYOnly(t, fromY, toY, duration));
if (i < list.Count - 1 && stagger > 0f)
yield return WaitRealtime(stagger);
}
yield return WaitRealtime(duration);
}
private IEnumerator AnimateRectsYOnlyWithFlash(List<Transform> list, float fromY, float toY, float duration, float stagger, float flashDur)
{
if (list == null || list.Count == 0) yield break;
for (int i = 0; i < list.Count; i++)
{
Transform t = list[i];
if (t == null) continue;
SetAlphaNormalized(t, 0f, true);
SetLocalY(t, fromY);
StartCoroutine(FlashIn(t, flashTimes, flashDur, true));
StartCoroutine(AnimateYOnly(t, fromY, toY, duration));
if (i < list.Count - 1 && stagger > 0f)
yield return WaitRealtime(stagger);
}
yield return WaitRealtime(Mathf.Max(duration, flashDur));
}
private IEnumerator AnimateYAndRotY(Transform t, float fromY, float toY, float fromRotY, float toRotY, float duration)
{
if (t == null) yield break;
float elapsed = 0f;
float dur = Mathf.Max(0.01f, duration);
float fromRot = NormalizeEulerY(fromRotY);
float toRot = NormalizeEulerY(toRotY);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
float k = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
SetLocalY(t, Mathf.LerpUnclamped(fromY, toY, k));
SetLocalRotY(t, Mathf.LerpAngle(fromRot, toRot, k));
yield return null;
}
SetLocalY(t, toY);
SetLocalRotY(t, toRotY);
}
private IEnumerator AnimateYOnly(Transform t, float fromY, float toY, float duration)
{
if (t == null) yield break;
float elapsed = 0f;
float dur = Mathf.Max(0.01f, duration);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
float k = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
SetLocalY(t, Mathf.LerpUnclamped(fromY, toY, k));
yield return null;
}
SetLocalY(t, toY);
}
private IEnumerator AnimateRotY(Transform t, float fromY, float toY, float duration)
{
if (t == null) yield break;
float elapsed = 0f;
float dur = Mathf.Max(0.01f, duration);
float from = NormalizeEulerY(fromY);
float to = NormalizeEulerY(toY);
SetLocalRotY(t, from);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
float k = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
SetLocalRotY(t, Mathf.LerpAngle(from, to, k));
yield return null;
}
SetLocalRotY(t, to);
}
private IEnumerator AnimatePseudoTurnScaleX(Transform t, float fromScaleX, float toScaleX, float duration)
{
if (t == null) yield break;
float elapsed = 0f;
float dur = Mathf.Max(0.01f, duration);
SetPseudoTurnScaleX(t, fromScaleX);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
float k = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
float sx = Mathf.LerpUnclamped(fromScaleX, toScaleX, k);
SetPseudoTurnScaleX(t, sx);
yield return null;
}
SetPseudoTurnScaleX(t, toScaleX);
}
private IEnumerator FlashIn(Transform target, int flashes, float duration, bool includeChildren)
{
if (target == null) yield break;
int count = Mathf.Max(1, flashes);
float total = Mathf.Max(0.04f, duration);
float unit = total / (count * 2f);
SetAlphaNormalized(target, 0f, includeChildren);
for (int i = 0; i < count; i++)
{
SetAlphaNormalized(target, 1f, includeChildren);
yield return WaitRealtime(unit);
if (i < count - 1)
{
SetAlphaNormalized(target, 0f, includeChildren);
yield return WaitRealtime(unit);
}
}
SetAlphaNormalized(target, 1f, includeChildren);
}
private IEnumerator FlashInByActiveToggle(Transform target, int flashes, float duration)
{
if (target == null) yield break;
int count = Mathf.Max(1, flashes);
float total = Mathf.Max(0.04f, duration);
float unit = total / (count * 2f);
if (target.gameObject.activeSelf)
target.gameObject.SetActive(false);
for (int i = 0; i < count; i++)
{
target.gameObject.SetActive(true);
yield return WaitRealtime(unit);
if (i < count - 1)
{
target.gameObject.SetActive(false);
yield return WaitRealtime(unit);
}
}
target.gameObject.SetActive(true);
}
private static IEnumerator WaitRealtime(float duration)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}
}
private static float EaseOutCubic(float t)
{
float inv = 1f - t;
return 1f - inv * inv * inv;
}
private static float NormalizeEulerY(float y)
{
float v = y % 360f;
if (v < 0f) v += 360f;
return v;
}
private void SetPseudoTurnScaleX(Transform t, float scaleX)
{
if (t == null) return;
Vector3 baseScale = GetBaseLocalScale(t);
Vector3 s = t.localScale;
s.x = Mathf.Max(0.01f, baseScale.x * scaleX);
s.y = baseScale.y;
s.z = baseScale.z;
t.localScale = s;
}
private Vector3 GetBaseLocalScale(Transform t)
{
if (t == null) return Vector3.one;
if (!baseLocalScale.TryGetValue(t, out Vector3 s))
{
s = t.localScale;
baseLocalScale[t] = s;
}
return s;
}
private static void SetScaleY(Transform t, float y)
{
if (t == null) return;
Vector3 s = t.localScale;
s.y = y;
t.localScale = s;
}
private static void SetLocalY(Transform t, float y)
{
if (t == null) return;
Vector3 p = t.localPosition;
p.y = y;
t.localPosition = p;
}
private static void SetLocalRotY(Transform t, float y)
{
if (t == null) return;
Vector3 e = t.localEulerAngles;
e.y = NormalizeEulerY(y);
t.localEulerAngles = e;
}
private void SetAlphaNormalized(Transform root, float normalized, bool includeChildren)
{
if (root == null) return;
normalized = Mathf.Clamp01(normalized);
if (includeChildren)
{
CanvasGroup[] cgs = root.GetComponentsInChildren<CanvasGroup>(true);
for (int i = 0; i < cgs.Length; i++)
{
CanvasGroup cg = cgs[i];
if (cg == null) continue;
float baseA = GetBaseAlpha(cg);
cg.alpha = baseA * normalized;
}
Graphic[] graphics = root.GetComponentsInChildren<Graphic>(true);
for (int i = 0; i < graphics.Length; i++)
{
Graphic g = graphics[i];
if (g == null) continue;
float baseA = GetBaseAlpha(g);
Color c = g.color;
c.a = baseA * normalized;
g.color = c;
}
SpriteRenderer[] sprites = root.GetComponentsInChildren<SpriteRenderer>(true);
for (int i = 0; i < sprites.Length; i++)
{
SpriteRenderer sr = sprites[i];
if (sr == null) continue;
float baseA = GetBaseAlpha(sr);
Color c = sr.color;
c.a = baseA * normalized;
sr.color = c;
}
}
else
{
CanvasGroup cg = root.GetComponent<CanvasGroup>();
if (cg != null)
{
float baseA = GetBaseAlpha(cg);
cg.alpha = baseA * normalized;
}
Graphic g = root.GetComponent<Graphic>();
if (g != null)
{
float baseA = GetBaseAlpha(g);
Color c = g.color;
c.a = baseA * normalized;
g.color = c;
}
SpriteRenderer sr = root.GetComponent<SpriteRenderer>();
if (sr != null)
{
float baseA = GetBaseAlpha(sr);
Color c = sr.color;
c.a = baseA * normalized;
sr.color = c;
}
}
}
private float GetBaseAlpha(CanvasGroup cg)
{
if (!baseCanvasGroupAlpha.TryGetValue(cg, out float a))
{
a = cg.alpha;
if (a <= 0.001f) a = 1f;
baseCanvasGroupAlpha[cg] = a;
}
return a;
}
private CanvasGroup GetOrAddRootCanvasGroup(Transform root)
{
if (root == null) return null;
if (!rootCanvasGroups.TryGetValue(root, out CanvasGroup cg) || cg == null)
{
cg = root.GetComponent<CanvasGroup>();
if (cg == null) cg = root.gameObject.AddComponent<CanvasGroup>();
rootCanvasGroups[root] = cg;
}
return cg;
}
private void SetRootCanvasAlpha(Transform root, float normalized)
{
if (root == null) return;
CanvasGroup cg = GetOrAddRootCanvasGroup(root);
if (cg == null) return;
float n = Mathf.Clamp01(normalized);
// Use direct alpha for root group to avoid touching child graphic colors.
cg.alpha = n;
}
private IEnumerator FlashInCanvasGroup(Transform root, int flashes, float duration)
{
if (root == null) yield break;
int count = Mathf.Max(1, flashes);
float total = Mathf.Max(0.04f, duration);
float unit = total / (count * 2f);
SetRootCanvasAlpha(root, 0f);
for (int i = 0; i < count; i++)
{
SetRootCanvasAlpha(root, 1f);
yield return WaitRealtime(unit);
if (i < count - 1)
{
SetRootCanvasAlpha(root, 0f);
yield return WaitRealtime(unit);
}
}
SetRootCanvasAlpha(root, 1f);
}
private float GetBaseAlpha(Graphic g)
{
if (!baseGraphicAlpha.TryGetValue(g, out float a))
{
a = g.color.a;
if (a <= 0.001f) a = 1f;
baseGraphicAlpha[g] = a;
}
return a;
}
private float GetBaseAlpha(SpriteRenderer sr)
{
if (!baseSpriteAlpha.TryGetValue(sr, out float a))
{
a = sr.color.a;
if (a <= 0.001f) a = 1f;
baseSpriteAlpha[sr] = a;
}
return a;
}
private List<Transform> CollectColorRects(Transform colorLineStaticRoot)
{
List<Transform> list = new List<Transform>();
if (colorLineStaticRoot == null) return list;
List<Transform> byEnglish = CollectDescendantsByNameContains(colorLineStaticRoot, "Rectangle 6");
if (byEnglish.Count > 0) return byEnglish;
for (int i = 0; i < colorLineStaticRoot.childCount; i++)
{
Transform child = colorLineStaticRoot.GetChild(i);
if (child == null) continue;
if (child.GetComponentInChildren<Graphic>(true) != null || child.GetComponentInChildren<SpriteRenderer>(true) != null)
{
list.Add(child);
}
}
return list;
}
private List<Transform> CollectGameObjectSdw3Excluding(Transform keyDownRoot)
{
List<Transform> list = new List<Transform>();
HashSet<int> seen = new HashSet<int>();
Transform[] all = GetAllSceneTransforms();
for (int i = 0; i < all.Length; i++)
{
Transform t = all[i];
if (t == null) continue;
if (t.name == null || t.name.IndexOf("sdw 3", System.StringComparison.OrdinalIgnoreCase) < 0) continue;
if (keyDownRoot != null && t.IsChildOf(keyDownRoot)) continue;
if (!seen.Add(t.GetInstanceID())) continue;
list.Add(t);
}
return list;
}
private List<Transform> CollectSdw3UnderNamedRoots(string rootName, Transform excludeRoot)
{
List<Transform> list = new List<Transform>();
if (string.IsNullOrEmpty(rootName)) return list;
HashSet<int> seen = new HashSet<int>();
Transform[] all = GetAllSceneTransforms();
for (int i = 0; i < all.Length; i++)
{
Transform root = all[i];
if (root == null) continue;
if (!string.Equals(root.name, rootName, System.StringComparison.OrdinalIgnoreCase)) continue;
if (excludeRoot != null && root.IsChildOf(excludeRoot)) continue;
List<Transform> descendants = CollectDescendantsByNameContains(root, "sdw 3");
for (int j = 0; j < descendants.Count; j++)
{
Transform t = descendants[j];
if (t == null) continue;
if (!seen.Add(t.GetInstanceID())) continue;
list.Add(t);
}
}
return list;
}
private static void FilterLaneRects(List<Transform> list, float targetY, int expectedCount)
{
if (list == null) return;
list.RemoveAll(t => t == null);
if (list.Count == 0) return;
const float yTolerance = 2.5f;
List<Transform> nearTarget = new List<Transform>();
for (int i = 0; i < list.Count; i++)
{
Transform t = list[i];
if (Mathf.Abs(t.localPosition.y - targetY) <= yTolerance)
nearTarget.Add(t);
}
List<Transform> selected = (nearTarget.Count >= expectedCount) ? nearTarget : new List<Transform>(list);
selected.Sort((a, b) =>
{
float da = Mathf.Abs(a.localPosition.y - targetY);
float db = Mathf.Abs(b.localPosition.y - targetY);
int yCmp = da.CompareTo(db);
if (yCmp != 0) return yCmp;
return a.localPosition.x.CompareTo(b.localPosition.x);
});
if (expectedCount > 0 && selected.Count > expectedCount)
selected = selected.GetRange(0, expectedCount);
selected.Sort((a, b) => a.localPosition.x.CompareTo(b.localPosition.x));
list.Clear();
list.AddRange(selected);
}
private static List<Transform> CollectDescendantsByNameContains(Transform root, string keyword)
{
List<Transform> list = new List<Transform>();
if (root == null || string.IsNullOrEmpty(keyword)) return list;
Transform[] all = root.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < all.Length; i++)
{
Transform child = all[i];
if (child == null || child == root) continue;
if (child.name != null && child.name.IndexOf(keyword, System.StringComparison.OrdinalIgnoreCase) >= 0)
{
list.Add(child);
}
}
return list;
}
private Transform FindPauseInScene()
{
Transform[] all = GetAllSceneTransforms();
Transform fallback = null;
for (int i = 0; i < all.Length; i++)
{
Transform t = all[i];
if (t == null || !string.Equals(t.name, "Pause", System.StringComparison.OrdinalIgnoreCase)) continue;
if (fallback == null) fallback = t;
if (t.GetComponent<Button>() != null) return t;
}
return fallback;
}
private Transform FindSceneTransformByName(string exactName)
{
if (string.IsNullOrEmpty(exactName)) return null;
Transform[] all = GetAllSceneTransforms();
for (int i = 0; i < all.Length; i++)
{
Transform t = all[i];
if (t == null) continue;
if (string.Equals(t.name, exactName, System.StringComparison.OrdinalIgnoreCase))
return t;
}
return null;
}
private Transform FindSceneTransformByNameUnder(string exactName, Transform parent)
{
if (string.IsNullOrEmpty(exactName)) return null;
if (parent != null)
{
Transform[] all = parent.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < all.Length; i++)
{
Transform t = all[i];
if (t == null) continue;
if (string.Equals(t.name, exactName, System.StringComparison.OrdinalIgnoreCase))
return t;
}
}
return FindSceneTransformByName(exactName);
}
private Transform[] GetAllSceneTransforms()
{
Transform[] all = Resources.FindObjectsOfTypeAll<Transform>();
List<Transform> filtered = new List<Transform>(all.Length);
Scene active = SceneManager.GetActiveScene();
for (int i = 0; i < all.Length; i++)
{
Transform t = all[i];
if (t == null) continue;
if (!t.gameObject.scene.IsValid()) continue;
if (t.gameObject.scene != active) continue;
filtered.Add(t);
}
return filtered.ToArray();
}
private Transform[] GetRootTransformsOfActiveScene()
{
Scene active = SceneManager.GetActiveScene();
if (!active.IsValid() || !active.isLoaded) return new Transform[0];
GameObject[] roots = active.GetRootGameObjects();
Transform[] result = new Transform[roots.Length];
for (int i = 0; i < roots.Length; i++)
result[i] = roots[i] != null ? roots[i].transform : null;
return result;
}
}