using System; using System.Collections.Generic; using UnityEngine; namespace UI { public class SkillTreeUI : FullScreenUI { public SkillTreePageUI skillTreePageUIPrefab; public Transform skillTreePageUIParent; // 用于承载 SkillTreePageUI 实例的 Transform private List _skillTreePageUIs = new List(); private int _currentPageIndex = 0; // 当前显示的页面索引 // 动画相关 private bool _isAnimating = false; private float _animationProgress = 0f; // 0到1的进度 [SerializeField] private float _animationDuration = 0.3f; // 动画持续时间 private int _animationDirection = 0; // -1:左翻页, 1:右翻页 (或0表示无动画) private SkillTreePageUI _currentMovingPage; // 当前正在移动的页面 private SkillTreePageUI _targetMovingPage; // 正在移入的页面 private float _pageWidth; // 页面的标准宽度,用于计算滑动位置 private void Start() { // 获取所有技能树标签 var allTags = Managers.SkillTreeManager.Instance.GetAllTag(); if (allTags == null || allTags.Length == 0) { Debug.LogWarning("No skill tree tags found. SkillTreeUI will be empty."); // 可以在这里禁用翻页按钮或显示提示 return; } // [改进] 动态获取页面的宽度 // 为了准确获取 prefab 的尺寸,最好在编辑器中设置好 prefab 的 RectTransform, // 或者在这里实例化一个临时对象来获取其 RectTransform 信息。 var prefabRect = skillTreePageUIPrefab.GetComponent(); if (prefabRect != null) { _pageWidth = prefabRect.rect.width; // 如果父级有LayoutGroup或者Canvas Scaler可能影响实际尺寸,需注意 // 如果 prefab 是撑满 FullScreenUI 的,那 _pageWidth 应该等于 FullScreenUI 的宽度 // 这里假设 SkillTreePageUI 会充满其父级,或者具有固定宽度。 // 简单起见,我们也可以假设它充满父级,那么 _pageWidth = parent.GetComponent().rect.width; if (skillTreePageUIParent != null) { _pageWidth = skillTreePageUIParent.GetComponent().rect.width; } } else { Debug.LogError("SkillTreePageUI Prefab does not have a RectTransform!"); _pageWidth = 1920; // fallback default } // 为每个标签实例化并初始化 SkillTreePageUI foreach (var tag in allTags) { var newPage = Instantiate(skillTreePageUIPrefab, skillTreePageUIParent); newPage.name = $"SkillTreePage_{tag}"; // 方便在 Hierarchy 中识别 // 设置 RectTransform 属性,确保页面正确布局 var pageRectTransform = newPage.GetComponent(); pageRectTransform.anchorMin = Vector2.zero; pageRectTransform.anchorMax = Vector2.one; pageRectTransform.pivot = new Vector2(0.5f, 0.5f); // 中心 pageRectTransform.anchoredPosition = Vector2.zero; pageRectTransform.sizeDelta = Vector2.zero; // 撑满父级 newPage.GenerateAndLayoutAllSkillTrees(tag); _skillTreePageUIs.Add(newPage); // 初始状态下所有页面都先设为激活,通过位置控制显示 newPage.gameObject.SetActive(true); } // 根据 _currentPageIndex 设置所有页面的初始位置 // 只有当前页面在中央 (0,0),其他页面在屏幕外 for (var i = 0; i < _skillTreePageUIs.Count; i++) { _skillTreePageUIs[i].GetComponent().anchoredPosition = new Vector2(_pageWidth * (i - _currentPageIndex), 0); } // 确保当前页面在层级的最上方,防止被其他未隐藏的页面遮挡 if (_skillTreePageUIs.Count > 0) { _skillTreePageUIs[_currentPageIndex].transform.SetAsLastSibling(); } } // 实现翻页动画 public override void TickUI() { base.TickUI(); if (_isAnimating) { _animationProgress += Time.deltaTime / _animationDuration; _animationProgress = Mathf.Clamp01(_animationProgress); // 计算当前页面的目标位置:从 0 移动到 -_pageWidth * _animationDirection var currentX = Mathf.Lerp(0, -_pageWidth * _animationDirection, _animationProgress); _currentMovingPage.GetComponent().anchoredPosition = new Vector2(currentX, 0); // 计算目标页面的目标位置:从 _pageWidth * _animationDirection 移动到 0 var targetX = Mathf.Lerp(_pageWidth * _animationDirection, 0, _animationProgress); _targetMovingPage.GetComponent().anchoredPosition = new Vector2(targetX, 0); if (_animationProgress >= 1f) { // 动画结束 _isAnimating = false; _animationDirection = 0; // 重置动画方向 // 更新当前页面索引 (使用 _targetMovingPage 的索引) _currentPageIndex = _skillTreePageUIs.IndexOf(_targetMovingPage); // 确保新的当前页面在正确位置 (0,0) _targetMovingPage.GetComponent().anchoredPosition = Vector2.zero; // 隐藏所有非当前页面,将其放置到屏幕外,减少渲染负担 for (var i = 0; i < _skillTreePageUIs.Count; i++) { if (i != _currentPageIndex) { // 将非当前页面放置到正确的位置,以便下次翻页时能从正确位置移入 _skillTreePageUIs[i].GetComponent().anchoredPosition = new Vector2(_pageWidth * (i - _currentPageIndex), 0); } } // 确保新的当前页面在层级最上方 _skillTreePageUIs[_currentPageIndex].transform.SetAsLastSibling(); } } } /// /// 控制切换到上一页。 /// public void TurnPageLeft() { if (_isAnimating) return; // 动画进行中,忽略新的翻页请求 // 检查是否已经到达第一页 if (_currentPageIndex <= 0) { Debug.Log("SkillTreeUI: Already at the first page."); return; } _animationDirection = -1; // 左翻页方向 StartPageTurnAnimation(_currentPageIndex - 1); } /// /// 控制切换到下一页。 /// public void TurnPageRight() { if (_isAnimating) return; // 动画进行中,忽略新的翻页请求 // 检查是否已经到达最后一页 if (_currentPageIndex >= _skillTreePageUIs.Count - 1) { Debug.Log("SkillTreeUI: Already at the last page."); return; } _animationDirection = 1; // 右翻页方向 StartPageTurnAnimation(_currentPageIndex + 1); } /// /// 启动翻页动画的内部方法。 /// /// 目标页面的索引。 private void StartPageTurnAnimation(int targetPageIndex) { _isAnimating = true; _animationProgress = 0f; // 重置动画进度 _currentMovingPage = _skillTreePageUIs[_currentPageIndex]; _targetMovingPage = _skillTreePageUIs[targetPageIndex]; // 确保参与动画的两个页面都是激活状态 _currentMovingPage.gameObject.SetActive(true); _targetMovingPage.gameObject.SetActive(true); // 将目标页面初始位置设置在当前页的左侧或右侧 // 例如,如果向右翻页 (_animationDirection = 1),目标页从右边 (_pageWidth) 滑入 _targetMovingPage.GetComponent().anchoredPosition = new Vector2(_pageWidth * _animationDirection, 0); // 确保目标页面在当前页面之上,以便在滑动时覆盖旧页面 _targetMovingPage.transform.SetAsLastSibling(); } // 可以添加一个 GoToPage(int pageIndex) 方法来直接跳转到某一页 // 这里为了简化,只实现了左右翻页 } }