In my game, the player has pickups that they can activate that each do different things. Some examples include:
- Restore your health
- Gives you a one time speed boost
- Give you a bubble that bounces you off the ground
- Move all the coins within range closer to you
- Boost you to a certain velocity for x seconds then returns you to your previous speed
- Turn off collision with enemies
These are all in their own ScriptableObjects that derive from a base Pickup ScriptableObject that has Activate, Tick, and Deactivate functions that are overridden per Pickup. These work fine (but not without their troubles), but the problem I am coming to have is implementing an upgrade system with them.
The player has and saves a list of Upgrade ScriptableObjects that detail what Pickup it is targeting, and what aspects about the Pickup it is modifying, along with the value that the upgrade gives. Things like "Magnet, Duration, 3 seconds" or "Chainsaw, Damage, 15".
The solution I came up with was to have Pickup variables that are runtime only, like duration, range, speed, horizontalRange, verticalRange, etc. and another Pickup function to be overridden, "Setup", which goes through all the Upgrades that the player has, finds which ones that target that Pickup, and then manually updates that runtime variable based on the different levels that the upgrade is at before any more processing is done. A special thing to note is that an upgrade can affect multiple aspects per level, such as level 1 upgrading duration, level 2 upgrading range, and level 3 upgrading both.
This comes with the problem that some Pickups use vastly different variables, such as a health restore not caring about a range, duration, or anything else besides "how much health do I restore". "Magnet" and "Jump Boots" both care about a duration, but not a range, while I might in the future make an upgrade that cares about damage, range, and speed... So I would be retyping the same code for 20+ Pickups with slight modifications, like one going through all range upgrades, one going through all duration upgrade, etc.
Relevant Scripts:
Pickup.cs
public abstract class Pickup : ScriptableObject
{
public Sprite sprite; //what sprite it shows up as in the UI
public GameObject prefab;
public float defaultDuration;
public enum PickupType
{
Passive,
Active
}
public PickupType type;
[SerializeField] protected PlayerManager player;
[SerializeField] public float duration { get; protected set; }
[SerializeField] public float range { get; protected set; }
public virtual void Setup() { }
public virtual bool Activate(PlayerManager playerManager)
{
player = playerManager;
return true;
}
public virtual void Tick()
{
}
public virtual void Deactivate()
{
}
}
MagnetPickup.cs
public class MagnetPickup : Pickup
{
public StatModifier bouncinessModifier;
public StatModifier fallDamageModifier;
float defaultRange = 4f;
public override void Setup()
{
duration = defaultDuration;
List<StatUpgrade> targettedUpgrades = PlayerData.instance.GetAllUpgradesForPickup(this);
foreach (StatUpgrade targettedUpgrade in targettedUpgrades)
{
foreach (UpgradeLevel level in targettedUpgrade.levels)
{
foreach (UpgradeStatModifier modifier in level.modifiers)
{
if (modifier.statType == UpgradeStatModifier.StatType.Duration)
{
duration += modifier.value;
}
}
}
}
range = defaultRange;
targettedUpgrades = PlayerData.instance.GetAllUpgradesForPickup(this);
foreach (StatUpgrade targettedUpgrade in targettedUpgrades)
{
foreach (UpgradeLevel level in targettedUpgrade.levels)
{
foreach (UpgradeStatModifier modifier in level.modifiers)
{
if (modifier.statType == UpgradeStatModifier.StatType.Range)
{
range += modifier.value;
}
}
}
}
}
I think I could get away with merging the loops in the MagnetPickup Setup so that it goes through Duration and Range, like
public override void Setup()
{
duration = defaultDuration;
range = defaultRange;
List<StatUpgrade> targettedUpgrades = PlayerData.instance.GetAllUpgradesForPickup(this);
foreach (StatUpgrade targettedUpgrade in targettedUpgrades)
{
foreach (UpgradeLevel level in targettedUpgrade.levels)
{
foreach (UpgradeStatModifier modifier in level.modifiers)
{
if (modifier.statType == UpgradeStatModifier.StatType.Duration)
{
duration += modifier.value;
}
if (modifier.statType == UpgradeStatModifier.StatType.Range)
{
range += modifier.value;
}
}
}
}
}
but again, I would be writing nearly this same setup for 20+ Pickups that all use slightly different things. Thanks for taking the time and reading this, and I appreciate the help!
Setup
to base classPickup
useful? \$\endgroup\$