first commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b06c226736be0a9ca6bbd2946cb7fd2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b84fdc271cff83cc985aac81f1ce2c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class ExperiencePickup : MonoBehaviour
|
||||
{
|
||||
public int experienceAmount = 50;
|
||||
public bool destroyOnCollect = true;
|
||||
public float respawnCooldown = 12f;
|
||||
public float rotationSpeed = 70f;
|
||||
|
||||
public LevelSystem levelSystem;
|
||||
public NotificationUI notificationUI;
|
||||
|
||||
private bool playerInRange;
|
||||
private bool collected;
|
||||
private Renderer[] renderers;
|
||||
private Collider[] colliders;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
renderers = GetComponentsInChildren<Renderer>();
|
||||
colliders = GetComponentsInChildren<Collider>();
|
||||
|
||||
if (levelSystem == null)
|
||||
{
|
||||
levelSystem = FindObjectOfType<LevelSystem>();
|
||||
}
|
||||
|
||||
if (notificationUI == null)
|
||||
{
|
||||
notificationUI = FindObjectOfType<NotificationUI>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime, Space.World);
|
||||
|
||||
if (playerInRange && !collected && Input.GetKeyDown(KeyCode.E))
|
||||
{
|
||||
Collect();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.GetComponentInParent<SimplePlayerController>() != null)
|
||||
{
|
||||
playerInRange = true;
|
||||
if (notificationUI != null)
|
||||
{
|
||||
notificationUI.Show("Нажмите E, чтобы забрать кристалл опыта");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (other.GetComponentInParent<SimplePlayerController>() != null)
|
||||
{
|
||||
playerInRange = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Collect()
|
||||
{
|
||||
collected = true;
|
||||
|
||||
if (levelSystem != null)
|
||||
{
|
||||
levelSystem.AddExperience(experienceAmount);
|
||||
}
|
||||
|
||||
if (notificationUI != null)
|
||||
{
|
||||
notificationUI.Show($"Кристалл опыта: +{experienceAmount} XP");
|
||||
}
|
||||
|
||||
if (destroyOnCollect)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartCoroutine(RespawnRoutine());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator RespawnRoutine()
|
||||
{
|
||||
SetVisible(false);
|
||||
yield return new WaitForSeconds(Mathf.Max(1f, respawnCooldown));
|
||||
collected = false;
|
||||
SetVisible(true);
|
||||
}
|
||||
|
||||
private void SetVisible(bool visible)
|
||||
{
|
||||
for (int i = 0; i < renderers.Length; i++)
|
||||
{
|
||||
if (renderers[i] != null)
|
||||
{
|
||||
renderers[i].enabled = visible;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < colliders.Length; i++)
|
||||
{
|
||||
if (colliders[i] != null)
|
||||
{
|
||||
colliders[i].enabled = visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 128b26ace3a81b67c825108a47f2a42c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class LevelSystem : MonoBehaviour
|
||||
{
|
||||
[Header("Progression")]
|
||||
public int level = 1;
|
||||
public int currentExp = 0;
|
||||
public int expToNextLevel = 100;
|
||||
|
||||
[Header("Dependencies")]
|
||||
public TalentManager talentManager;
|
||||
public NotificationUI notificationUI;
|
||||
|
||||
[Header("HUD")]
|
||||
public Text levelText;
|
||||
public Text expText;
|
||||
public Image expFillImage;
|
||||
|
||||
public event Action OnExperienceChanged;
|
||||
public event Action OnLevelChanged;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (talentManager == null)
|
||||
{
|
||||
talentManager = FindObjectOfType<TalentManager>();
|
||||
}
|
||||
|
||||
if (notificationUI == null)
|
||||
{
|
||||
notificationUI = FindObjectOfType<NotificationUI>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.X))
|
||||
{
|
||||
AddExperience(50);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddExperience(int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentExp += amount;
|
||||
Notify($"Получено опыта: {amount}");
|
||||
|
||||
while (currentExp >= expToNextLevel)
|
||||
{
|
||||
currentExp -= expToNextLevel;
|
||||
LevelUp();
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
OnExperienceChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void LevelUp()
|
||||
{
|
||||
level = Mathf.Max(1, level + 1);
|
||||
expToNextLevel = Mathf.Max(1, Mathf.RoundToInt(expToNextLevel * 1.25f + 25f));
|
||||
|
||||
if (talentManager != null)
|
||||
{
|
||||
talentManager.AddTalentPoints(1);
|
||||
}
|
||||
|
||||
Notify($"Уровень {level}! Получено очко талантов");
|
||||
RefreshUI();
|
||||
OnLevelChanged?.Invoke();
|
||||
}
|
||||
|
||||
public float GetExpNormalized()
|
||||
{
|
||||
if (expToNextLevel <= 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return Mathf.Clamp01((float)currentExp / expToNextLevel);
|
||||
}
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
if (levelText != null)
|
||||
{
|
||||
levelText.text = $"Уровень {level}";
|
||||
}
|
||||
|
||||
if (expText != null)
|
||||
{
|
||||
expText.text = $"{currentExp} / {expToNextLevel} XP";
|
||||
}
|
||||
|
||||
if (expFillImage != null)
|
||||
{
|
||||
expFillImage.fillAmount = GetExpNormalized();
|
||||
}
|
||||
}
|
||||
|
||||
private void Notify(string message)
|
||||
{
|
||||
if (notificationUI != null)
|
||||
{
|
||||
notificationUI.Show(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca4263d1e4dbc27e59409154d02bbb6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class SimplePlayerController : MonoBehaviour
|
||||
{
|
||||
public PlayerStats playerStats;
|
||||
public CharacterController characterController;
|
||||
public Transform cameraTransform;
|
||||
|
||||
[Header("Movement")]
|
||||
public float gravity = -25f;
|
||||
public float rotationSpeed = 12f;
|
||||
|
||||
[Header("Camera")]
|
||||
public Vector3 cameraOffset = new Vector3(0f, 9f, -8f);
|
||||
public float cameraYaw = 45f;
|
||||
public float cameraSmooth = 12f;
|
||||
|
||||
private float verticalVelocity;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (characterController == null)
|
||||
{
|
||||
characterController = GetComponent<CharacterController>();
|
||||
}
|
||||
|
||||
if (playerStats == null)
|
||||
{
|
||||
playerStats = GetComponent<PlayerStats>();
|
||||
}
|
||||
|
||||
if (cameraTransform == null && Camera.main != null)
|
||||
{
|
||||
cameraTransform = Camera.main.transform;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetMouseButton(1))
|
||||
{
|
||||
cameraYaw += Input.GetAxis("Mouse X") * 120f * Time.deltaTime;
|
||||
}
|
||||
|
||||
Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
|
||||
input = Vector3.ClampMagnitude(input, 1f);
|
||||
|
||||
Quaternion yawRotation = Quaternion.Euler(0f, cameraYaw, 0f);
|
||||
Vector3 moveDirection = yawRotation * input;
|
||||
|
||||
if (characterController.isGrounded && verticalVelocity < 0f)
|
||||
{
|
||||
verticalVelocity = -1f;
|
||||
}
|
||||
|
||||
verticalVelocity += gravity * Time.deltaTime;
|
||||
|
||||
float speed = playerStats != null ? playerStats.MoveSpeed : 5f;
|
||||
Vector3 velocity = moveDirection * speed;
|
||||
velocity.y = verticalVelocity;
|
||||
characterController.Move(velocity * Time.deltaTime);
|
||||
|
||||
if (moveDirection.sqrMagnitude > 0.001f)
|
||||
{
|
||||
Quaternion targetRotation = Quaternion.LookRotation(moveDirection, Vector3.up);
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (cameraTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Quaternion yawRotation = Quaternion.Euler(0f, cameraYaw, 0f);
|
||||
Vector3 desiredPosition = transform.position + yawRotation * cameraOffset;
|
||||
cameraTransform.position = Vector3.Lerp(cameraTransform.position, desiredPosition, cameraSmooth * Time.deltaTime);
|
||||
cameraTransform.LookAt(transform.position + Vector3.up * 1.3f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23ff40a71fbf048f08376f5796c86de7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee116732209f61cd3bffeba190d81c0d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[Serializable]
|
||||
public class TalentSaveData
|
||||
{
|
||||
public int availableTalentPoints;
|
||||
public List<string> learnedTalentIds = new List<string>();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9f0ba340003e18279312b5cde767c91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bc4b0205c2ec4bd09c773e84ba9deca
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
public enum ModifierType
|
||||
{
|
||||
FlatAdd,
|
||||
PercentAdd,
|
||||
PercentMultiply
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17d0bd4f5e38d384e85abdd7bb8a32db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerStats : MonoBehaviour
|
||||
{
|
||||
[Header("Base Values")]
|
||||
[SerializeField] private float baseMaxHealth = 100f;
|
||||
[SerializeField] private float baseMaxMana = 100f;
|
||||
[SerializeField] private float baseDamage = 10f;
|
||||
[SerializeField] private float baseMoveSpeed = 5f;
|
||||
[SerializeField] private float baseAbilityCooldownReduction = 0f;
|
||||
[SerializeField] private float baseCriticalChance = 0f;
|
||||
|
||||
[Header("Runtime Modifiers")]
|
||||
[SerializeField] private List<StatModifier> activeModifiers = new List<StatModifier>();
|
||||
|
||||
private readonly Dictionary<StatType, float> baseValues = new Dictionary<StatType, float>();
|
||||
private readonly Dictionary<StatType, float> currentValues = new Dictionary<StatType, float>();
|
||||
|
||||
public event Action OnStatsChanged;
|
||||
|
||||
public float MaxHealth { get { return GetStat(StatType.MaxHealth); } }
|
||||
public float MaxMana { get { return GetStat(StatType.MaxMana); } }
|
||||
public float Damage { get { return GetStat(StatType.Damage); } }
|
||||
public float MoveSpeed { get { return GetStat(StatType.MoveSpeed); } }
|
||||
public float AbilityCooldownReduction { get { return GetStat(StatType.AbilityCooldownReduction); } }
|
||||
public float CriticalChance { get { return GetStat(StatType.CriticalChance); } }
|
||||
|
||||
// Compatibility-friendly alias for lab wording: player speed is driven by PlayerStats.speed.
|
||||
public float speed { get { return MoveSpeed; } }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ClampBaseValues();
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
public IReadOnlyList<StatModifier> GetActiveModifiers()
|
||||
{
|
||||
return activeModifiers;
|
||||
}
|
||||
|
||||
public float GetBaseStat(StatType statType)
|
||||
{
|
||||
EnsureBaseValues();
|
||||
return baseValues.TryGetValue(statType, out float value) ? value : 0f;
|
||||
}
|
||||
|
||||
public float GetStat(StatType statType)
|
||||
{
|
||||
if (currentValues.Count == 0)
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
return currentValues.TryGetValue(statType, out float value) ? value : GetBaseStat(statType);
|
||||
}
|
||||
|
||||
public void AddModifier(StatModifier modifier)
|
||||
{
|
||||
if (!IsValidNumber(modifier.value))
|
||||
{
|
||||
Debug.LogWarning($"Ignored invalid modifier value for {modifier.statType}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(modifier.sourceId))
|
||||
{
|
||||
modifier.sourceId = "Runtime";
|
||||
}
|
||||
|
||||
activeModifiers.Add(modifier);
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
public int RemoveModifiersBySource(string sourceId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceId))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed = activeModifiers.RemoveAll(modifier => modifier.sourceId == sourceId);
|
||||
if (removed > 0)
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public int RemoveModifiersBySourcePrefix(string sourcePrefix)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourcePrefix))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed = activeModifiers.RemoveAll(modifier =>
|
||||
!string.IsNullOrEmpty(modifier.sourceId) &&
|
||||
modifier.sourceId.StartsWith(sourcePrefix, StringComparison.Ordinal));
|
||||
|
||||
if (removed > 0)
|
||||
{
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void ClearModifiers()
|
||||
{
|
||||
if (activeModifiers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeModifiers.Clear();
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
public void RecalculateStats()
|
||||
{
|
||||
ClampBaseValues();
|
||||
EnsureBaseValues();
|
||||
currentValues.Clear();
|
||||
|
||||
foreach (StatType statType in Enum.GetValues(typeof(StatType)))
|
||||
{
|
||||
float baseValue = GetBaseStat(statType);
|
||||
float flatAdd = 0f;
|
||||
float percentAdd = 0f;
|
||||
float percentMultiply = 1f;
|
||||
|
||||
for (int i = 0; i < activeModifiers.Count; i++)
|
||||
{
|
||||
StatModifier modifier = activeModifiers[i];
|
||||
if (modifier.statType != statType || !IsValidNumber(modifier.value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (modifier.modifierType)
|
||||
{
|
||||
case ModifierType.FlatAdd:
|
||||
flatAdd += modifier.value;
|
||||
break;
|
||||
case ModifierType.PercentAdd:
|
||||
percentAdd += modifier.value;
|
||||
break;
|
||||
case ModifierType.PercentMultiply:
|
||||
percentMultiply *= Mathf.Max(0f, 1f + modifier.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float finalValue = (baseValue + flatAdd) * Mathf.Max(0f, 1f + percentAdd) * percentMultiply;
|
||||
currentValues[statType] = ClampFinalValue(statType, finalValue);
|
||||
}
|
||||
|
||||
OnStatsChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void EnsureBaseValues()
|
||||
{
|
||||
baseValues[StatType.MaxHealth] = Mathf.Max(0f, baseMaxHealth);
|
||||
baseValues[StatType.MaxMana] = Mathf.Max(0f, baseMaxMana);
|
||||
baseValues[StatType.Damage] = Mathf.Max(0f, baseDamage);
|
||||
baseValues[StatType.MoveSpeed] = Mathf.Max(0.1f, baseMoveSpeed);
|
||||
baseValues[StatType.AbilityCooldownReduction] = Mathf.Clamp(baseAbilityCooldownReduction, 0f, 100f);
|
||||
baseValues[StatType.CriticalChance] = Mathf.Clamp(baseCriticalChance, 0f, 100f);
|
||||
}
|
||||
|
||||
private void ClampBaseValues()
|
||||
{
|
||||
baseMaxHealth = Mathf.Max(0f, baseMaxHealth);
|
||||
baseMaxMana = Mathf.Max(0f, baseMaxMana);
|
||||
baseDamage = Mathf.Max(0f, baseDamage);
|
||||
baseMoveSpeed = Mathf.Max(0.1f, baseMoveSpeed);
|
||||
baseAbilityCooldownReduction = Mathf.Clamp(baseAbilityCooldownReduction, 0f, 100f);
|
||||
baseCriticalChance = Mathf.Clamp(baseCriticalChance, 0f, 100f);
|
||||
}
|
||||
|
||||
private float ClampFinalValue(StatType statType, float value)
|
||||
{
|
||||
if (!IsValidNumber(value))
|
||||
{
|
||||
return GetBaseStat(statType);
|
||||
}
|
||||
|
||||
switch (statType)
|
||||
{
|
||||
case StatType.MoveSpeed:
|
||||
return Mathf.Max(0.1f, value);
|
||||
case StatType.AbilityCooldownReduction:
|
||||
case StatType.CriticalChance:
|
||||
return Mathf.Clamp(value, 0f, 100f);
|
||||
default:
|
||||
return Mathf.Max(0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidNumber(float value)
|
||||
{
|
||||
return !float.IsNaN(value) && !float.IsInfinity(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bea8fb1929119353ea53e65980138a53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
[Serializable]
|
||||
public struct StatModifier
|
||||
{
|
||||
public StatType statType;
|
||||
public ModifierType modifierType;
|
||||
public float value;
|
||||
public string sourceId;
|
||||
|
||||
public StatModifier(StatType statType, ModifierType modifierType, float value, string sourceId = "")
|
||||
{
|
||||
this.statType = statType;
|
||||
this.modifierType = modifierType;
|
||||
this.value = value;
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48d0d2852923565a596a441444ff0153
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
public enum StatType
|
||||
{
|
||||
MaxHealth,
|
||||
MaxMana,
|
||||
Damage,
|
||||
MoveSpeed,
|
||||
AbilityCooldownReduction,
|
||||
CriticalChance
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f45916503d6cc198badac0de1d5845f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 998a69656c6d3d932a74831b91629ba4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "TalentData", menuName = "Talents/TalentData")]
|
||||
public class TalentData : ScriptableObject
|
||||
{
|
||||
public string talentId;
|
||||
public string displayName;
|
||||
[TextArea(2, 5)] public string description;
|
||||
public Sprite icon;
|
||||
public int cost = 1;
|
||||
public TalentData[] prerequisites;
|
||||
public StatModifier[] modifiers;
|
||||
public Vector2 treePosition;
|
||||
public Color branchColor = Color.white;
|
||||
public bool unlockedByDefault;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
cost = Mathf.Max(0, cost);
|
||||
if (string.IsNullOrWhiteSpace(talentId))
|
||||
{
|
||||
talentId = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bd9889a6ab0a4e0a9a5e3d7a82343ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,408 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class TalentManager : MonoBehaviour
|
||||
{
|
||||
private const string SaveKey = "Lab07_TalentProgress";
|
||||
private const string TalentSourcePrefix = "Talent:";
|
||||
|
||||
[Header("Data")]
|
||||
public List<TalentData> allTalents = new List<TalentData>();
|
||||
public PlayerStats playerStats;
|
||||
|
||||
[Header("Runtime Progress")]
|
||||
public HashSet<string> learnedTalentIds = new HashSet<string>();
|
||||
public int availableTalentPoints = 1;
|
||||
public int startingTalentPoints = 1;
|
||||
|
||||
[Header("Feedback")]
|
||||
public NotificationUI notificationUI;
|
||||
|
||||
private readonly Dictionary<string, TalentData> talentLookup = new Dictionary<string, TalentData>();
|
||||
|
||||
public event Action OnTalentStateChanged;
|
||||
public event Action<string> OnNotificationRequested;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (playerStats == null)
|
||||
{
|
||||
playerStats = FindObjectOfType<PlayerStats>();
|
||||
}
|
||||
|
||||
if (notificationUI == null)
|
||||
{
|
||||
notificationUI = FindObjectOfType<NotificationUI>();
|
||||
}
|
||||
|
||||
BuildTalentLookup();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
LoadProgress(false);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.T))
|
||||
{
|
||||
AddTalentPoints(1);
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.F5))
|
||||
{
|
||||
SaveProgress();
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.F9))
|
||||
{
|
||||
LoadProgress();
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.Backspace))
|
||||
{
|
||||
ResetProgress();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLearned(TalentData talent)
|
||||
{
|
||||
return IsValidTalent(talent) && learnedTalentIds.Contains(talent.talentId);
|
||||
}
|
||||
|
||||
public bool CanLearn(TalentData talent)
|
||||
{
|
||||
return IsValidTalent(talent) &&
|
||||
!IsLearned(talent) &&
|
||||
availableTalentPoints >= Mathf.Max(0, talent.cost) &&
|
||||
ArePrerequisitesLearned(talent);
|
||||
}
|
||||
|
||||
public bool ArePrerequisitesLearned(TalentData talent)
|
||||
{
|
||||
if (!IsValidTalent(talent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (talent.prerequisites == null || talent.prerequisites.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < talent.prerequisites.Length; i++)
|
||||
{
|
||||
TalentData prerequisite = talent.prerequisites[i];
|
||||
if (!IsValidTalent(prerequisite) || !IsLearned(prerequisite))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void LearnTalent(TalentData talent)
|
||||
{
|
||||
if (!IsValidTalent(talent))
|
||||
{
|
||||
Notify("Некорректный талант");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsLearned(talent))
|
||||
{
|
||||
Notify($"Талант уже изучен: {talent.displayName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ArePrerequisitesLearned(talent))
|
||||
{
|
||||
Notify(GetBlockingReason(talent));
|
||||
return;
|
||||
}
|
||||
|
||||
int cost = Mathf.Max(0, talent.cost);
|
||||
if (availableTalentPoints < cost)
|
||||
{
|
||||
Notify("Недостаточно очков талантов");
|
||||
return;
|
||||
}
|
||||
|
||||
availableTalentPoints -= cost;
|
||||
learnedTalentIds.Add(talent.talentId);
|
||||
ApplyTalentModifiers(talent);
|
||||
SaveProgress(false);
|
||||
Notify($"Изучен талант: {talent.displayName}");
|
||||
RaiseTalentStateChanged();
|
||||
}
|
||||
|
||||
public void AddTalentPoints(int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
availableTalentPoints = Mathf.Max(0, availableTalentPoints + amount);
|
||||
Notify(amount == 1 ? "Получено очко талантов" : $"Получено очков талантов: {amount}");
|
||||
RaiseTalentStateChanged();
|
||||
}
|
||||
|
||||
public void ApplyTalentModifiers(TalentData talent)
|
||||
{
|
||||
if (!IsValidTalent(talent) || playerStats == null || talent.modifiers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string sourceId = GetModifierSourceId(talent);
|
||||
playerStats.RemoveModifiersBySource(sourceId);
|
||||
|
||||
for (int i = 0; i < talent.modifiers.Length; i++)
|
||||
{
|
||||
StatModifier modifier = talent.modifiers[i];
|
||||
modifier.sourceId = sourceId;
|
||||
playerStats.AddModifier(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
public void RebuildAllModifiers()
|
||||
{
|
||||
if (playerStats == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
playerStats.RemoveModifiersBySourcePrefix(TalentSourcePrefix);
|
||||
|
||||
for (int i = 0; i < allTalents.Count; i++)
|
||||
{
|
||||
TalentData talent = allTalents[i];
|
||||
if (IsLearned(talent))
|
||||
{
|
||||
ApplyTalentModifiers(talent);
|
||||
}
|
||||
}
|
||||
|
||||
playerStats.RecalculateStats();
|
||||
RaiseTalentStateChanged();
|
||||
}
|
||||
|
||||
public void SaveProgress()
|
||||
{
|
||||
SaveProgress(true);
|
||||
}
|
||||
|
||||
public void LoadProgress()
|
||||
{
|
||||
LoadProgress(true);
|
||||
}
|
||||
|
||||
public void ResetProgress()
|
||||
{
|
||||
PlayerPrefs.DeleteKey(SaveKey);
|
||||
ResetRuntimeToDefaults();
|
||||
RebuildAllModifiers();
|
||||
Notify("Прогресс сброшен");
|
||||
RaiseTalentStateChanged();
|
||||
}
|
||||
|
||||
public int GetAvailableTalentPoints()
|
||||
{
|
||||
return availableTalentPoints;
|
||||
}
|
||||
|
||||
public List<TalentData> GetLearnedTalents()
|
||||
{
|
||||
BuildTalentLookup();
|
||||
return allTalents.Where(IsLearned).ToList();
|
||||
}
|
||||
|
||||
public string GetBlockingReason(TalentData talent)
|
||||
{
|
||||
if (!IsValidTalent(talent))
|
||||
{
|
||||
return "Некорректные данные таланта";
|
||||
}
|
||||
|
||||
if (IsLearned(talent))
|
||||
{
|
||||
return "Талант уже изучен";
|
||||
}
|
||||
|
||||
if (talent.prerequisites != null)
|
||||
{
|
||||
for (int i = 0; i < talent.prerequisites.Length; i++)
|
||||
{
|
||||
TalentData prerequisite = talent.prerequisites[i];
|
||||
if (!IsValidTalent(prerequisite))
|
||||
{
|
||||
return "Некорректное требование таланта";
|
||||
}
|
||||
|
||||
if (!IsLearned(prerequisite))
|
||||
{
|
||||
return $"Требуется талант: {prerequisite.displayName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (availableTalentPoints < Mathf.Max(0, talent.cost))
|
||||
{
|
||||
return "Недостаточно очков талантов";
|
||||
}
|
||||
|
||||
return "Доступно";
|
||||
}
|
||||
|
||||
public TalentData GetTalentById(string talentId)
|
||||
{
|
||||
BuildTalentLookup();
|
||||
if (string.IsNullOrWhiteSpace(talentId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
talentLookup.TryGetValue(talentId, out TalentData talent);
|
||||
return talent;
|
||||
}
|
||||
|
||||
private void SaveProgress(bool notify)
|
||||
{
|
||||
TalentSaveData saveData = new TalentSaveData
|
||||
{
|
||||
availableTalentPoints = Mathf.Max(0, availableTalentPoints),
|
||||
learnedTalentIds = learnedTalentIds
|
||||
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||
.OrderBy(id => id, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
PlayerPrefs.SetString(SaveKey, JsonUtility.ToJson(saveData));
|
||||
PlayerPrefs.Save();
|
||||
|
||||
if (notify)
|
||||
{
|
||||
Notify("Прогресс сохранён");
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadProgress(bool notify)
|
||||
{
|
||||
BuildTalentLookup();
|
||||
learnedTalentIds.Clear();
|
||||
|
||||
if (PlayerPrefs.HasKey(SaveKey))
|
||||
{
|
||||
try
|
||||
{
|
||||
TalentSaveData saveData = JsonUtility.FromJson<TalentSaveData>(PlayerPrefs.GetString(SaveKey));
|
||||
availableTalentPoints = Mathf.Max(0, saveData != null ? saveData.availableTalentPoints : startingTalentPoints);
|
||||
|
||||
if (saveData != null && saveData.learnedTalentIds != null)
|
||||
{
|
||||
for (int i = 0; i < saveData.learnedTalentIds.Count; i++)
|
||||
{
|
||||
string talentId = saveData.learnedTalentIds[i];
|
||||
if (talentLookup.ContainsKey(talentId))
|
||||
{
|
||||
learnedTalentIds.Add(talentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogWarning($"Talent progress was corrupted and will be reset. {exception.Message}");
|
||||
ResetRuntimeToDefaults();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetRuntimeToDefaults();
|
||||
}
|
||||
|
||||
EnsureDefaultTalentsLearned();
|
||||
RebuildAllModifiers();
|
||||
|
||||
if (notify)
|
||||
{
|
||||
Notify("Прогресс загружен");
|
||||
}
|
||||
|
||||
RaiseTalentStateChanged();
|
||||
}
|
||||
|
||||
private void ResetRuntimeToDefaults()
|
||||
{
|
||||
learnedTalentIds.Clear();
|
||||
availableTalentPoints = Mathf.Max(0, startingTalentPoints);
|
||||
EnsureDefaultTalentsLearned();
|
||||
}
|
||||
|
||||
private void EnsureDefaultTalentsLearned()
|
||||
{
|
||||
for (int i = 0; i < allTalents.Count; i++)
|
||||
{
|
||||
TalentData talent = allTalents[i];
|
||||
if (IsValidTalent(talent) && talent.unlockedByDefault)
|
||||
{
|
||||
learnedTalentIds.Add(talent.talentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildTalentLookup()
|
||||
{
|
||||
talentLookup.Clear();
|
||||
for (int i = 0; i < allTalents.Count; i++)
|
||||
{
|
||||
TalentData talent = allTalents[i];
|
||||
if (!IsValidTalent(talent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (talentLookup.ContainsKey(talent.talentId))
|
||||
{
|
||||
Debug.LogWarning($"Duplicate talent id ignored: {talent.talentId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
talentLookup.Add(talent.talentId, talent);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidTalent(TalentData talent)
|
||||
{
|
||||
return talent != null && !string.IsNullOrWhiteSpace(talent.talentId);
|
||||
}
|
||||
|
||||
private string GetModifierSourceId(TalentData talent)
|
||||
{
|
||||
return TalentSourcePrefix + talent.talentId;
|
||||
}
|
||||
|
||||
private void RaiseTalentStateChanged()
|
||||
{
|
||||
OnTalentStateChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void Notify(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnNotificationRequested?.Invoke(message);
|
||||
if (notificationUI != null)
|
||||
{
|
||||
notificationUI.Show(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edd6cb3143345fd4a982292c062fd6d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8eaf4d60fca44a8a9193c9d0368fcb2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9af78e14524d49c8942a7b8ab1dc61d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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.#}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2abe10fb9912e35b99a6e90c1840beb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2a5a7bbb12a718f3b66184c7d62d48f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d35bf66a09a03cd7ab53136fcb7d2a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ce84f6aceb15dff29ba0e6028b52677
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user