Refactor character attributes system; replace individual attributes with a dictionary-based approach for better scalability and maintainability

This commit is contained in:
2025-08-02 06:06:51 +02:00
parent 93cbc4a3e5
commit 3871cba753
21 changed files with 505 additions and 417 deletions

View File

@@ -130,19 +130,20 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
attributes:
health: 10
maxHealth: 10
moveSpeed: 2
luck: 0
armor: 0
level: 1
experience: 0
baseExperienceToLevelUp: 100
damage: 1
rangedDamage: 1
meleeDamage: 1
attackRange: 1
attackSpeed: 1
serializedAttributes:
- key: 0
- key: 1
- key: 2
- key: 3
- key: 4
- key: 5
- key: 6
- key: 7
- key: 8
- key: 9
- key: 10
- key: 11
- key: 12
--- !u!114 &9093693903318130491
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@@ -7087,20 +7087,281 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 44b572240c4243c9954bd19967bfeb64, type: 3}
m_Name:
m_EditorClassIdentifier:
attributes:
health: 100
maxHealth: 100
moveSpeed: 5
luck: 0
armor: 0
level: 1
experience: 0
baseExperienceToLevelUp: 5
damage: 1
rangedDamage: 1
meleeDamage: 1
attackRange: 1
attackSpeed: 1
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: attributes
Entry: 7
Data: 0|Data.CharacterAttributes, Assembly-CSharp
- Name: attributes
Entry: 7
Data: 1|System.Collections.Generic.Dictionary`2[[Data.Attribute, Assembly-CSharp],[Data.AttributeData`1[[System.Single,
mscorlib]], Assembly-CSharp]], mscorlib
- Name: comparer
Entry: 7
Data: 2|System.Collections.Generic.EnumEqualityComparer`1[[Data.Attribute,
Assembly-CSharp]], mscorlib
- Name:
Entry: 12
Data: 0
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 12
Data: 13
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 0
- Name: $v
Entry: 7
Data: 3|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 100
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 1
- Name: $v
Entry: 7
Data: 4|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 100
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 2
- Name: $v
Entry: 7
Data: 5|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 1
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 3
- Name: $v
Entry: 7
Data: 6|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 0
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 4
- Name: $v
Entry: 7
Data: 7|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 1
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 5
- Name: $v
Entry: 7
Data: 8|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 5
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 6
- Name: $v
Entry: 7
Data: 9|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 1
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 7
- Name: $v
Entry: 7
Data: 10|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 1
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 8
- Name: $v
Entry: 7
Data: 11|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 1
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 9
- Name: $v
Entry: 7
Data: 12|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 1
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 10
- Name: $v
Entry: 7
Data: 13|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 1
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 11
- Name: $v
Entry: 7
Data: 14|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 0
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 3
Data: 12
- Name: $v
Entry: 7
Data: 15|Data.AttributeData`1[[System.Single, mscorlib]], Assembly-CSharp
- Name: Value
Entry: 4
Data: 100
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
--- !u!4 &1261447604
Transform:
m_ObjectHideFlags: 0

View File

