Files
OTG_8/Assets/_Scripts/Talents/TalentTreeUI.cs
T
2026-06-04 23:22:13 +03:00

232 lines
8.7 KiB
C#

using System.Collections.Generic;
using OTGIntegrated.Save;
using OTGIntegrated.Stats;
using UnityEngine;
using UnityEngine.UI;
namespace OTGIntegrated.Talents
{
public sealed class TalentTreeUI : MonoBehaviour
{
public GameObject panelRoot;
public RectTransform nodesRoot;
public RectTransform lineRoot;
public TalentTooltipUI tooltip;
public Text titleText;
public Text pointsText;
public Text statsText;
public Button saveButton;
public Button loadButton;
public Button resetButton;
public Button closeButton;
public Font uiFont;
private TalentManager talentManager;
private PlayerStats playerStats;
private readonly List<TalentNodeUI> nodes = new List<TalentNodeUI>();
private readonly List<GameObject> spawnedLines = new List<GameObject>();
public void Initialize(TalentManager manager, PlayerStats stats)
{
talentManager = manager;
playerStats = stats;
if (talentManager != null)
{
talentManager.OnTalentStateChanged -= Refresh;
talentManager.OnTalentStateChanged += Refresh;
}
if (playerStats != null)
{
playerStats.OnStatsChanged -= Refresh;
playerStats.OnStatsChanged += Refresh;
}
if (saveButton != null) saveButton.onClick.AddListener(() => FindObjectOfType<SaveSystem>()?.Save());
if (loadButton != null) loadButton.onClick.AddListener(() => FindObjectOfType<SaveSystem>()?.Load());
if (resetButton != null) resetButton.onClick.AddListener(() => FindObjectOfType<SaveSystem>()?.ResetSave());
if (closeButton != null) closeButton.onClick.AddListener(() => FindObjectOfType<OTGIntegrated.UI.UIManager>()?.CloseCurrentWindow());
BuildTree();
Refresh();
}
private void OnEnable()
{
Refresh();
}
private void OnDestroy()
{
if (talentManager != null)
{
talentManager.OnTalentStateChanged -= Refresh;
}
if (playerStats != null)
{
playerStats.OnStatsChanged -= Refresh;
}
}
public void BuildTree()
{
ClearTree();
if (talentManager == null || nodesRoot == null)
{
return;
}
Dictionary<TalentData, TalentNodeUI> lookup = new Dictionary<TalentData, TalentNodeUI>();
for (int i = 0; i < talentManager.allTalents.Count; i++)
{
TalentData talent = talentManager.allTalents[i];
if (talent == null)
{
continue;
}
TalentNodeUI node = CreateNode(talent);
nodes.Add(node);
lookup[talent] = node;
}
for (int i = 0; i < talentManager.allTalents.Count; i++)
{
TalentData talent = talentManager.allTalents[i];
if (talent == null || talent.prerequisites == null || !lookup.TryGetValue(talent, out TalentNodeUI node))
{
continue;
}
for (int p = 0; p < talent.prerequisites.Length; p++)
{
TalentData prerequisite = talent.prerequisites[p];
if (prerequisite != null && lookup.TryGetValue(prerequisite, out TalentNodeUI prerequisiteNode))
{
CreateLine(prerequisiteNode.GetComponent<RectTransform>().anchoredPosition, node.GetComponent<RectTransform>().anchoredPosition);
}
}
}
}
public void Refresh()
{
if (titleText != null)
{
titleText.text = "Дерево талантов";
}
if (pointsText != null)
{
int points = talentManager != null ? talentManager.availableTalentPoints : 0;
pointsText.text = $"Очки талантов: {points}";
}
if (statsText != null)
{
statsText.text = playerStats == null
? "Характеристики недоступны"
: $"Характеристики\nHP: {Mathf.RoundToInt(playerStats.CurrentHealth)} / {Mathf.RoundToInt(playerStats.MaxHealth)}\nMana: {Mathf.RoundToInt(playerStats.CurrentMana)} / {Mathf.RoundToInt(playerStats.MaxMana)}\nDamage: {playerStats.Damage:0.#}\nMove: {playerStats.MoveSpeed:0.#}\nCrit: {playerStats.CriticalChance:P0}\nCooldown: {playerStats.CooldownReduction:P0}";
}
for (int i = 0; i < nodes.Count; i++)
{
if (nodes[i] != null)
{
nodes[i].Refresh();
}
}
}
private TalentNodeUI CreateNode(TalentData talent)
{
GameObject root = new GameObject(talent.displayName, typeof(RectTransform), typeof(Image), typeof(Button), typeof(TalentNodeUI));
root.transform.SetParent(nodesRoot, false);
RectTransform rect = root.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(160f, 76f);
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.anchoredPosition = talent.treePosition;
Text title = CreateText(root.transform, "Title", talent.displayName, 15, TextAnchor.MiddleCenter);
title.rectTransform.anchorMin = new Vector2(0.08f, 0.25f);
title.rectTransform.anchorMax = new Vector2(0.92f, 0.9f);
title.rectTransform.offsetMin = Vector2.zero;
title.rectTransform.offsetMax = Vector2.zero;
Text cost = CreateText(root.transform, "Cost", talent.cost.ToString(), 14, TextAnchor.MiddleCenter);
cost.rectTransform.anchorMin = new Vector2(0.78f, 0.02f);
cost.rectTransform.anchorMax = new Vector2(0.98f, 0.3f);
cost.rectTransform.offsetMin = Vector2.zero;
cost.rectTransform.offsetMax = Vector2.zero;
TalentNodeUI node = root.GetComponent<TalentNodeUI>();
node.background = root.GetComponent<Image>();
node.button = root.GetComponent<Button>();
node.titleText = title;
node.costText = cost;
node.Initialize(talent, talentManager, tooltip, uiFont);
return node;
}
private void CreateLine(Vector2 start, Vector2 end)
{
if (lineRoot == null)
{
return;
}
GameObject line = new GameObject("TalentLine", typeof(RectTransform), typeof(Image));
line.transform.SetParent(lineRoot, false);
spawnedLines.Add(line);
Image image = line.GetComponent<Image>();
image.color = new Color(0.78f, 0.62f, 0.26f, 0.55f);
RectTransform rect = line.GetComponent<RectTransform>();
Vector2 delta = end - start;
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.sizeDelta = new Vector2(delta.magnitude, 3f);
rect.anchoredPosition = start + delta * 0.5f;
rect.localRotation = Quaternion.Euler(0f, 0f, Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg);
}
private Text CreateText(Transform parent, string name, string value, int fontSize, TextAnchor anchor)
{
GameObject textObject = new GameObject(name, typeof(RectTransform), typeof(Text));
textObject.transform.SetParent(parent, false);
Text text = textObject.GetComponent<Text>();
text.font = uiFont != null ? uiFont : Resources.GetBuiltinResource<Font>("Arial.ttf");
text.fontSize = fontSize;
text.alignment = anchor;
text.color = new Color(0.95f, 0.94f, 0.9f, 1f);
text.text = value;
text.resizeTextForBestFit = true;
text.resizeTextMinSize = 10;
text.resizeTextMaxSize = fontSize;
return text;
}
private void ClearTree()
{
for (int i = nodesRoot != null ? nodesRoot.childCount - 1 : -1; i >= 0; i--)
{
Destroy(nodesRoot.GetChild(i).gameObject);
}
for (int i = 0; i < spawnedLines.Count; i++)
{
if (spawnedLines[i] != null)
{
Destroy(spawnedLines[i]);
}
}
nodes.Clear();
spawnedLines.Clear();
}
}
}