Hi, I’ve been banging my head on this one for a while now.
I am trying to create a Mass Effect 3 style ability progression system, where an ability has 5 upgrades, or 6 upgrades if we include the ability itself.
I would like to be able to script an ability, and assign an upgrade script to each upgrade slot in the editor. I have been trying to program it in such a way that the abilities do not need to explicitly know what upgrade they have, and the upgrades do not need to know what ability they are attached to.
Now, I’m not sure that its possible without using reflection… and honestly, I’m fine if each ability has only individualized upgrades even if it means that I’ll need a “GrenadeDamageUp” and a “FlamethrowerDamageUp”. I’d prefer to generalize where I can, but I think I have fried my brain and would like to hear other people’s ideas on how to create a flexible “drag’n’drop” upgrade system for abilities.
Below is my code attempt. I will, of course, refactor things and put them in separate files when I get a working baseline. Also I did not use interfaces because I cannot [SerializeReference] them. If necessary I can refactor the base classes to implement an interface later.
public class AbilityBase: MonoBehaviour
{
public virtual void Use(AbilityUpgradeProgressData upgradeData)
{
}
}
[Serializable]
public class AbilityUpgradeProgressData
{
public bool AbilityUnlocked;
public bool Upgrade1;
public bool Upgrade2a;
public bool Upgrade2b;
}
public class TestAbility01 : AbilityBase
{
[SerializeField]
private float Damage;
[SerializeField]
private float Range;
[SerializeField]
private float Cooldown;
[SerializeReference]
private AbilityUpgrade Upgrade1;
[SerializeReference]
private AbilityUpgrade Upgrade2a;
[SerializeReference]
private AbilityUpgrade Upgrade2b;
public override void Use(AbilityUpgradeProgressData upgradeData)
{
if (!upgradeData.AbilityUnlocked)
return;
object realDamage = Damage;
object realRange = Range;
object realCooldown = Cooldown;
var upgradeReferenceValues = new Dictionary<string, AbilityApplyUpgradeFieldData>();
upgradeReferenceValues.Add("Damage", new AbilityApplyUpgradeFieldData(Damage,ref realDamage));
upgradeReferenceValues.Add("Range", new AbilityApplyUpgradeFieldData(Range,ref realRange));
upgradeReferenceValues.Add("Cooldown", new AbilityApplyUpgradeFieldData(Cooldown,ref realCooldown));
if (upgradeData.Upgrade1)
Upgrade1.ApplyUpgrade(this, upgradeReferenceValues);
if (upgradeData.Upgrade2a)
Upgrade2a.ApplyUpgrade(this, upgradeReferenceValues);
if (upgradeData.Upgrade2b)
Upgrade2b.ApplyUpgrade(this, upgradeReferenceValues);
Debug.Log($"Ability cast with {realDamage} damage, {realRange} range, and {realCooldown} cooldown.");
}
}
public class AbilityApplyUpgradeFieldData
{
public object BaseValue;
public object ReferenceValue;
public AbilityApplyUpgradeFieldData(object baseValue, ref object referenceValue)
{
BaseValue = baseValue;
ReferenceValue = referenceValue;
}
}
[Serializable]
public class AbilityUpgrade//<T> where T : AbilityBase
{
public virtual void ApplyUpgrade(AbilityBase ability, Dictionary<string, AbilityApplyUpgradeFieldData> args)
{
}
}
[Serializable]
public class UpgradeStat : AbilityUpgrade
{
public string statName;
public float StatIncreasePercen = 1f;
public override void ApplyUpgrade(AbilityBase ability, Dictionary<string, AbilityApplyUpgradeFieldData> args)
{
if (args.ContainsKey(statName))
{
args[statName].ReferenceValue = (float)args[statName].BaseValue * StatIncreasePercen;
}
}
}
public class UseAbilityOnStart : MonoBehaviour
{
[SerializeField]
private AbilityBase ability;
[SerializeField]
private AbilityUpgradeProgressData upgrades;
private float cooldown;
void Start()
{
//ability.Use(upgrades);
}
private void Update()
{
if (cooldown <= 0)
{
cooldown = 2;
ability.Use(upgrades);
}
else
{
cooldown -= Time.deltaTime;
}
}
}