Refactor character attributes system; replace individual attributes with a dictionary-based approach for better scalability and maintainability
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
19
Assets/Scripts/Data/Attribute.cs
Normal file
19
Assets/Scripts/Data/Attribute.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Data
|
||||
{
|
||||
public enum Attribute
|
||||
{
|
||||
Health,
|
||||
MaxHealth,
|
||||
MoveSpeed,
|
||||
Luck,
|
||||
Armor,
|
||||
Damage,
|
||||
RangedDamage,
|
||||
MeleeDamage,
|
||||
AttackRange,
|
||||
AttackSpeed,
|
||||
Level,
|
||||
Experience,
|
||||
BaseExperienceToNextLevel,
|
||||
}
|
||||
}
|
3
Assets/Scripts/Data/Attribute.cs.meta
Normal file
3
Assets/Scripts/Data/Attribute.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9587f73172674bd1ad04d9e2327ad882
|
||||
timeCreated: 1754103975
|
29
Assets/Scripts/Data/AttributeData.cs
Normal file
29
Assets/Scripts/Data/AttributeData.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/Scripts/Data/AttributeData.cs.meta
Normal file
3
Assets/Scripts/Data/AttributeData.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd15401c68cc46338799a2718a5e6960
|
||||
timeCreated: 1754104026
|
18
Assets/Scripts/Data/AttributeEntry.cs
Normal file
18
Assets/Scripts/Data/AttributeEntry.cs
Normal 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;
|
||||
}
|
||||
}
|
3
Assets/Scripts/Data/AttributeEntry.cs.meta
Normal file
3
Assets/Scripts/Data/AttributeEntry.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 056d8fc09bcc477fba9ff895cde4bb6e
|
||||
timeCreated: 1754105855
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user