996 lines
33 KiB
C#
996 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 List<Transform> explicitKeyDownRects = new List<Transform>(5);
|
|
|
|
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());
|
|
}
|
|
|
|
public void SetExplicitKeyDownRects(IEnumerable<Transform> rects)
|
|
{
|
|
explicitKeyDownRects.Clear();
|
|
if (rects != null)
|
|
{
|
|
foreach (Transform rect in rects)
|
|
{
|
|
if (rect != null)
|
|
{
|
|
explicitKeyDownRects.Add(rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
refsCached = false;
|
|
preStartPrepared = false;
|
|
}
|
|
|
|
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 (explicitKeyDownRects.Count > 0)
|
|
{
|
|
keyDownRects.AddRange(explicitKeyDownRects);
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|