Unity: ScriptableObjects and Inheritance

6 minute read

Note: This post builds upon my previous post ScriptableObject Basics.


Inheritance

Inheritance is a key concept in object-oriented programming. In partical terms it allows you to make a class that automatically has data and/or behaviours that are on another thing. Conceptually you could think of it in terms of a recliner versus an armchair versus a chair. A regular chair has a seat and a back and you can sit on it. An arm chair has all those things plus padding and arms. A recliner in turn has all those things plus a reclining mechanism. For a more technical definition of inheritance and how it works with regard to C#, see Microsoft’s C# inheritance documentation.

One can see how this concept lends itself well to game development. One might want to design a weapon that inherits some characteristics or behaviours from a base class, which in turn inherits from another and so on. I’ll leave all the possibilities for such things to your own imagination so I can expound on the concept with regard to ScriptableObjects and in particular the use of them to store data about upgrades in my last post.

UpgradeDefinitions (naming is important)

In my previous post I simply created a single HealthBoostUpgrade to avoid having to explain too much and to keep the post length reasonable. The name HealthBoostUpgrade is certainly descriptive to some extent, but it isn’t specific enough. I have learned from the other half of Sparrowhawk Labs (John Leacox) and from the book Clean Code by Robert C. Martin that one of the most important things in writing good code is naming. You are going to have a lot of classes and variables in your game or project, and if they are lazily named, you are going to find yourself very confused. Can you tell what a HealthBoostUpgrade does just based on that name? It’s not entirely clear what it is aside from the fact that it has something to do with some kind of upgrade. Does it just hold data? Does it perform any behaviors? If we add the word Definition to the name, it clarifies things quite a bit. Now we know that it is just a definition of this upgrade, not necessarily any behaviors. HealthBoostUpgradeDefinition is a better name, and while it might look a bit long, it is quite parse-able (and if you’ve got a good IDE, it’ll help you find the names quickly).

Duplication

Great. We’ve decided on a better name and now we have a class that looks something like this:

using UnityEngine;

[CreateAssetMenu(menuName = "SparrowhawkLabs/Health Boost Upgrade Definition")]
public class HealthBoostUpgradeDefinition : ScriptableObject
{
    [SerializeField]
    [Tooltip("Name of this upgrade.")]
    private string _name;

    [SerializeField]
    [Tooltip("Description of this upgrade.")]
    private string _description;

    [SerializeField]
    [Tooltip("Required player level to apply this upgrade.")]
    private int _requiredPlayerLevel;

    [SerializeField]
    [Tooltip("Health boost amount applied by this upgrade.")]
    private int _healthBoostAmount;

    public string Name => _name;
    public string Description => _description;
    public int RequiredPlayerLevel => _requiredPlayerLevel;
    public int HealthBoostAmount => _healthBoostAmount;
}

What if I want to make a new kind of upgrade? No problem, I can make a ScriptableObject called GunUpgradeDefinition for example:

using UnityEngine;

[CreateAssetMenu(menuName = "SparrowhawkLabs/Gun Upgrade Definition")]
public class GunUpgradeDefinition : ScriptableObject
{
    [SerializeField]
    [Tooltip("Name of this upgrade.")]
    private string _name;

    [SerializeField]
    [Tooltip("Description of this upgrade.")]
    private string _description;

    [SerializeField]
    [Tooltip("Required player level to apply this upgrade.")]
    private int _requiredPlayerLevel;

    [SerializeField]
    [Tooltip("Amount of damage this upgrade will add to the weapon.")]
    private  int _damageIncreaseAmount;

    [SerializeField]
    [Tooltip("This upgrade will increase the rate of fire by this many rounds per second.")]
    private  float _rateOfFireIncrease;

    public string Name => _name;
    public string Description => _description;
    public int RequiredPlayerLevel => _requiredPlayerLevel;
    public int DamageIncreaseAmount => _damageIncreaseAmount;
    public float RateOfFireIncrease => _rateOfFireIncrease;
}

Cool. That was fairly simple. I just copy-pasted my code and changed it in a few places, but did you notice something? Even though all I had to do in this case was copy-paste and replace a few things, what if I keep doing this and make 8 different kinds of upgrades then decide I need to change the type of data that goes on my upgrade definitions? I have to go back to each one and make sure they match. This is solved by making a base class for our upgrade definitions.

