using System.Collections; using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using Object = UnityEngine.Object; public class TeamSelectorAnimController : MonoBehaviour { public static TeamSelectorAnimController Instance { get; private set; } [Header("Roots")] [SerializeField] RectTransform teamCanvas; [SerializeField] RectTransform leftSlotsContainer; [SerializeField] RectTransform rightTopPanel; [SerializeField] RectTransform rightBottomPanel; [SerializeField] RectTransform confirmButton; [SerializeField] RectTransform titleText; [SerializeField] RectTransform smallCardsContainer; [Header("Timing")] [SerializeField] bool useUnscaled = true; [SerializeField] float panelEnterOffset = 200f; [SerializeField] float panelEnterTime = 0.35f; [SerializeField] float panelStagger = 0.08f; [SerializeField] float slotEnterOffset = 140f; [SerializeField] float slotEnterTime = 0.3f; [SerializeField] float slotStagger = 0.06f; [SerializeField] float confirmEnterOffset = 120f; [SerializeField] float confirmEnterTime = 0.3f; [SerializeField] float titleBlinkTime = 0.06f; [SerializeField] int titleBlinkCount = 2; [SerializeField] float cardBlinkTime = 0.05f; [SerializeField] int cardBlinkCount = 2; [SerializeField] float skillsBlinkTime = 0.05f; [SerializeField] int skillsBlinkCount = 2; bool teamChanged; int[] cachedTeam = new int[5]; bool playRequested; static bool pendingTeamChanged; void Awake() { if (Instance == null) Instance = this; } void OnEnable() { if (Instance == null) Instance = this; CacheTeamSnapshot(); if (pendingTeamChanged) teamChanged = true; pendingTeamChanged = false; BindConfirmButton(); if (!playRequested) { playRequested = true; StartCoroutine(PlayOpenAnim()); } } void OnDisable() { playRequested = false; if (HasTeamChanged()) { FlashTeammatesProfiles(); } teamChanged = false; } void OnDestroy() { if (Instance == this) Instance = null; } public static void NotifyTeamChanged() { pendingTeamChanged = true; if (Instance != null) Instance.teamChanged = true; } public static void PlaySkillsFlash(RectTransform container = null) { if (Instance == null) return; Instance.PlaySkillsFlashInternal(container); } IEnumerator PlayOpenAnim() { yield return null; // wait one frame for layout EnsureRefs(); if (teamCanvas != null && teamCanvas.localScale == Vector3.zero) { teamCanvas.localScale = Vector3.one; } AnimatePanel(rightTopPanel, panelEnterOffset, 0f); AnimatePanel(rightBottomPanel, panelEnterOffset, panelStagger); AnimateConfirm(confirmButton); FlickerTitle(); StartCoroutine(AnimateSlots()); StartCoroutine(AnimateSmallCards()); // Defer skills flash to avoid stacking with slot enter animation work. float delay = panelEnterTime + panelStagger + EstimateSlotEnterWindow() + 0.05f; DOVirtual.DelayedCall(delay, () => PlaySkillsFlashInternal(null)).SetUpdate(useUnscaled); } void EnsureRefs() { if (teamCanvas == null) { var child = transform.Find("teamCanvas"); teamCanvas = child as RectTransform; } if (teamCanvas != null && teamCanvas.localScale == Vector3.zero) { teamCanvas.localScale = Vector3.one; } if (leftSlotsContainer == null) { if (newTeamSelector.Instance != null && newTeamSelector.Instance.container != null) leftSlotsContainer = newTeamSelector.Instance.container; else if (teamCanvas != null) leftSlotsContainer = FindChildByName(teamCanvas, "horizontal"); } if (rightTopPanel == null && teamCanvas != null) rightTopPanel = FindChildByName(teamCanvas, "selectBase_hero"); if (rightBottomPanel == null && teamCanvas != null) rightBottomPanel = FindChildByName(teamCanvas, "selectBase_skills"); if (confirmButton == null && teamCanvas != null) confirmButton = FindChildByName(teamCanvas, "confirmTeamSelecting"); if (titleText == null && teamCanvas != null) titleText = FindChildByName(teamCanvas, "titleTXT"); if (smallCardsContainer == null && rightTopPanel != null) { smallCardsContainer = FindSmallCardsContainer(rightTopPanel); } } void CacheTeamSnapshot() { cachedTeam[0] = PlayerPrefs.GetInt("selected_heroSlot01_heroID", 0); cachedTeam[1] = PlayerPrefs.GetInt("selected_heroSlot02_heroID", 0); cachedTeam[2] = PlayerPrefs.GetInt("selected_heroSlot03_heroID", 0); cachedTeam[3] = PlayerPrefs.GetInt("selected_heroSlot04_heroID", 0); cachedTeam[4] = PlayerPrefs.GetInt("selected_heroSlot05_heroID", 0); } bool HasTeamChanged() { if (teamChanged) return true; if (cachedTeam[0] != PlayerPrefs.GetInt("selected_heroSlot01_heroID", 0)) return true; if (cachedTeam[1] != PlayerPrefs.GetInt("selected_heroSlot02_heroID", 0)) return true; if (cachedTeam[2] != PlayerPrefs.GetInt("selected_heroSlot03_heroID", 0)) return true; if (cachedTeam[3] != PlayerPrefs.GetInt("selected_heroSlot04_heroID", 0)) return true; if (cachedTeam[4] != PlayerPrefs.GetInt("selected_heroSlot05_heroID", 0)) return true; return false; } IEnumerator AnimateSlots() { // Some slots can be instantiated/enabled a couple frames after the panel is shown. // Wait a bit for the full slot set so we don't miss animating the last one. int expectedCount = cachedTeam != null && cachedTeam.Length > 0 ? cachedTeam.Length : 5; if (newTeamSelector.Instance != null && newTeamSelector.Instance.slotCount > 0) expectedCount = newTeamSelector.Instance.slotCount; List slotRects = GetSlotRects(); int tries = 0; while ((slotRects == null || slotRects.Count < expectedCount) && tries < 20) { tries++; yield return null; slotRects = GetSlotRects(); } if (slotRects == null || slotRects.Count == 0) yield break; LayoutGroup layout = leftSlotsContainer != null ? leftSlotsContainer.GetComponent() : null; bool layoutEnabled = layout != null && layout.enabled; if (leftSlotsContainer != null) LayoutRebuilder.ForceRebuildLayoutImmediate(leftSlotsContainer); slotRects.Sort((a, b) => a.GetSiblingIndex().CompareTo(b.GetSiblingIndex())); if (layoutEnabled) layout.enabled = false; var basePosBySlot = new Dictionary(slotRects.Count); for (int i = 0; i < slotRects.Count; i++) { RectTransform rt = slotRects[i]; if (rt == null) continue; Vector2 basePos = rt.anchoredPosition; basePosBySlot[rt] = basePos; rt.DOKill(); rt.anchoredPosition = basePos + new Vector2(0f, -slotEnterOffset); rt.DOAnchorPos(basePos, slotEnterTime) .SetEase(Ease.OutCubic) .SetDelay(i * slotStagger) .SetUpdate(useUnscaled) // If a slot gets disabled/enabled during layout/UI updates, don't kill the tween // (killing it can leave the slot stuck at the start offset). .SetLink(rt.gameObject, LinkBehaviour.PauseOnDisablePlayOnEnable); } float total = slotEnterTime + slotStagger * Mathf.Max(0, slotRects.Count - 1); float t = 0f; while (t < total) { t += useUnscaled ? Time.unscaledDeltaTime : Time.deltaTime; yield return null; } // Failsafe: if any slot tween was interrupted (e.g. killed by disable), snap it back. foreach (var kvp in basePosBySlot) { RectTransform rt = kvp.Key; if (rt == null) continue; Vector2 basePos = kvp.Value; if (Vector2.Distance(rt.anchoredPosition, basePos) > 0.5f) { rt.DOKill(); rt.DOAnchorPos(basePos, 0.12f) .SetEase(Ease.OutCubic) .SetUpdate(useUnscaled) .SetLink(rt.gameObject, LinkBehaviour.PauseOnDisablePlayOnEnable); } } if (layoutEnabled) layout.enabled = true; if (leftSlotsContainer != null) LayoutRebuilder.ForceRebuildLayoutImmediate(leftSlotsContainer); } IEnumerator AnimateSmallCards() { if (smallCardsContainer == null) EnsureRefs(); // Start a bit later to avoid overlapping slot enter bursts. float t = panelEnterTime + Mathf.Min(0.2f, EstimateSlotEnterWindow() * 0.4f); while (t > 0f) { t -= useUnscaled ? Time.unscaledDeltaTime : Time.deltaTime; yield return null; } List cards = FindSmallCards(); if (cards.Count == 0) yield break; cards.Sort((a, b) => a.anchoredPosition.x.CompareTo(b.anchoredPosition.x)); for (int i = 0; i < cards.Count; i++) { RectTransform rt = cards[i]; if (rt == null) continue; if (rt.GetComponent() == null) rt.gameObject.AddComponent(); EnsureRaycastTarget(rt); CanvasGroup cg = EnsureCanvasGroup(rt); if (cg == null) continue; cg.DOKill(); cg.alpha = 0f; Sequence seq = DOTween.Sequence().SetUpdate(useUnscaled); seq.SetDelay(i * 0.04f); seq.Append(cg.DOFade(1f, cardBlinkTime)); for (int b = 0; b < cardBlinkCount; b++) { seq.Append(cg.DOFade(0f, cardBlinkTime)); seq.Append(cg.DOFade(1f, cardBlinkTime)); } seq.SetLink(cg.gameObject, LinkBehaviour.KillOnDisable); } } void AnimatePanel(RectTransform panel, float offsetX, float delay) { if (panel == null) return; Vector2 basePos = panel.anchoredPosition; panel.DOKill(); panel.anchoredPosition = basePos + new Vector2(offsetX, 0f); panel.DOAnchorPos(basePos, panelEnterTime) .SetEase(Ease.OutCubic) .SetDelay(delay) .SetUpdate(useUnscaled) .SetLink(panel.gameObject, LinkBehaviour.KillOnDisable); } void AnimateConfirm(RectTransform button) { if (button == null) return; Vector2 basePos = button.anchoredPosition; button.DOKill(); button.anchoredPosition = basePos + new Vector2(0f, -confirmEnterOffset); button.DOAnchorPos(basePos, confirmEnterTime) .SetEase(Ease.OutCubic) .SetUpdate(useUnscaled) .SetLink(button.gameObject, LinkBehaviour.KillOnDisable); } void FlickerTitle() { if (titleText == null) return; CanvasGroup cg = EnsureCanvasGroup(titleText); if (cg == null) return; cg.DOKill(); cg.alpha = 0f; Sequence seq = DOTween.Sequence().SetUpdate(useUnscaled); seq.Append(cg.DOFade(1f, titleBlinkTime)); for (int i = 0; i < titleBlinkCount; i++) { seq.Append(cg.DOFade(0f, titleBlinkTime)); seq.Append(cg.DOFade(1f, titleBlinkTime)); } seq.SetLink(cg.gameObject, LinkBehaviour.KillOnDisable); } void PlaySkillsFlashInternal(RectTransform container) { Transform target = container != null ? container.transform : FindSkillContainer(); if (target == null) target = rightBottomPanel != null ? rightBottomPanel.transform : null; if (target == null) return; var skillSlots = target.GetComponentsInChildren(true); if (skillSlots != null && skillSlots.Length > 0) { for (int i = 0; i < skillSlots.Length; i++) { var slot = skillSlots[i]; if (slot == null) continue; CanvasGroup cg = EnsureCanvasGroup(slot.transform as RectTransform); if (cg == null) continue; cg.DOKill(); cg.alpha = 0f; Sequence seq = DOTween.Sequence().SetUpdate(useUnscaled); seq.SetDelay(i * 0.03f); seq.Append(cg.DOFade(1f, skillsBlinkTime)); for (int b = 0; b < skillsBlinkCount; b++) { seq.Append(cg.DOFade(0f, skillsBlinkTime)); seq.Append(cg.DOFade(1f, skillsBlinkTime)); } seq.SetLink(cg.gameObject, LinkBehaviour.KillOnDisable); } return; } // fallback: blink all graphics under the panel if no skill slots yet var graphics = target.GetComponentsInChildren(true); for (int i = 0; i < graphics.Length; i++) { Graphic g = graphics[i]; if (g == null) continue; CanvasGroup cg = EnsureCanvasGroup(g.transform as RectTransform); if (cg == null) continue; cg.DOKill(); cg.alpha = 0f; Sequence seq = DOTween.Sequence().SetUpdate(useUnscaled); seq.SetDelay(i * 0.02f); seq.Append(cg.DOFade(1f, skillsBlinkTime)); for (int b = 0; b < skillsBlinkCount; b++) { seq.Append(cg.DOFade(0f, skillsBlinkTime)); seq.Append(cg.DOFade(1f, skillsBlinkTime)); } seq.SetLink(cg.gameObject, LinkBehaviour.KillOnDisable); } } void FlashTeammatesProfiles() { GameObject go = SceneObjectLookupCache.Find("teammates_profilesHorizontalLayout"); if (go == null) return; CanvasGroup cg = EnsureCanvasGroup(go.transform as RectTransform); if (cg == null) return; cg.DOKill(); cg.alpha = 0f; Sequence seq = DOTween.Sequence().SetUpdate(useUnscaled); seq.Append(cg.DOFade(1f, 0.06f)); seq.Append(cg.DOFade(0f, 0.06f)); seq.Append(cg.DOFade(1f, 0.06f)); seq.SetLink(cg.gameObject, LinkBehaviour.KillOnDisable); } RectTransform FindChildByName(Transform root, string name) { if (root == null || string.IsNullOrEmpty(name)) return null; Transform found = root.Find(name); if (found != null) return found as RectTransform; foreach (Transform child in root.GetComponentsInChildren(true)) { if (child.name == name) return child as RectTransform; } return null; } RectTransform FindSmallCardsContainer(Transform root) { if (root == null) return null; var all = root.GetComponentsInChildren(true); Transform parent = null; int count = 0; for (int i = 0; i < all.Length; i++) { RectTransform rt = all[i]; if (rt == null) continue; if (!rt.name.StartsWith("teamSettings_teammateProfile")) continue; parent = rt.parent; count++; } if (count > 0 && parent != null) { return parent as RectTransform; } return null; } List FindSmallCards() { List list = new List(); if (rightTopPanel == null) return list; var all = rightTopPanel.GetComponentsInChildren(true); for (int i = 0; i < all.Length; i++) { RectTransform rt = all[i]; if (rt == null) continue; if (!rt.name.StartsWith("teamSettings_teammateProfile")) continue; list.Add(rt); } return list; } Transform FindSkillContainer() { if (rightBottomPanel == null) return null; var slot = rightBottomPanel.GetComponentInChildren(true); return slot != null ? slot.transform.parent : null; } List GetSlotRects() { List list = new List(); if (newTeamSelector.Instance != null) { var created = newTeamSelector.Instance.GetCreatedSlots(); if (created != null && created.Count > 0) { for (int i = 0; i < created.Count; i++) { if (created[i] == null) continue; RectTransform rt = created[i].transform as RectTransform; if (rt != null) list.Add(rt); } return list; } } if (leftSlotsContainer != null) { var slots = leftSlotsContainer.GetComponentsInChildren(true); for (int i = 0; i < slots.Length; i++) { if (slots[i] == null) continue; RectTransform rt = slots[i].transform as RectTransform; if (rt != null) list.Add(rt); } } return list; } void BindConfirmButton() { if (confirmButton == null) return; Button btn = confirmButton.GetComponent