Home » ScriptableObjects Part 1: Data Containers and Blueprints

ScriptableObjects Part 1: Data Containers and Blueprints

When you create classes in Unity, there is the ever present decision over whether or not to inherit MonoBehaviour. Basically, an instance class that relates to the spatial dimensions or should be attached to a game object, has to be a MonoBehaviour.

This has been aptly described as “the MonoBehaviour Tyranny”. Fortunately, it can be solved with ScriptableObjects.

This article is split into two parts. In the first part, we’ll first discuss what ScriptableObjects are, how to create them, and what’s the main purposes of using them.

In the second part, I’ll demonstrate how I’m using a static manager class together with singleton ScriptableObjects to overcome some issue that arise from the above-described “tyranny”.

What is a ScriptableObject?

ScriptableObjects inherit from UnityEngine.Object just like MonoBehaviours. In principle, they can be used very much like MonoBehaviour scripts: you may define fields and methods in them. You can also use some Unity callbacks: Awake()OnDestroy(), OnEnable(), and OnDisable(). But that’s it. There is no Update() method, for instance.

Unlike MonoBehaviours, ScriptableObjects are not serialized in the scene (or prefabs) and they are not used as components of GameObjects. Instead, they are stored as assets in the project folders – just like textures, models, and script files.

How to Create ScriptableObjects?

ScriptableObjects can be instantiated with ScriptableObject.Instantiate<T>() method. This method creates them in the memory, but does not automatically create the asset file. You have to use editor scripting to do that. Here’s an example how to do that. But the simple method for creating assets is to use the CreateAssetMenu attribute, like this:

[CreateAssetMenu(menuName = "MyScriptableObject")]
public class MyScriptableObject : ScriptableObject
{
    public string someString;
}

CreateAssetMenu attribute adds “MyScriptableObject” menu item under the Assets/Create submenu. You can create an instance of the ScriptableObject by clicking this menu item. The ScriptableObject instance is saved in the project folder as an asset.

Limitations of ScriptableObjects

There are two limitations in the usage of ScriptableObjects that raise from the fact that ScriptableObject instances live as assets in your project folder.

First limitation is related to references. You can easily setup references to ScriptableObjects in your MonoBehaviour scripts in the editor by dragging the ScriptableObject asset into a field of a MonoBehaviour script. However, like with prefabs, you cannot define references to scene objects in ScriptableObjects. Note that this does not mean you couldn’t use methods like GameObject.Find()! It simply means that you can’t store the reference via dragging in the editor, because an asset does not have an access to the scene.

The other limitation considers data persistence. Just like MonoBehaviours, ScriptableObjects are not meant to store any persisting data created at runtime. In the editor, the changes made to ScriptableObjects (in code or by hand) do persist. This can easily mislead you to think that you could save the in-game changes also in builds. That’s not the case! In builds, we can manipulate the data at runtime, but the changes don’t persist from a session to another!

ScriptableObjects as Data Containers

The limitation regarding serialization actually makes ScriptableObjects good as read-only data containers. For example, game settings that should not be edited by the player or presets for editors scripts, are good candidates to be implemented as ScriptableObjects.

Make note that ScriptableObjects can have references to other assets, including other ScriptableObjects. This means that you can use one ScriptableObject as a manager and others to define the data. You can also use custom serializable (i.e. pure C#) classes in a ScriptableObject. In the picture below, all the items that have a drop down arrow next to them are actually distinct classes.

ScriptableObjects can be used as a configuration file or preset to define the game settings.

The point of using classes here is to organize the settings into subcategories. The code to do this follows this pattern:

[Serializable]
public class TagNames
{
    public string player = "Player";
    public string enemy = "Enemy";
    public string weapon = "Weapon";
    public string body = "Body";
    public string bodyPart = "BodyPart";
    public string armor = "Armor";
    public string hair = "Hair";
    public string facialHair = "FacialHair";
    public string projectile = "Projectile";
}
[SerializeField]
private TagNames tags = new TagNames();
public TagNames Tags { get { return tags; } }

In addition to having foldable categories in the Inspector view, the organization also comes in the structure of your code. If we use descriptive classes to hold the data, there’s no need to include a suffix like “-tag”, for instance, in all the tag related fields.

In other words: when you find that you are using suffixes in your variables, you might want to consider a stricter encapsulation for your data.

Note that if the Inspector UI is all you care about, you can also use the Header Attribute.

ScriptableObjects as Intelligent Blueprints

Besides storing data, ScriptableObjects can also contain methods, events, and delegates – just like any other C# class. Therefore, we can use ScriptableObjects to provide plug-in functionality in our game.

Let’s suppose that we have different combat abilities for the player to use. These can be implemented as ScriptableObjects. The code for implementing the blueprint could look something like this:

public abstract class AbilityBlueprint : ScriptableObject
{
    public float nonLethalDmgMultiplier = 1;
    public float lethalDmgMultiplier = 1;
    public float staminaCostMultiplier = 1;
    public float knockBackMultiplier = 1;
    public ActivationMode activationMode;
    public float cooldown = 0;
    public bool requiresTarget;
    public bool isOffensive;
    public bool canBeBlocked = true;
    public bool mayTriggerSlowMotion = true;
    public string specialAnimation;
    public ParticleSystem specialImpactEffect;
    public ParticleSystem specialTrailEffect;

    public bool CanBeUsedAsAttack { get { return activationMode == ActivationMode.Action && isOffensive; } }
}

Instances of ScriptableObjects can be treated like blueprints, which store data and functionality that can be accessed by reference to the blueprint.

For providing per ability instance functionality, we would also need a class that is created for each instance separately. For this, we can use a MonoBehaviour or a normal C# class, depending on our design. The ability instance class could be implemented like so:

public abstract class Ability
{
    public readonly AbilityBlueprint blueprint;
    public abstract AbilityType AbilityType { get; }
    public bool IsActive { get; protected set; }

    public virtual bool Activate()
    {
        IsActive = true;
    }

    public virtual bool Deactivate()
    {
        IsActive = false;
    }
}

The implementation details are quite irrelevant. What is important, is the pattern: an instance has the reference to a blueprint that contains data and functionality that is common to all the instances using the same blueprint.

ScriptableObjects can be used to create “blueprints” of abilities, skill, effects etc. They can contain methods, which may be called via other code.

Conclusion

In this article, I have discussed what ScriptableObjects are and how to create them. We have also seen two example cases, where using a ScriptableObject is a lot more convenient and less error-prone than using MonoBehaviours.

In comparison to other options, such as json and xml formats, ScriptableObjects are different in that a) they provide functionality in addition to data, and b) they can be easily modified in the Inspector.

In the next post, I’ll discuss how to use ScriptableObjects as persistent managers.

Further Reading:
https://docs.unity3d.com/Manual/class-ScriptableObject.html
https://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/scriptable-objects
https://blogs.unity3d.com/2017/11/20/making-cool-stuff-with-scriptableobjects/
https://blogs.unity3d.com/2012/10/25/unity-serialization/
https://sometimesicode.wordpress.com/2015/10/22/unity-serialization-part-3-scriptable-objects/
https://unity3d.com/learn/tutorials/topics/scripting/ability-system-scriptable-objects
https://medium.com/@mormo_music/game-settings-with-scriptable-objects-in-unity3d-6f753fe508fd

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.