(client) feat:实现技能树界面,实现地图生成器,实现维度指定,实现规则瓦片定义,实现逃跑逻辑,实现消息定义,实现武器动画,实现受击动画 fix: 修复单攻击子弹击中多个目标,修复人物属性计算错误 (#56)

Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com>
Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/56
This commit is contained in:
2025-09-19 08:26:54 +08:00
parent 78849e0cc5
commit 87a8abe86c
282 changed files with 19364 additions and 8824 deletions

View File

@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UI
{
public class SkillTreeUI : FullScreenUI
{
public SkillTreePageUI skillTreePageUIPrefab;
public Transform skillTreePageUIParent; // 用于承载 SkillTreePageUI 实例的 Transform
private List<SkillTreePageUI> _skillTreePageUIs = new List<SkillTreePageUI>();
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<RectTransform>();
if (prefabRect != null)
{
_pageWidth = prefabRect.rect.width;
// 如果父级有LayoutGroup或者Canvas Scaler可能影响实际尺寸需注意
// 如果 prefab 是撑满 FullScreenUI 的,那 _pageWidth 应该等于 FullScreenUI 的宽度
// 这里假设 SkillTreePageUI 会充满其父级,或者具有固定宽度。
// 简单起见,我们也可以假设它充满父级,那么 _pageWidth = parent.GetComponent<RectTransform>().rect.width;
if (skillTreePageUIParent != null)
{
_pageWidth = skillTreePageUIParent.GetComponent<RectTransform>().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<RectTransform>();
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<RectTransform>().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<RectTransform>().anchoredPosition = new Vector2(currentX, 0);
// 计算目标页面的目标位置:从 _pageWidth * _animationDirection 移动到 0
var targetX = Mathf.Lerp(_pageWidth * _animationDirection, 0, _animationProgress);
_targetMovingPage.GetComponent<RectTransform>().anchoredPosition = new Vector2(targetX, 0);
if (_animationProgress >= 1f)
{
// 动画结束
_isAnimating = false;
_animationDirection = 0; // 重置动画方向
// 更新当前页面索引 (使用 _targetMovingPage 的索引)
_currentPageIndex = _skillTreePageUIs.IndexOf(_targetMovingPage);
// 确保新的当前页面在正确位置 (0,0)
_targetMovingPage.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
// 隐藏所有非当前页面,将其放置到屏幕外,减少渲染负担
for (var i = 0; i < _skillTreePageUIs.Count; i++)
{
if (i != _currentPageIndex)
{
// 将非当前页面放置到正确的位置,以便下次翻页时能从正确位置移入
_skillTreePageUIs[i].GetComponent<RectTransform>().anchoredPosition =
new Vector2(_pageWidth * (i - _currentPageIndex), 0);
}
}
// 确保新的当前页面在层级最上方
_skillTreePageUIs[_currentPageIndex].transform.SetAsLastSibling();
}
}
}
/// <summary>
/// 控制切换到上一页。
/// </summary>
public void TurnPageLeft()
{
if (_isAnimating) return; // 动画进行中,忽略新的翻页请求
// 检查是否已经到达第一页
if (_currentPageIndex <= 0)
{
Debug.Log("SkillTreeUI: Already at the first page.");
return;
}
_animationDirection = -1; // 左翻页方向
StartPageTurnAnimation(_currentPageIndex - 1);
}
/// <summary>
/// 控制切换到下一页。
/// </summary>
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);
}
/// <summary>
/// 启动翻页动画的内部方法。
/// </summary>
/// <param name="targetPageIndex">目标页面的索引。</param>
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<RectTransform>().anchoredPosition =
new Vector2(_pageWidth * _animationDirection, 0);
// 确保目标页面在当前页面之上,以便在滑动时覆盖旧页面
_targetMovingPage.transform.SetAsLastSibling();
}
// 可以添加一个 GoToPage(int pageIndex) 方法来直接跳转到某一页
// 这里为了简化,只实现了左右翻页
}
}