2
\$\begingroup\$

In my game skills can be leveled up to modify base stats like reducing cooldown by 20% and increasing fired projectile count by one. How would I architect the system so that each skill has its own custom upgrade tiers for example:

Projectile Skill Tier 1:

  • Cooldown -20%
  • Projectile Count +1

Projectile Skill Tier 2:

  • Cooldown -10%
  • Projectile Count +2
public abstract class Skill : ScriptableObject
{
    public float Cooldown;

    public abstract void Activate(Player player);
}

And an example of a subclassed skill:

public class ProjectileSkill : Skill
{
    [SerializeField] Projectile _projectile;
    [SerializeField] int _projectileCount;

    public override void Activate(Player player)
    {
        // Instantiates number of projectiles
    }
}

What I have tried:

  • Storing the state in the scriptable object and updating the values. However this is saved between sessions whereas it needs to reset.
public void LevelUp()
{
    _projectileCount++;
}
\$\endgroup\$
3
  • 2
    \$\begingroup\$ "However this is saved between sessions" - in the editor, are you modifying your source ScriptableObject instead of instantiating a mutable copy for the purpose of the current session? \$\endgroup\$
    – DMGregory
    Jun 14 at 2:39
  • \$\begingroup\$ "whereas it needs to reset" - why not write a reset method? \$\endgroup\$
    – Pikalek
    Jun 14 at 4:01
  • \$\begingroup\$ Huh so I could store current stat values in the SO and set them to their starting values (serialized fields) in the Start method. \$\endgroup\$
    – gamer1
    Jun 14 at 5:41

1 Answer 1

2
\$\begingroup\$

Using scriptable objects to store game state is usually a bad idea. They are meant to represent game-specific static assets. "Upgrading a skill" by upgrading the data of the skill is an even worse idea. What if you need multiple gameObjects which have the same skill on different levels? What if you need the data of a different skill level than the current one, for example to preview the effects of an upgrade before the player buys it?

Solution A: Skill tiers as separate assets

One option would be to represent each upgrade tier as a separate asset. So "Fireball Tier 1" and "Fireball Tier 2" are separate assets of the type ProjectileSkill.

Upgrade progressions can then be represented by adding a field

public ProjectileSkill nextTier;

to the class. Now you can use the inspector of "Fireball Tier 1" to assign "Fireball Tier 2" as the next upgrade tier. When a skill is the final upgrade tier, you can represent that by leaving nextSkill empty.

This would be the solution I would prefer if skills don't have too many upgrade tiers. But the downsides are that it does not scale too well when you have a very high number of upgrade tiers, and that it does not support infinite upgrade-able skills.

Solution B: The asset contains the data for all its tiers

Another option would be to create one ScriptableObject for each skill, but then store the data of the different upgrade levels within that ScriptableObject. Now when a gameObject wants to "activate" a skill, it also needs to provide the level it wants to activate it at.

public class ProjectileSkill : Skill
{
    [Serializable]
    public struct UpgradeLevel{
        public Projectile projectile;
        public int projectileCount;
    }
    public UpgradeLevel[] upgradeLevels;

    public override void Activate(Player player, int level)
    {
        var upgradeLevel = upgradeLevels[level];
        // Use data from upgradeLevel to Instantiate number and type of projectiles
    }
}

Now this scriptable object has an array of levels where you can maintain the data for each upgrade level. That means the script which references this ScriptableObject needs an additional variable representing the current upgrade level.

[SerializeField] private Skill mySkill;
[SerializeField] private int mySkillLevel;

/*...*/

    mySkill.Activate(this, mySkillLevel);

This architecture also allows indefinitely upgradeable skills with a mathematical progression. Such skills wouldn't have an internal array of upgrade levels. Their Activate method would calculate all the numbers based on the requested level.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the help. I have used a modified version of solution A by having a next skill attribute in the base Skill class. This works much better with polymorphism since I keep skills in an array. \$\endgroup\$
    – gamer1
    Jun 14 at 23:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .