RankUpLooper.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using DG.Tweening;
  4. using System.Collections.Generic;
  5. using System.Collections;
  6. public class RankUpLooper : MonoBehaviour
  7. {
  8. public ScrollRect scrollRect;
  9. public RectTransform content;
  10. public RectTransform viewport;
  11. public GameObject itemPrefab;
  12. public int itemCount = 20;
  13. public int targetRankIndex = 2;
  14. public float scrollSpeed = 2000f;
  15. public float scrollDuration = 1.5f;
  16. public float stopDelay = 0.5f;
  17. private RectTransform highlightItem;
  18. private float itemHeight;
  19. private bool isAnimating = false;
  20. private float contentResetThreshold;
  21. private List<GameObject> allItems = new List<GameObject>();
  22. public void OnClickRankUp()
  23. {
  24. PlayRankUp(currentRankIndex: 10, targetRankIndex: 2);// 从第10名冲到第2名(模拟)
  25. }
  26. void Start()
  27. {
  28. InitRankList();
  29. }
  30. void InitRankList()
  31. {
  32. allItems.Clear();
  33. foreach (Transform child in content) Destroy(child.gameObject);
  34. // 创建原始项
  35. for (int i = 0; i < itemCount; i++)
  36. {
  37. var item = Instantiate(itemPrefab, content);
  38. item.GetComponentInChildren<Text>().text = $"#{i + 1}";
  39. allItems.Add(item);
  40. }
  41. // 复制一轮,实现循环
  42. for (int i = 0; i < itemCount; i++)
  43. {
  44. var clone = Instantiate(itemPrefab, content);
  45. clone.GetComponentInChildren<Text>().text = $"#{i + 1} (copy)";
  46. allItems.Add(clone);
  47. }
  48. LayoutRebuilder.ForceRebuildLayoutImmediate(content);
  49. // 获取 item 高度
  50. itemHeight = allItems[0].GetComponent<RectTransform>().rect.height;
  51. contentResetThreshold = itemHeight * itemCount;
  52. }
  53. public void PlayRankUp(int currentRankIndex, int targetRankIndex)
  54. {
  55. if (isAnimating) return;
  56. this.targetRankIndex = targetRankIndex;
  57. var original = allItems[currentRankIndex - 1];
  58. highlightItem = Instantiate(original, viewport).GetComponent<RectTransform>();
  59. highlightItem.name = "HighlightItem";
  60. highlightItem.SetAsLastSibling();
  61. if (!highlightItem.TryGetComponent(out CanvasGroup group))
  62. group = highlightItem.gameObject.AddComponent<CanvasGroup>();
  63. group.alpha = 1f;
  64. highlightItem.localScale = Vector3.one * 1.2f;
  65. isAnimating = true;
  66. scrollRect.enabled = false;
  67. // 🔄 强制刷新布局
  68. LayoutRebuilder.ForceRebuildLayoutImmediate(content);
  69. itemHeight = allItems[0].GetComponent<RectTransform>().rect.height;
  70. contentResetThreshold = itemHeight * itemCount;
  71. // 🎯 提前计算目标位置
  72. Vector3 targetPosition = CalculateTargetLocalPosition(targetRankIndex);
  73. // 启动滚动协程(传入目标位置)
  74. StartCoroutine(ScrollAndStop(targetPosition));
  75. }
  76. // IEnumerator DelayedScroll()
  77. // {
  78. // // 🔄 强制更新布局
  79. // LayoutRebuilder.ForceRebuildLayoutImmediate(content);
  80. // // ⏳ 等待一帧,确保布局完成
  81. // yield return new WaitForEndOfFrame();
  82. // // 再次确认布局高度等
  83. // itemHeight = allItems[0].GetComponent<RectTransform>().rect.height;
  84. // contentResetThreshold = itemHeight * itemCount;
  85. // // 🔥 启动滚动
  86. // StartCoroutine(ScrollAndStop());
  87. // }
  88. IEnumerator ScrollAndStop(Vector3 targetPos)
  89. {
  90. float elapsed = 0f;
  91. while (elapsed < scrollDuration)
  92. {
  93. content.anchoredPosition += Vector2.up * scrollSpeed * Time.deltaTime;
  94. // 无限滚动逻辑
  95. if (content.anchoredPosition.y >= contentResetThreshold)
  96. {
  97. content.anchoredPosition -= Vector2.up * contentResetThreshold;
  98. }
  99. elapsed += Time.deltaTime;
  100. yield return null;
  101. }
  102. yield return new WaitForSeconds(stopDelay);
  103. SnapToTarget(targetPos);
  104. }
  105. void SnapToTarget(Vector3 targetPos)
  106. {
  107. content.DOAnchorPos(targetPos, 0.6f).SetEase(Ease.OutCubic);
  108. highlightItem.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutBack);
  109. highlightItem.DOAnchorPosY(0, 0.4f).SetEase(Ease.OutSine).OnComplete(() =>
  110. {
  111. isAnimating = false;
  112. });
  113. }
  114. private Vector3 CalculateTargetLocalPosition(int targetRank)
  115. {
  116. float spacing = 0f;
  117. float totalItemHeight = itemHeight + spacing;
  118. float contentY = (targetRank - 1) * totalItemHeight;
  119. float centeredOffset = contentY - (viewport.rect.height - totalItemHeight) / 2f;
  120. // 限制 offset 范围
  121. centeredOffset = Mathf.Clamp(centeredOffset, 0, content.rect.height - viewport.rect.height);
  122. return new Vector3(0f, centeredOffset, 0f);
  123. }
  124. void UpdateHighlightPosition()
  125. {
  126. RectTransform targetItem = allItems[targetRankIndex - 1].GetComponent<RectTransform>();
  127. Vector3 worldPos = targetItem.position;
  128. Vector2 localPoint;
  129. RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, worldPos, null, out localPoint);
  130. highlightItem.anchoredPosition = localPoint;
  131. }
  132. // void SnapToTarget()
  133. // {
  134. // // 停止后直接对 content 归位
  135. // float targetY = itemHeight * (targetRankIndex - 1);
  136. // float totalContentHeight = content.rect.height;
  137. // float viewHeight = viewport.rect.height;
  138. // float offset = Mathf.Clamp(targetY - (viewHeight - itemHeight) / 2f, 0, totalContentHeight - viewHeight);
  139. // content.anchoredPosition = new Vector2(0, offset);
  140. // // 获取目标 item 的本地位置(在 content 中的位置)
  141. // RectTransform targetItem = allItems[targetRankIndex - 1].GetComponent<RectTransform>();
  142. // // 计算目标位置相对 viewport 的 localPosition
  143. // Vector3 worldPos = targetItem.position;
  144. // Vector2 localPoint;
  145. // RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, worldPos, null, out localPoint);
  146. // // Animate highlightItem 到目标位置
  147. // highlightItem.DOAnchorPos(localPoint, 0.4f).SetEase(Ease.OutSine);
  148. // highlightItem.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutBack).OnComplete(() =>
  149. // {
  150. // isAnimating = false;
  151. // });
  152. // }
  153. }