@@ -4,6 +4,7 @@ using KBCore.Refs;
using Pathfinding;
using Systems;
using UnityEngine;
using Attribute = Data.Attribute;
namespace AI
{
@@ -27,7 +28,7 @@ namespace AI
{
if (!target || !aiPath) return;
aiPath.maxSpeed = character.attributes.MoveSpeed;
aiPath.maxSpeed = character.attributes.Get(Attribute.MoveSpeed);
aiPath.destination = target.position;
}

View File

@@ -0,0 +1,19 @@
namespace Data
{
public enum Attribute
{
Health,
MaxHealth,
MoveSpeed,
Luck,
Armor,
Damage,
RangedDamage,
MeleeDamage,
AttackRange,
AttackSpeed,
Level,
Experience,
BaseExperienceToNextLevel,
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9587f73172674bd1ad04d9e2327ad882
timeCreated: 1754103975

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
namespace Data
{
[Serializable]
public class AttributeData<T>
{
[OdinSerialize]
public T Value { get; private set; }
public event Action<T> OnChanged;
public void Set(T value)
{
if (!EqualityComparer<T>.Default.Equals(Value, value))
{
Value = value;
OnChanged?.Invoke(Value);
}
}
public void Modify(Func<T, T> modifier)
{
Set(modifier(Value));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dd15401c68cc46338799a2718a5e6960
timeCreated: 1754104026

View File

@@ -0,0 +1,18 @@
using System;
using Sirenix.OdinInspector;
namespace Data
{
[Serializable]
public class AttributeEntry
{
[HorizontalGroup("Split", 0.4f)]
[LabelWidth(100)]
public Attribute key;
[HorizontalGroup("Split", 0.6f)]
[LabelWidth(50)]
[InlineProperty]
public AttributeData<float> value;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 056d8fc09bcc477fba9ff895cde4bb6e
timeCreated: 1754105855

View File

@@ -1,11 +1,17 @@
using System;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;
namespace Data
{
public class Character : MonoBehaviour
public class Character : SerializedMonoBehaviour
{
[OdinSerialize, InlineProperty] public CharacterAttributes attributes = new();
[OdinSerialize] public CharacterAttributes attributes = new();
private void Start()
{
attributes.Init();
}
}
}

View File

@@ -1,348 +1,113 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;
namespace Data
{
public enum Stat
{
Health,
MaxHealth,
MoveSpeed,
Luck,
Armor,
Damage,
RangedDamage,
MeleeDamage,
AttackRange,
AttackSpeed
}
[Serializable]
public class CharacterAttributes
{
[OdinSerialize] public float health = 100f;
[OdinSerialize] public float maxHealth = 100f;
[OdinSerialize] public float moveSpeed = 5f;
[OdinSerialize] public float luck = 0f;
[OdinSerialize] public float armor = 0f;
[OdinSerialize] public int level = 1;
[OdinSerialize] public int experience = 0;
[OdinSerialize] public int baseExperienceToLevelUp = 100;
private float lastLevel = 1f;
[OdinSerialize, PropertyTooltip("This is damage multiplier")]
public float damage = 1f;
[OdinSerialize, PropertyTooltip("This is damage multiplier for ranged attacks")]
public float rangedDamage = 1f;
[OdinSerialize, PropertyTooltip("This is damage multiplier for melee attacks")]
public float meleeDamage = 1f;
[OdinSerialize] public float attackRange = 16f;
[OdinSerialize] public float attackSpeed = 1f;
public event Action<float> OnHealthChanged;
public event Action<float> OnMaxHealthChanged;
public event Action<float> OnMoveSpeedChanged;
public event Action<float> OnLuckChanged;
public event Action<float> OnArmorChanged;
public event Action<float> OnDamageChanged;
public event Action<float> OnRangedDamageChanged;
public event Action<float> OnMeleeDamageChanged;
public event Action<float> OnAttackRangeChanged;
public event Action<float> OnAttackSpeedChanged;
[OdinSerialize, DictionaryDrawerSettings(KeyLabel = "Stat", ValueLabel = "Value", DisplayMode = DictionaryDisplayOptions.OneLine)]
private Dictionary<Attribute, AttributeData<float>> attributes = new();
public event Action<int> OnExperienceChanged;
public event Action<int> OnLevelChanged;
public event Action OnLevelUp;
public float Health
public CharacterAttributes()
{
get => health;
private set
foreach (Attribute attr in Enum.GetValues(typeof(Attribute)))
{
if (Math.Abs(health - value) < float.Epsilon) return;
health = value;
OnHealthChanged?.Invoke(health);
}
}
public float MaxHealth
{
get => maxHealth;
private set
{
if (Math.Abs(maxHealth - value) < float.Epsilon) return;
maxHealth = value;
OnMaxHealthChanged?.Invoke(maxHealth);
}
}
public float MoveSpeed
{
get => moveSpeed;
private set
{
if (Math.Abs(moveSpeed - value) < float.Epsilon) return;
moveSpeed = value;
OnMoveSpeedChanged?.Invoke(moveSpeed);
}
}
public float Luck
{
get => luck;
private set
{
if (Math.Abs(luck - value) < float.Epsilon) return;
luck = value;
OnLuckChanged?.Invoke(luck);
}
}
public float Armor
{
get => armor;
private set
{
if (Math.Abs(armor - value) < float.Epsilon) return;
armor = value;
OnArmorChanged?.Invoke(armor);
}
}
public float Damage
{
get => damage;
private set
{
if (Math.Abs(damage - value) < float.Epsilon) return;
damage = value;
OnDamageChanged?.Invoke(damage);
}
}
public float RangedDamage
{
get => rangedDamage;
private set
{
if (Math.Abs(rangedDamage - value) < float.Epsilon) return;
rangedDamage = value;
OnRangedDamageChanged?.Invoke(rangedDamage);
}
}
public float MeleeDamage
{
get => meleeDamage;
private set
{
if (Math.Abs(meleeDamage - value) < float.Epsilon) return;
meleeDamage = value;
OnMeleeDamageChanged?.Invoke(meleeDamage);
}
}
public float AttackRange
{
get => attackRange;
private set
{
if (Math.Abs(attackRange - value) < float.Epsilon) return;
attackRange = value;
OnAttackRangeChanged?.Invoke(attackRange);
}
}
public float AttackSpeed
{
get => attackSpeed;
private set
{
if (Math.Abs(attackSpeed - value) < float.Epsilon) return;
attackSpeed = value;
OnAttackSpeedChanged?.Invoke(attackSpeed);
if (!attributes.ContainsKey(attr))
attributes[attr] = new AttributeData<float>();
}
}
public int Experience
{
get => experience;
private set
{
if (experience == value) return;
experience = value;
OnExperienceChanged?.Invoke(experience);
if (experience >= ExperienceToNextLevel())
{
Level++;
experience -= ExperienceToNextLevel();
experience = Math.Min(0, experience);
}
else if (experience < 0)
{
experience = 0;
}
}
}
public float Get(Attribute attr) => attributes[attr].Value;
public int Level
{
get => level;
private set
{
if (level == value) return;
level = value;
OnLevelChanged?.Invoke(level);
OnLevelUp?.Invoke();
}
}
public void SetHealth(float value)
{
Health = Math.Clamp(value, 0, MaxHealth);
}
public void SetMaxHealth(float value)
{
MaxHealth = Math.Max(value, 0);
if (Health > MaxHealth)
{
Health = MaxHealth;
}
}
public void SetMoveSpeed(float value)
{
MoveSpeed = Math.Max(value, 0);
}
public void SetLuck(float value)
{
Luck = Math.Max(value, 0);
}
public void SetArmor(float value)
{
Armor = Math.Max(value, 0);
}
public void SetDamage(float value)
{
Damage = Math.Max(value, 0);
}
public void SetRangedDamage(float value)
{
RangedDamage = Math.Max(value, 0);
}
public void SetMeleeDamage(float value)
{
MeleeDamage = Math.Max(value, 0);
}
public void SetAttackRange(float value)
{
AttackRange = Math.Max(value, 0);
}
public void SetAttackSpeed(float value)
{
AttackSpeed = Math.Max(value, 0);
}
public void SetExperience(int value)
{
Experience = Math.Max(value, 0);
}
public void Set(Attribute attr, float value) => attributes[attr].Set(value);
public void SetLevel(int value)
{
Level = Math.Max(value, 1);
}
public void Modify(Attribute attr, float delta) => attributes[attr].Set(attributes[attr].Value + delta);
public void ModifyHealth(float delta)
{
SetHealth(Health + delta);
}
public void ModifyMaxHealth(float delta)
{
SetMaxHealth(MaxHealth + delta);
}
public void ModifyMoveSpeed(float delta)
{
SetMoveSpeed(MoveSpeed + delta);
}
public void ModifyLuck(float delta)
{
SetLuck(Luck + delta);
}
public void ModifyArmor(float delta)
{
SetArmor(Armor + delta);
}
public void ModifyDamage(float delta)
{
SetDamage(Damage + delta);
}
public void ModifyRangedDamage(float delta)
{
SetRangedDamage(RangedDamage + delta);
}
public void ModifyMeleeDamage(float delta)
{
SetMeleeDamage(MeleeDamage + delta);
}
public void ModifyAttackRange(float delta)
{
SetAttackRange(AttackRange + delta);
}
public void ModifyAttackSpeed(float delta)
{
SetAttackSpeed(AttackSpeed + delta);
}
public void Subscribe(Attribute attr, Action<float> listener) => attributes[attr].OnChanged += listener;
public void ModifyExperience(int delta)
{
SetExperience(Experience + delta);
}
public void ModifyLevel(int delta)
{
SetLevel(Level + delta);
}
public void Unsubscribe(Attribute attr, Action<float> listener) => attributes[attr].OnChanged -= listener;
public void Reset()
{
Health = MaxHealth = 100f;
MoveSpeed = 5f;
Luck = 0f;
Armor = 0f;
Damage = 1f;
RangedDamage = 1f;
MeleeDamage = 1f;
AttackRange = 16f;
AttackSpeed = 1f;
Level = 1;
Experience = 0;
Set(Attribute.Health, Get(Attribute.MaxHealth));
Set(Attribute.MoveSpeed, 5f);
Set(Attribute.Luck, 0f);
Set(Attribute.Armor, 0f);
Set(Attribute.Damage, 1f);
Set(Attribute.RangedDamage, 1f);
Set(Attribute.MeleeDamage, 1f);
Set(Attribute.AttackRange, 1f);
Set(Attribute.AttackSpeed, 1f);
Set(Attribute.Level, 1f);
Set(Attribute.Experience, 0f);
Set(Attribute.BaseExperienceToNextLevel, 100f);
}
/*
* health: 10
maxHealth: 10
moveSpeed: 2
luck: 0
armor: 0
level: 1
experience: 0
baseExperienceToLevelUp: 100
damage: 1
rangedDamage: 1
meleeDamage: 1
attackRange: 1
attackSpeed: 1
basic enemy btw
*/
public void Init()
{
foreach (Attribute attr in Enum.GetValues(typeof(Attribute)))
{
if (!attributes.ContainsKey(attr))
attributes[attr] = new AttributeData<float>();
}
lastLevel = Get(Attribute.Level);
Subscribe(Attribute.Experience, OnExperienceChanged);
}
private int ExperienceToNextLevel()
{
return (int)(baseExperienceToLevelUp * Math.Pow(Level, 2));
return (int)(Get(Attribute.BaseExperienceToNextLevel) * Math.Pow(Get(Attribute.Level), 2));
}
private void OnExperienceChanged(float newExp)
{
var xpToNext = ExperienceToNextLevel();
if (newExp >= xpToNext)
{
Modify(Attribute.Level, 1f);
Set(Attribute.Experience, newExp - xpToNext);
}
else if (newExp < 0)
{
Set(Attribute.Experience, 0f);
}
var currentLevel = Get(Attribute.Level);
if (currentLevel > lastLevel)
{
lastLevel = currentLevel;
OnLevelUp?.Invoke();
}
}
}
}

View File

@@ -1,13 +1,14 @@
using System;
using Data;
using Interfaces;
using Attribute = Data.Attribute;
namespace Modifiers
{
[Serializable]
public class FlatStatModifier : IStatModifier
{
public Stat stat;
public Attribute stat;
public float value;
public string Description => BuildDescription();
@@ -30,35 +31,35 @@ namespace Modifiers
{
switch (stat)
{
case Stat.Health:
attributes.ModifyHealth(value);
case Attribute.Health:
attributes.Modify(Attribute.Health, value);
break;
case Stat.MaxHealth:
attributes.ModifyMaxHealth(value);
case Attribute.MaxHealth:
attributes.Modify(Attribute.MaxHealth, value);
break;
case Stat.MoveSpeed:
attributes.ModifyMoveSpeed(value);
case Attribute.MoveSpeed:
attributes.Modify(Attribute.MoveSpeed, value);
break;
case Stat.Luck:
attributes.ModifyLuck(value);
case Attribute.Luck:
attributes.Modify(Attribute.Luck, value);
break;
case Stat.Armor:
attributes.ModifyArmor(value);
case Attribute.Armor:
attributes.Modify(Attribute.Armor, value);
break;
case Stat.Damage:
attributes.ModifyDamage(value);
case Attribute.Damage:
attributes.Modify(Attribute.Damage, value);
break;
case Stat.RangedDamage:
attributes.ModifyRangedDamage(value);
case Attribute.RangedDamage:
attributes.Modify(Attribute.RangedDamage, value);
break;
case Stat.MeleeDamage:
attributes.ModifyMeleeDamage(value);
case Attribute.MeleeDamage:
attributes.Modify(Attribute.MeleeDamage, value);
break;
case Stat.AttackRange:
attributes.ModifyAttackRange(value);
case Attribute.AttackRange:
attributes.Modify(Attribute.AttackRange, value);
break;
case Stat.AttackSpeed:
attributes.ModifyAttackSpeed(value);
case Attribute.AttackSpeed:
attributes.Modify(Attribute.AttackSpeed, value);
break;
default:
throw new ArgumentOutOfRangeException();

View File

@@ -3,6 +3,7 @@ using Data;
using Interfaces;
using Systems;
using UnityEngine;
using Attribute = Data.Attribute;
namespace Modifiers
{
@@ -19,7 +20,7 @@ namespace Modifiers
public void OnKill(GameObject killer, GameObject victim)
{
killer.TryGetComponent(out Character character);
character?.attributes.ModifyHealth(value);
character?.attributes.Modify(Attribute.Health, value);
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using Data;
using Interfaces;
using Attribute = Data.Attribute;
namespace Modifiers
{
@@ -9,51 +10,20 @@ namespace Modifiers
{
private float lastAppliedAmount;
public Stat stat;
public Attribute stat;
public float percent;
public string Description => GetDescription();
public void Apply(CharacterAttributes attributes)
{
var baseValue = GetBaseValue<float>(attributes);
var baseValue = attributes.Get(stat);
lastAppliedAmount = baseValue * percent;
var flatModifier = new FlatStatModifier
{
value = lastAppliedAmount,
stat = stat
};
flatModifier.Apply(attributes);
attributes.Modify(stat, lastAppliedAmount);
}
public void Remove(CharacterAttributes attributes)
{
var flatModifier = new FlatStatModifier
{
value = -lastAppliedAmount,
stat = stat
};
flatModifier.Apply(attributes);
}
private T GetBaseValue<T>(CharacterAttributes attributes)
{
return stat switch
{
Stat.Health => (T)(object)attributes.Health,
Stat.MaxHealth => (T)(object)attributes.MaxHealth,
Stat.MoveSpeed => (T)(object)attributes.MoveSpeed,
Stat.Luck => (T)(object)attributes.Luck,
Stat.Armor => (T)(object)attributes.Armor,
Stat.Damage => (T)(object)attributes.Damage,
Stat.RangedDamage => (T)(object)attributes.RangedDamage,
Stat.MeleeDamage => (T)(object)attributes.MeleeDamage,
Stat.AttackRange => (T)(object)attributes.AttackRange,
Stat.AttackSpeed => (T)(object)attributes.AttackSpeed,
_ => throw new System.ArgumentOutOfRangeException()
};
attributes.Modify(stat, -lastAppliedAmount);
}
private string GetDescription()

View File

@@ -13,12 +13,12 @@ namespace Systems
private void OnEnable()
{
character.attributes.OnHealthChanged += OnHealthChanged;
character.attributes.Subscribe(Attribute.Health, OnHealthChanged);
}
private void OnDisable()
{
character.attributes.OnHealthChanged -= OnHealthChanged;
character.attributes.Unsubscribe(Attribute.Health, OnHealthChanged);
}
private void OnHealthChanged(float newHealth)

View File

@@ -2,6 +2,7 @@ using System;
using Interfaces;
using Sirenix.Serialization;
using UnityEngine;
using Attribute = Data.Attribute;
namespace Systems
{
@@ -14,7 +15,7 @@ namespace Systems
public void Die(GameObject killer = null)
{
GameManager.Instance.Player.attributes.ModifyExperience(expReward);
GameManager.Instance.Player.attributes.Modify(Attribute.Experience, expReward);
GameManager.Instance.AddCoins(coinReward);
OnAnyEnemyKilled?.Invoke(killer ?? GameManager.Instance.Player.gameObject, gameObject);

View File

@@ -2,6 +2,7 @@ using System;
using Data;
using KBCore.Refs;
using UnityEngine;
using Attribute = Data.Attribute;
namespace Systems
{
@@ -19,15 +20,15 @@ namespace Systems
private void Start()
{
character.attributes.SetHealth(initialHealth);
character.attributes.Set(Attribute.Health, initialHealth);
}
public void TakeDamage(float damage, GameObject attacker = null)
{
lastAttacker = attacker;
var effectiveDamage = Math.Max(damage - character.attributes.Armor, 1);
character.attributes.ModifyHealth(-effectiveDamage);
var effectiveDamage = Math.Max(damage - character.attributes.Get(Attribute.Armor), 1);
character.attributes.Modify(Attribute.Health, -effectiveDamage);
if (damageSound)
{

View File

@@ -3,6 +3,7 @@ using Data;
using KBCore.Refs;
using UnityEngine;
using UnityEngine.InputSystem;
using Attribute = Data.Attribute;
namespace Systems
{
@@ -43,7 +44,7 @@ namespace Systems
{
if (!rb) return;
var velocity = new Vector2(movementInput.x, movementInput.y).normalized * character.attributes.MoveSpeed;
var velocity = new Vector2(movementInput.x, movementInput.y).normalized * character.attributes.Get(Attribute.MoveSpeed);
rb.linearVelocity = velocity;
}
}

View File

@@ -3,6 +3,7 @@ using Systems;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Attribute = Data.Attribute;
namespace UI
{
@@ -18,11 +19,11 @@ namespace UI
var player = GameManager.Instance.Player;
if (!player) return;
expText.text = $"EXP: {player.attributes.Experience}";
expText.text = $"EXP: {player.attributes.Get(Attribute.Experience)}";
coinsText.text = $"Coins: {GameManager.Instance.Coins}";
roundTimeLeftText.text = $"{GameManager.Instance.RoundTimeLeft:F1}s";
healthSlider.maxValue = player.attributes.MaxHealth;
healthSlider.value = player.attributes.Health;
healthSlider.maxValue = player.attributes.Get(Attribute.MaxHealth);
healthSlider.value = player.attributes.Get(Attribute.Health);
}
}
}

View File

@@ -5,11 +5,13 @@ using KBCore.Refs;
using Shop;
using Systems;
using UnityEngine;
using Attribute = Data.Attribute;
namespace UI
{
public class LevelUpHud : MonoBehaviour
{
private float previousLevel = 1f;
private List<UpgradeSlot> currentSlots = new();
private List<StatModifierItem> currentItemChoices = new();
private List<WeaponItem> currentWeaponChoices = new();

View File

@@ -4,6 +4,7 @@ using Interfaces;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;
using Attribute = Data.Attribute;
namespace Weapons
{
@@ -27,18 +28,18 @@ namespace Weapons
private float GetFinalAttackSpeed()
{
return character.attributes.AttackSpeed * weaponStats.attackSpeed;
return character.attributes.Get(Attribute.AttackSpeed) * weaponStats.attackSpeed;
}
protected float GetFinalDamage()
{
return weaponStats.damage + character.attributes.Damage *
(weaponStats.damageType == DamageType.Melee ? character.attributes.MeleeDamage : character.attributes.RangedDamage);
return weaponStats.damage + character.attributes.Get(Attribute.Damage) *
(weaponStats.damageType == DamageType.Melee ? character.attributes.Get(Attribute.MeleeDamage) : character.attributes.Get(Attribute.RangedDamage));
}
protected float GetFinalRange()
{
return weaponStats.range * character.attributes.AttackRange;
return weaponStats.range * character.attributes.Get(Attribute.AttackRange);
}
protected void PlayShotSound()