If we look at these two classes we can se that they all have fields for Name, Description and RequiredPlayerLevel. I’m going to go ahead and make the decision right now that I want all my Upgrade Definitions to have these. Take a look at the class below:

using UnityEngine;

public abstract class UpgradeDefinitionBase : ScriptableObject
{
    [SerializeField]
    [Tooltip("Name of this upgrade.")]
    private string _name;

    [SerializeField]
    [Tooltip("Description of this upgrade.")]
    private string _description;

    [SerializeField]
    [Tooltip("Required player level to apply this upgrade.")]
    private int _requiredPlayerLevel;

    public string Name => _name;
    public string Description => _description;
    public int RequiredPlayerLevel => _requiredPlayerLevel;
}

Make note of my inclusion of the word abstract. This makes it so I can’t create an instance of this. Think about it: it wouldn’t make any sense to define an Upgrade that has information about its own name, description and level requirement and nothing more. This keeps me from doing that. Here’s what my new classes for HealthBoostUpgradeDefinition and GunUpgradeDefinition look like now:

using UnityEngine;

[CreateAssetMenu(menuName = "SparrowhawkLabs/Health Boost Upgrade Definition")]
public class HealthBoostUpgradeDefinition : UpgradeDefinitionBase
{
    [SerializeField]
    [Tooltip("Health boost amount applied by this upgrade.")]
    private int _healthBoostAmount;

    public int HealthBoostAmount => _healthBoostAmount;
}
using UnityEngine;

[CreateAssetMenu(menuName = "SparrowhawkLabs/Gun Upgrade Definition")]
public class GunUpgradeDefinition : UpgradeDefinitionBase
{
    [SerializeField]
    [Tooltip("Amount of damage this upgrade will add to the weapon.")]
    private  int _damageIncreaseAmount;

    [SerializeField]
    [Tooltip("This upgrade will increase the rate of fire by this many rounds per second.")]
    private  float _rateOfFireIncrease;

    public int DamageIncreaseAmount => _damageIncreaseAmount;
    public float RateOfFireIncrease => _rateOfFireIncrease;
}

Not only are the classes smaller, but they are more concise. The only fields present are directly relevant to that class, and the ones that pertain to upgrades in general are still “there” due to inheritance since they inherit from UpgradeDefinitionBase.

These two definitions are still just as easy to create in the editor as they were before and the same fields will show up in the inspector. There is another bonus that is pretty cool. Imagine you need to store a list of avaialable upgrades. This class isn’t quite how we would do this in an actual project, but it will illustrate the point. Without inheritance we need to keep separate lists to keep track of the upgrade definitions:

using System.Collections.Generic;
using UnityEngine;

public class UpgradeList : MonoBehaviour
{
    [SerializeField]
    [Tooltip("List of HealthUpgradeDefinitions available to be applied.")]
    private List<HealthUpgradeDefinition> _healthUpgradeDefinitions;

    [SerializeField]
    [Tooltip("List of GunUpgradeDefinitions available to be applied.")]
    private List<GunUpgradeDefinition> _gunUpgradeDefinitions;

    public List<HealthUpgradeDefinition> HealthUpgradeDefinitions => _healthUpgradeDefinitions;
    public List<GunUpgradeDefinition> GunUpgradeDefinitions => _gunUpgradeDefinitions;
}

This obviously isn’t the most complicated thing in the world, but it would become cumbersome as the project grows. If we use inheritance, however, the class looks like this:

using System.Collections.Generic;
using UnityEngine;

public class UpgradeList : MonoBehaviour
{
    [SerializeField]
    [Tooltip("List of Upgrade Definitions available for use.")]
    private List<UpgradeDefinitionBase> _upgradeDefinitions;

    public List<UpgradeDefinitionBase> UpgradeDefinitions => _upgradeDefinitions;
}

That’s it. One list. We can keep making new ScriptableObjects to define upgrades and as long as they inherit from UpgradeDefinitionBase, they can all be put in that one list in the editor. Inheritance is worth reading up on and applying to your own game or project wherever you can. It can save time and can make refactoring code much less painful.

Video

I made a 15 minute YouTube video showing the entire process live with coding.