first commit

This commit is contained in:
2026-06-04 22:14:21 +03:00
commit 677f9b8090
139 changed files with 15981 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(RectTransform))]
public class LineConnectorUI : MonoBehaviour
{
public RectTransform start;
public RectTransform end;
public Image lineImage;
public float thickness = 4f;
public bool dynamicUpdate = true;
private RectTransform rectTransform;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
if (lineImage == null)
{
lineImage = GetComponent<Image>();
}
}
private void LateUpdate()
{
if (dynamicUpdate)
{
UpdateLine();
}
}
public void SetEndpoints(RectTransform startPoint, RectTransform endPoint)
{
start = startPoint;
end = endPoint;
UpdateLine();
}
public void UpdateLine()
{
if (start == null || end == null)
{
return;
}
if (rectTransform == null)
{
rectTransform = GetComponent<RectTransform>();
}
RectTransform parent = rectTransform.parent as RectTransform;
if (parent == null)
{
return;
}
Vector2 startPosition = GetCenterInParentSpace(parent, start);
Vector2 endPosition = GetCenterInParentSpace(parent, end);
Vector2 delta = endPosition - startPosition;
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
rectTransform.pivot = new Vector2(0.5f, 0.5f);
rectTransform.anchoredPosition = startPosition + delta * 0.5f;
rectTransform.sizeDelta = new Vector2(delta.magnitude, Mathf.Max(1f, thickness));
rectTransform.localRotation = Quaternion.Euler(0f, 0f, Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg);
}
private Vector2 GetCenterInParentSpace(RectTransform parent, RectTransform target)
{
Vector3 worldCenter = target.TransformPoint(target.rect.center);
Vector3 localCenter = parent.InverseTransformPoint(worldCenter);
return new Vector2(localCenter.x, localCenter.y);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91664eb39be68649fa272e155e08b460
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+67
View File
@@ -0,0 +1,67 @@
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class NotificationUI : MonoBehaviour
{
public Text messageText;
public CanvasGroup canvasGroup;
public float visibleDuration = 2.2f;
public float fadeDuration = 0.25f;
private Coroutine routine;
private void Awake()
{
if (canvasGroup == null)
{
canvasGroup = GetComponent<CanvasGroup>();
}
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
canvasGroup.blocksRaycasts = false;
}
}
public void Show(string message)
{
if (messageText == null || string.IsNullOrWhiteSpace(message))
{
return;
}
messageText.text = message;
if (routine != null)
{
StopCoroutine(routine);
}
routine = StartCoroutine(ShowRoutine());
}
private IEnumerator ShowRoutine()
{
if (canvasGroup == null)
{
yield break;
}
canvasGroup.alpha = 1f;
yield return new WaitForSeconds(Mathf.Max(0.1f, visibleDuration));
float elapsed = 0f;
float duration = Mathf.Max(0.05f, fadeDuration);
while (elapsed < duration)
{
elapsed += Time.deltaTime;
canvasGroup.alpha = Mathf.Lerp(1f, 0f, elapsed / duration);
yield return null;
}
canvasGroup.alpha = 0f;
routine = null;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9af78e14524d49c8942a7b8ab1dc61d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+75
View File
@@ -0,0 +1,75 @@
using UnityEngine;
using UnityEngine.UI;
public class StatsPanelUI : MonoBehaviour
{
public PlayerStats playerStats;
public Text titleText;
public Text maxHealthText;
public Text maxManaText;
public Text damageText;
public Text moveSpeedText;
public Text cooldownReductionText;
public Text criticalChanceText;
private void Awake()
{
if (playerStats == null)
{
playerStats = FindObjectOfType<PlayerStats>();
}
}
private void OnEnable()
{
if (playerStats != null)
{
playerStats.OnStatsChanged += Refresh;
}
Refresh();
}
private void OnDisable()
{
if (playerStats != null)
{
playerStats.OnStatsChanged -= Refresh;
}
}
public void Refresh()
{
if (playerStats == null)
{
playerStats = FindObjectOfType<PlayerStats>();
}
if (playerStats == null)
{
return;
}
if (titleText != null)
{
titleText.text = "Характеристики";
}
SetText(maxHealthText, "Max Health", playerStats.MaxHealth, false);
SetText(maxManaText, "Max Mana", playerStats.MaxMana, false);
SetText(damageText, "Damage", playerStats.Damage, false);
SetText(moveSpeedText, "Move Speed", playerStats.MoveSpeed, false);
SetText(cooldownReductionText, "Cooldown Reduction", playerStats.AbilityCooldownReduction, true);
SetText(criticalChanceText, "Critical Chance", playerStats.CriticalChance, true);
}
private void SetText(Text text, string label, float value, bool percent)
{
if (text == null)
{
return;
}
text.text = percent ? $"{label}: {value:0.#}%" : $"{label}: {value:0.#}";
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2abe10fb9912e35b99a6e90c1840beb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+167
View File
@@ -0,0 +1,167 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class TalentNodeUI : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public TalentData talent;
public TalentManager manager;
public TalentTooltipUI tooltip;
public Image backgroundImage;
public Image iconImage;
public Text nameText;
public Text costText;
public Text stateText;
public Button learnButton;
public Outline borderOutline;
private void Awake()
{
if (learnButton == null)
{
learnButton = GetComponent<Button>();
}
if (backgroundImage == null)
{
backgroundImage = GetComponent<Image>();
}
if (borderOutline == null)
{
borderOutline = GetComponent<Outline>();
}
}
private void OnEnable()
{
if (learnButton != null)
{
learnButton.onClick.RemoveListener(OnClickLearn);
learnButton.onClick.AddListener(OnClickLearn);
}
}
private void OnDisable()
{
if (learnButton != null)
{
learnButton.onClick.RemoveListener(OnClickLearn);
}
}
public void Setup(TalentData talent, TalentManager manager)
{
this.talent = talent;
this.manager = manager;
Refresh();
}
public void Refresh()
{
if (talent == null)
{
return;
}
if (nameText != null)
{
nameText.text = talent.displayName;
}
if (costText != null)
{
costText.text = $"{talent.cost} очк.";
}
if (iconImage != null)
{
iconImage.sprite = talent.icon;
iconImage.color = talent.icon != null ? Color.white : talent.branchColor;
}
bool learned = manager != null && manager.IsLearned(talent);
bool prerequisitesLearned = manager != null && manager.ArePrerequisitesLearned(talent);
bool enoughPoints = manager != null && manager.GetAvailableTalentPoints() >= talent.cost;
bool available = manager != null && manager.CanLearn(talent);
Color nodeColor = UITheme.LockedNode;
string state = "Заблокировано";
if (learned)
{
nodeColor = UITheme.LearnedNode;
state = "Изучено";
}
else if (available)
{
nodeColor = talent.branchColor;
state = "Доступно";
}
else if (prerequisitesLearned && !enoughPoints)
{
nodeColor = UITheme.NotEnoughNode;
state = "Не хватает очков";
}
if (backgroundImage != null)
{
backgroundImage.color = new Color(nodeColor.r, nodeColor.g, nodeColor.b, learned || available ? 0.92f : 0.72f);
}
if (borderOutline != null)
{
borderOutline.effectColor = learned ? UITheme.Gold : (available ? Color.white : UITheme.LockedNode);
}
if (stateText != null)
{
stateText.text = state;
}
if (learnButton != null)
{
learnButton.interactable = available;
}
}
public void OnClickLearn()
{
if (manager != null && talent != null)
{
manager.LearnTalent(talent);
}
}
public void ShowTooltip()
{
if (tooltip == null)
{
tooltip = FindObjectOfType<TalentTooltipUI>(true);
}
if (tooltip != null)
{
tooltip.Show(talent, manager);
}
}
public void HideTooltip()
{
if (tooltip != null)
{
tooltip.Hide();
}
}
public void OnPointerEnter(PointerEventData eventData)
{
ShowTooltip();
}
public void OnPointerExit(PointerEventData eventData)
{
HideTooltip();
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2a5a7bbb12a718f3b66184c7d62d48f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+227
View File
@@ -0,0 +1,227 @@
using System.Text;
using UnityEngine;
using UnityEngine.UI;
public class TalentTooltipUI : MonoBehaviour
{
public RectTransform panelRoot;
public CanvasGroup canvasGroup;
public Text titleText;
public Text descriptionText;
public Text costText;
public Text modifiersText;
public Text requirementsText;
public Text statusText;
public Text reasonText;
public Vector2 screenOffset = new Vector2(22f, -18f);
private bool visible;
private void Awake()
{
if (panelRoot == null)
{
panelRoot = transform as RectTransform;
}
if (canvasGroup == null)
{
canvasGroup = GetComponent<CanvasGroup>();
}
Hide();
}
private void Update()
{
if (!visible || panelRoot == null)
{
return;
}
panelRoot.position = Input.mousePosition + new Vector3(screenOffset.x, screenOffset.y, 0f);
}
public void Show(TalentData talent, TalentManager manager)
{
if (talent == null)
{
return;
}
if (titleText != null)
{
titleText.text = talent.displayName;
}
if (descriptionText != null)
{
descriptionText.text = talent.description;
}
if (costText != null)
{
costText.text = $"Стоимость: {talent.cost}";
}
if (modifiersText != null)
{
modifiersText.text = BuildModifiersText(talent);
}
if (requirementsText != null)
{
requirementsText.text = BuildRequirementsText(talent);
}
if (statusText != null)
{
statusText.text = $"Статус: {BuildStatusText(talent, manager)}";
}
if (reasonText != null)
{
reasonText.text = manager != null ? manager.GetBlockingReason(talent) : string.Empty;
}
visible = true;
if (canvasGroup != null)
{
canvasGroup.alpha = 1f;
canvasGroup.blocksRaycasts = false;
}
if (panelRoot != null)
{
panelRoot.gameObject.SetActive(true);
panelRoot.SetAsLastSibling();
}
}
public void Hide()
{
visible = false;
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
canvasGroup.blocksRaycasts = false;
}
if (panelRoot != null)
{
panelRoot.gameObject.SetActive(false);
}
}
private string BuildModifiersText(TalentData talent)
{
if (talent.modifiers == null || talent.modifiers.Length == 0)
{
return "Модификаторы: нет";
}
StringBuilder builder = new StringBuilder();
builder.AppendLine("Модификаторы:");
for (int i = 0; i < talent.modifiers.Length; i++)
{
builder.AppendLine(FormatModifier(talent.modifiers[i]));
}
return builder.ToString().TrimEnd();
}
private string BuildRequirementsText(TalentData talent)
{
if (talent.prerequisites == null || talent.prerequisites.Length == 0)
{
return "Требования: нет";
}
StringBuilder builder = new StringBuilder("Требования: ");
for (int i = 0; i < talent.prerequisites.Length; i++)
{
if (i > 0)
{
builder.Append(", ");
}
TalentData prerequisite = talent.prerequisites[i];
builder.Append(prerequisite != null ? prerequisite.displayName : "Missing");
}
return builder.ToString();
}
private string BuildStatusText(TalentData talent, TalentManager manager)
{
if (manager == null)
{
return "неизвестно";
}
if (manager.IsLearned(talent))
{
return "изучено";
}
if (manager.CanLearn(talent))
{
return "доступно";
}
if (!manager.ArePrerequisitesLearned(talent))
{
return "заблокировано";
}
return "не хватает очков";
}
private string FormatModifier(StatModifier modifier)
{
string statName = FormatStatName(modifier.statType);
string valueText;
if (modifier.modifierType == ModifierType.PercentAdd || modifier.modifierType == ModifierType.PercentMultiply)
{
valueText = FormatSignedNumber(modifier.value * 100f) + "%";
}
else if (modifier.statType == StatType.AbilityCooldownReduction || modifier.statType == StatType.CriticalChance)
{
valueText = FormatSignedNumber(modifier.value) + "%";
}
else
{
valueText = FormatSignedNumber(modifier.value);
}
return $"{statName} {valueText}";
}
private string FormatStatName(StatType statType)
{
switch (statType)
{
case StatType.MaxHealth:
return "Max Health";
case StatType.MaxMana:
return "Max Mana";
case StatType.Damage:
return "Damage";
case StatType.MoveSpeed:
return "Move Speed";
case StatType.AbilityCooldownReduction:
return "Cooldown Reduction";
case StatType.CriticalChance:
return "Critical Chance";
default:
return statType.ToString();
}
}
private string FormatSignedNumber(float value)
{
string sign = value >= 0f ? "+" : string.Empty;
return sign + value.ToString("0.##");
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad8bf819a87ddc4de9584bb11236e5a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+279
View File
@@ -0,0 +1,279 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TalentTreeUI : MonoBehaviour
{
public TalentManager manager;
public PlayerStats playerStats;
[Header("Tree")]
public GameObject panelRoot;
public RectTransform lineRoot;
public RectTransform nodesRoot;
public TalentNodeUI nodePrefab;
public TalentTooltipUI tooltip;
[Header("Panels")]
public Text titleText;
public Text pointsText;
public Text controlsText;
public StatsPanelUI statsPanel;
[Header("Buttons")]
public Button saveButton;
public Button loadButton;
public Button resetButton;
public bool openOnStart;
private readonly List<TalentNodeUI> nodes = new List<TalentNodeUI>();
private readonly List<LineConnectorUI> lines = new List<LineConnectorUI>();
private bool isOpen;
private void Awake()
{
if (manager == null)
{
manager = FindObjectOfType<TalentManager>();
}
if (playerStats == null)
{
playerStats = FindObjectOfType<PlayerStats>();
}
}
private void OnEnable()
{
if (manager != null)
{
manager.OnTalentStateChanged += Refresh;
}
}
private void OnDisable()
{
if (manager != null)
{
manager.OnTalentStateChanged -= Refresh;
}
}
private void Start()
{
BindButtons();
BuildTree();
Refresh();
SetOpen(openOnStart);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.K))
{
Toggle();
}
}
public void Toggle()
{
SetOpen(!isOpen);
}
public void SetOpen(bool open)
{
isOpen = open;
if (panelRoot != null)
{
panelRoot.SetActive(open);
}
if (open)
{
Refresh();
UpdateLines();
}
}
public void BuildTree()
{
ClearChildren(nodesRoot);
ClearChildren(lineRoot);
nodes.Clear();
lines.Clear();
if (manager == null || nodePrefab == null || nodesRoot == null)
{
return;
}
Dictionary<TalentData, TalentNodeUI> nodeByTalent = new Dictionary<TalentData, TalentNodeUI>();
for (int i = 0; i < manager.allTalents.Count; i++)
{
TalentData talent = manager.allTalents[i];
if (talent == null)
{
continue;
}
TalentNodeUI node = Instantiate(nodePrefab, nodesRoot);
node.gameObject.SetActive(true);
node.tooltip = tooltip;
node.Setup(talent, manager);
RectTransform rectTransform = node.transform as RectTransform;
if (rectTransform != null)
{
rectTransform.anchoredPosition = talent.treePosition;
}
nodes.Add(node);
nodeByTalent[talent] = node;
}
if (lineRoot == null)
{
return;
}
for (int i = 0; i < manager.allTalents.Count; i++)
{
TalentData talent = manager.allTalents[i];
if (talent == null || talent.prerequisites == null)
{
continue;
}
for (int prerequisiteIndex = 0; prerequisiteIndex < talent.prerequisites.Length; prerequisiteIndex++)
{
TalentData prerequisite = talent.prerequisites[prerequisiteIndex];
if (prerequisite == null || !nodeByTalent.ContainsKey(prerequisite) || !nodeByTalent.ContainsKey(talent))
{
continue;
}
LineConnectorUI line = CreateLine(lineRoot, prerequisite, nodeByTalent[prerequisite], nodeByTalent[talent]);
lines.Add(line);
}
}
UpdateLines();
}
public void Refresh()
{
if (titleText != null)
{
titleText.text = "Дерево талантов";
}
if (pointsText != null)
{
int points = manager != null ? manager.GetAvailableTalentPoints() : 0;
pointsText.text = $"Очки талантов: {points}";
}
if (controlsText != null)
{
controlsText.text = "K - дерево X - +50 XP T - +1 очко E - кристалл F5 - сохранить F9 - загрузить Backspace - сброс";
}
for (int i = 0; i < nodes.Count; i++)
{
if (nodes[i] != null)
{
nodes[i].Refresh();
}
}
if (statsPanel != null)
{
statsPanel.Refresh();
}
UpdateLines();
}
private void BindButtons()
{
if (saveButton != null)
{
saveButton.onClick.RemoveAllListeners();
saveButton.onClick.AddListener(() =>
{
if (manager != null)
{
manager.SaveProgress();
}
});
}
if (loadButton != null)
{
loadButton.onClick.RemoveAllListeners();
loadButton.onClick.AddListener(() =>
{
if (manager != null)
{
manager.LoadProgress();
}
});
}
if (resetButton != null)
{
resetButton.onClick.RemoveAllListeners();
resetButton.onClick.AddListener(() =>
{
if (manager != null)
{
manager.ResetProgress();
}
});
}
}
private LineConnectorUI CreateLine(RectTransform parent, TalentData childTalent, TalentNodeUI startNode, TalentNodeUI endNode)
{
GameObject lineObject = new GameObject($"Line_{startNode.talent.talentId}_to_{childTalent.talentId}", typeof(RectTransform), typeof(Image), typeof(LineConnectorUI));
lineObject.transform.SetParent(parent, false);
Image image = lineObject.GetComponent<Image>();
Color color = childTalent.branchColor;
image.color = new Color(color.r, color.g, color.b, 0.72f);
image.raycastTarget = false;
LineConnectorUI connector = lineObject.GetComponent<LineConnectorUI>();
connector.lineImage = image;
connector.thickness = 5f;
connector.dynamicUpdate = true;
connector.SetEndpoints(startNode.transform as RectTransform, endNode.transform as RectTransform);
return connector;
}
private void UpdateLines()
{
Canvas.ForceUpdateCanvases();
for (int i = 0; i < lines.Count; i++)
{
if (lines[i] != null)
{
lines[i].UpdateLine();
}
}
}
private void ClearChildren(RectTransform root)
{
if (root == null)
{
return;
}
for (int i = root.childCount - 1; i >= 0; i--)
{
Destroy(root.GetChild(i).gameObject);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d35bf66a09a03cd7ab53136fcb7d2a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+15
View File
@@ -0,0 +1,15 @@
using UnityEngine;
public static class UITheme
{
public static readonly Color Panel = new Color(0.06f, 0.07f, 0.09f, 0.92f);
public static readonly Color PanelSoft = new Color(0.09f, 0.10f, 0.13f, 0.86f);
public static readonly Color Gold = new Color(1.00f, 0.72f, 0.24f, 1f);
public static readonly Color Text = new Color(0.94f, 0.92f, 0.86f, 1f);
public static readonly Color MutedText = new Color(0.70f, 0.72f, 0.76f, 1f);
public static readonly Color LearnedNode = new Color(0.19f, 0.66f, 0.34f, 1f);
public static readonly Color AvailableNode = new Color(0.86f, 0.58f, 0.18f, 1f);
public static readonly Color LockedNode = new Color(0.28f, 0.29f, 0.32f, 1f);
public static readonly Color NotEnoughNode = new Color(0.14f, 0.15f, 0.18f, 1f);
public static readonly Color Danger = new Color(0.90f, 0.28f, 0.23f, 1f);
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ce84f6aceb15dff29ba0e6028b52677
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: