scriptable object feature

Scriptable Objects in Unity (how and when to use them)

In Unity by John French2 Comments

When you’re building a game in Unity, one of the challenges that you’re likely to face early on is how to manage your game’s data.

Where will your player’s health value go?

How can you create a quest or an objective?

And how can you share information with other scripts, objects and between different scenes, without having to connect everything together?

While there are many different ways to manage data in Unity, one method is to use Scriptable Objects.

Scriptable object classes allow you to store instances of scripts as assets in your project, instead of as components on game objects in your scene.

Which is useful, as it means that you can set a reference to a scriptable object asset in the same way that you would any other asset type, such as audio clips, materials or textures.

This makes scriptable objects ideal for building your game’s data assets, such as quests, objectives, perks, spells, cards, recipes, books, enemy stats, settings profiles, player data and more.

All in a format that can be accessed from any class, on any object, in any scene.

In this article, you’ll learn how scriptable objects work, what you can do with them and how they can make organising the data in your game much easier.

Here’s what you’ll learn on this page:

Let’s get started.

What is a scriptable object in Unity?

Scriptable Objects are a type of class in Unity that can be used to create multiple instances of a script that each contain their own unique values.

But how is that different to any other type of script?

Normally, when you create a Monobehaviour script, it’s attached to a game object as a component running in your scene.

This is an instance of the class, and it can be used to hold a unique set of values, such as the health of the object it’s attached to.

However, if you added another instance of the same class to a different object in the scene then, while the template of information would be the same, the data itself would be different.

Two instances of the same script, each with unique values.

Normally, however, this can only happen inside of a scene, meaning that any other objects that might want to connect to the class can only do so by getting a reference to it first.

And while this might not be a problem for very specific data that doesn’t need to be shared, if you want to connect important information with other scripts, such as the health of the player, for example, it means you’ll need to find a way to pass that data around the scene without making each script dependent on each other.

So what’s the answer?

One solution is to store important data as global variables using statics, which allows you to create a value that belongs to the class itself, not any one instance of it.

The benefit of using statics is that they allow any script to access a variable or a function via the class itself, without needing to get a reference to an instance of it.

However, there’s a drawback with this method.

When using statics, there can only ever be one value of that type, since static values belong to the class and are shared by all of its instances.

This means that, if you use statics, or Singletons, which are based on statics, for something that you might need more than one of, you may find it difficult to change your project later on.

For example, if you were to create a static player health value, and have a health bar script read the value directly, it would be difficult to create a second health bar using the same script for a second player later on.

Put simply, it’s not possible to use statics to create multiple versions of the same data type.

So what’s the alternative?

Scriptable objects allow you to create instances of classes as assets, which is useful for two main reasons.

Firstly, it allows you to create multiple versions of the same kind of data.

Generally speaking, this is what scriptable objects are designed to do, making them ideal for building your game’s content, such as the details of objectives, items, recipes, or any other kind of data structure that you want to exist outside of the scene as an asset.

However, because of how scriptable objects work, there’s a second benefit.

Because the data exists outside of the scene, any script can access it, which means that scriptable objects can also be used as a kind of global variable, where live data can be written and read by any class, without needing to hardcode static variables into your scripts.

Instead, when using scriptable objects, scripts in your scene only need to reference a type of scriptable object data, allowing you to select an instance of the asset you want to use.

And, if you need to change which asset a particular script is using, such as which player health value a health bar is reading from, it’s possible to swap it out for a different one, in the exact same way that you would with an audio clip, a material, or any other asset type.

Scriptable object assets explained

Even if you’re still new to scriptable objects, you’ve probably already used a similar system before.

They are, essentially, assets after all, just like sprites, materials or audio clips.

For example, Audio Clips are simply a type of data, information in a particular format, from which unique audio clip instances are created, which are the different sound effects and music files in your project.

Audio Source components are attached to game objects as components and use references to audio clip assets in order to play them.

Multiple audio sources can reference the same clip and, if you want to use a different audio clip, changing it is as easy as dragging a new sound to the audio clip field instead.

Scriptable objects work in the exact same way, just with data.

Just like audio clips, it’s possible to create multiple instances of a scriptable object type and, just like audio sources, scripts can use a reference of that type in the game.

So how do you actually make a scriptable object in Unity?

How do you make a scriptable object?

In order to make a scriptable object asset, first, you’ll need to create the data template on which all of its instances will be based.

In the same way that script components are simply instances of a class, each scriptable object asset is an instance of the original template.

However, scriptable objects can’t be attached to game objects in your scene, they only exist in the project, so while you might normally create a new script when adding it as a component to a game object, to make a scriptable object class, you’ll need to right-click in your project window and select Create > C# Script.

Like this:

Create New Script dialogue in Unity

Scriptable objects can’t be attached to game objects, meaning that you’ll need to create them in your project using the Create Menu.

When creating a new scriptable object, just like when referencing an instance of a class, the name you choose here will be the variable type you declare in other scripts when referencing this kind of asset in your game.

As a result, it can help to choose a name that describes what kind of data it is, such as a settings profile, or a type of variable, so that it’s easier to recognise what it’s for later.

In this example, I’m going to create a Player Data asset that can be used to hold live values about a player for other scripts to use.

Renaming a script in Unity

At this point, I’ve created a new script, but it isn’t a scriptable object yet.

By default, new scripts inherit from Monobehaviour, which is what allows them to be added to game objects as components.

To create a scriptable object class instead, replace ‘Monobehaviour’ with ‘ScriptableObject’.

Like this:

public class PlayerData : ScriptableObject
{
}

Just like in any other script, you can declare variables in the body of the class.

For instance, in this basic example, I might add a health value, a magic value and a Vector 3 position to track where the player is.

Like this:

public class PlayerData : ScriptableObject
{
    public float health;
    public float magic;
    public Vector3 currentPosition;
}

But, the Player Data class is only a template, so how do you actually use it?

To use a scriptable object, you’ll need to create at least one instance of it and, generally, the easiest way to do this is by adding the Create Asset Menu attribute to the scriptable object class.

Like this:

[CreateAssetMenu]
public class PlayerData : ScriptableObject
{
    public float health;
    public float magic;
    public Vector3 currentPosition;
}

This attribute will allow you to create scriptable object instances from the Create Menu, by right-clicking in your project window.

Screenshot - Create Scriptable Object Instance in Unity

Which will then create a new instance of the scriptable object type as an asset in your project.

Example of a scriptable object instance

Each asset is unique, just like a script component on a game object, meaning that you could create just one player data asset, as I’ve done here, or multiple player data files, where each one provides live data about a particular player, but using the same data type.

Selecting the asset in the project will display its specific values in the inspector, just like when selecting any other asset.

Scriptable Object asset data in the Inspector

This can be useful as, if you’re using scriptable objects to create data content, such as items or stats profiles, it allows you to do it in the inspector.

While, in this example, where I’m using scriptable objects to store dynamic data, it also allows me to view the live values as they change or, because they’re public, access them from a script in the scene.

For example, a Player Class, that’s attached to an object, might hold a reference to a Player Data asset, which it can use to update the player’s position as the game runs.

Like this:

public class Player : MonoBehaviour
{
    public PlayerData playerData;
    void Update()
    {
        playerData.currentPosition = transform.position;
    }
}

Then, to connect the player object in the scene with a specific player data asset in the project, simply drag it to the field or use the circle select button.

Like this:

Select Scriptable Object

But, why do it this way?

What’s the benefit of using a scriptable object over a regular class?

Generally speaking, the main purpose of scriptable objects is to store instances of data outside of a scene.

This is useful when the data itself doesn’t belong to any one scene, such as settings data, profiles and item configurations, as it allows you to create the data in the inspector and access it from anywhere in your game, just like any other asset.

While storing in-scene data in an asset allows you to access it from other scripts and objects more easily.

For example, if you wanted to create a health bar for the player, all you’d need to do is create a health bar script that takes a reference to a type of Player Data.

Like this: 

public class PlayerHealthBar : MonoBehaviour
{
    public float displayValue;
    public PlayerData playerData;
    void UpdateHealthBar()
    {
        displayValue = playerData.health;
    }
}

Then, for the health bar to work, simply tell it which player data asset it’s supposed to read from.

But, how is this any better than simply connecting the health bar directly to a health class on the player object in the scene?

This example works because both the player class and the health bar class are referencing the same Player One Data asset.

Which means that they don’t have to be directly connected to each other in order to work.

Instead, the scriptable object acts as a layer of abstraction between the two, meaning that, while both classes are able to use the same data, as if they were connected, they’re not dependent on each other to exist, avoiding dependency problems caused by tight coupling.

Visualisation of a health bar and player script accessing player data in Unity

Because the scripts reference the permanent Player Data asset, they don’t have to reference each other.

For example, it’s possible for the health bar to exist without the player and vice-versa, since all each script does is read from the player data asset.

Which is useful, as it allows you to create a connection to a scriptable object asset ahead of time, in a prefab for example, before the object is ever instantiated in a scene. Something you can’t do with regular scripts on objects separate to a prefab, at least without searching the scene or using a static reference.

And, if you decide, later on, that you want to create a second player, or a third, or a fourth, all you need to do is create additional instances of the player data asset and select which one you want to use.

screenshot - Selecting a different scriptable object asset to use in the Inspector in Unity

How to use the Create Asset Menu attribute

To create a scriptable object in your project, you’ll need to use the Create Asset Menu attribute, which allows you to create new scriptable object instances from the Create Menu.

Like this:

[CreateAssetMenu]
public class EnemyStatsProfile : ScriptableObject
{
}

A lot of the time, particularly if you’re only using a handful of scriptable objects in your game, this will be all you need to do.

However, it’s also possible to specify the name of the menu item that will appear, the default filename for new instances that you create and, importantly, the order each option will appear in the create menu list.

Like this:

[CreateAssetMenu(menuName = "Enemy Stats", order = 2, fileName = "New Enemy Stats")]

You can choose to add one of these parameters, or all of them, or none at all and, unlike when passing arguments to a function, the order you enter them in doesn’t matter.

But why bother?

After all, just adding the create asset menu attribute on its own works well enough.

As your project grows, you’re probably going to need to organise the create menu to avoid filling it up with asset types which, depending on how much you use scriptable objects in your game, can happen surprisingly quickly.

Luckily, it’s possible to organise your assets into groups, using subfolders.

Like this:

[CreateAssetMenu(menuName = "Folder 1/Folder 2/Enemy Stats")]
public class EnemyStatsProfile : ScriptableObject
{
}

Which, in your menu, looks like this:

Scriptable Object Create Asset Menu Sub Folders

Which can be useful for keeping categories of assets together.

While the order value determines where the menu item will appear in the list:

[CreateAssetMenu(menuName = "Enemy Stats", order = 2)]
public class EnemyStatsProfile : ScriptableObject
{
}

While this can be useful for organising your asset types, the way it works can be a little unintuitive.

For example, the order value doesn’t just apply to scriptable objects at the top of the create menu, it actually applies to every entry on the list, including Unity’s built-in options.

Which means that it’s possible to place an item anywhere on the list if the order value is high enough.

So how does that work?

Unity’s menu items are organised based on their order value or, if they’re a subfolder, the lowest order value of their contents.

Unity’s first built-in option is the New Folder item, which has an order value of 201, however, in order to maintain the existing subdivisions in the Create menu, you’ll need to keep your subfolders’ order values lower than 8.

This is because Unity automatically creates a divider between items that have a gap in value of 12 or above.

Which is useful, as it allows you to create divided sections in your subfolders while still being able to choose where the folder itself should go.

Visualisation of a scriptable object menu structure in Unity: "Sub folders are ordered by their lowest order value"."

Subfolders are ordered depending on their lowest order value, allowing you to sort items within folders more easily.

For a deep dive on the Unity menu system see Edward Rowe’s article here.

How to create a scriptable object at runtime

Most of the time, the easiest way to create a new instance of a scriptable object is to right-click in the project window and make it yourself.

However, it’s also possible to create a new instance of an asset while the game is running, using the Create Instance function.

Like this:

MyScriptableObjectType newScriptableObject =  ScriptableObject.CreateInstance<MyScriptableObjectType>();

This will set a reference to a newly created scriptable object asset.

However, this asset is temporary. Once the game ends it’ll be destroyed and, while the game is running in play mode, it won’t show up in the project window.

Which makes sense.

After all, if you’re creating scriptable objects while the game runs, you won’t be able to manually edit them anyway like you would when working in the editor.

However, if you want to use the create instance function to save a number of asset instances to your project as files, for convenience, then you can still do that.

Simply use the Create Asset function of the Unity editor’s Asset Database class and pass in the type of scriptable object you’d like to create.

Like this:

MyScriptableObjectType newScriptableObject =  ScriptableObject.CreateInstance<MyScriptableObjectType>();
UnityEditor.AssetDatabase.CreateAsset(newScriptableObject, "Assets/SO.asset");

Keep in mind, however, that this will only work in the editor, and you’ll need to remove this code from your game when you come to build it.

Scriptable objects are extremely versatile and, in a lot of ways, they work just like regular scripts.

But, there are differences, so what will work in a scriptable object, and what won’t?

What can you put in a scriptable object?

Scriptable objects basically work like regular classes except that they’re assets, not components.

You can put pretty much anything you like in a scriptable object, such as values or functions, even references to real objects in a scene, and it will work. 

However, there are a couple of differences between a Monobehaviour script that’s attached to an object and a scriptable object class that exists in your project.

For example, regular script components are temporary, they exist in the scene and, unless the object they’re attached to is preserved using Don’t Destroy on Load, the script, and its data, will be lost when the scene changes.

Scriptable objects, on the other hand, because they’re assets, exist outside of the scene, meaning that their data will remain the same when the scene changes or, in the editor, even when exiting play mode.

Which can be useful for passing data between scenes.

For example, the player’s health value, if it’s stored in a scriptable object, will be the same in a new level as it was at the end of the last.

Allowing you to store value-type data between scenes easily.

Reference variables, however, such as references to game objects and instances of Monobehaviour classes, point to real objects in the scene.

Meaning that, while you can use them in scriptable objects, when the scene changes, and those objects are destroyed, any references to them will be missing.

Missing Game Object

This isn’t necessarily a problem however as, so long as you check the field before using it, missing objects still return null in the same way that empty references do.

Scriptable object Type Mismatch

If you’re trying to reference a real game object in your scene from a scriptable object, and that reference is public, which it’s likely to be, you’re going to see a Type Mismatch entry in the field in the inspector.

This happens because Unity cannot serialise a reference to an in-game object from an asset, meaning that you won’t be able to see what the reference is pointing to, only that a reference exists.

However, while this may appear as if it’s an error, the reference will still work.

There’s no problem with using scriptable objects to reference in-game objects in this way, however, unlike the asset, the object that the reference is pointing to will only exist for as long as the scene does, so it’s not possible to create permanent references to in-scene objects from assets.

Instead, it’s better to treat references to objects from assets as ‘live data’, that is likely to change or that may be null or missing, such as a currently selected object reference, for example.

While being able to store data beyond the life of a scene might be useful to you, there may also be times when you’d like scriptable object data to reset with the scene in the same way that a regular script would.

For example, the structure of your game might mean that the player’s health should reset to a default value when a new scene is loaded, just like it would if it existed in the scene.

But how can you do that using scriptable objects?

One way is to provide a reset function in your scriptable object asset.

Like this:

[CreateAssetMenu]
public class PlayerData : ScriptableObject
{
    [SerializeField] float defaultHealth=100;
    [SerializeField] float defaultMagic=100;
    [HideInInspector] public GameObject myObject;
    [HideInInspector] public float health;
    [HideInInspector] public float magic;
    [HideInInspector] public Vector3 currentPosition;
    public void ResetData()
    {
        myObject = null;
        health = defaultHealth;
        magic = defaultMagic;
        currentPosition = Vector3.zero;
    }
}

In this example, I’ve created a set of default values that can be manually set on the asset in the inspector.

This works because they’re serialised, meaning they will be visible and editable, but are, technically private, so that other scripts aren’t able to change them.

While the script’s live dynamic values are hidden in the inspector for simplicity, but are publicly available to scripts in the scene that will control it or read from it.

Then, when the game is running, if I want to restore the live variables to their default values I can simply call the Reset Data function.

But when would you call it? And where from?

Normally, if you wanted to set up a script when it’s first loaded, you might do it in Start, Awake or the On Enable function.

However, most Monobehaviour event messages, such as Start and Update, aren’t called on scriptable objects and, while Awake and On Enable are called, they don’t work in the same way.

So how can you initialise a scriptable object when the scene loads?

Just like any asset, scriptable objects can’t really be used on their own. 

And while you can control scriptable objects from other scriptable objects, typically, you’ll always need a real script that’s attached to a real game object in the scene in order to use it.

In which case, you can reset your scriptable object’s data with the object that’s controlling it, such as the player, for example.

Like this:

public class Player : MonoBehaviour
{
    public PlayerData playerData;
    private void OnEnable()
    {
        playerData.myObject = gameObject;
    }
    void Update()
    {
        playerData.currentPosition = transform.position;
    }
    private void OnDisable()
    {
        playerData.ResetData();
    }
}

Using Awake and On Enable with scriptable objects

Normally, when using a Monobehaviour script, you might use Awake or On Enable to initialise connections and set up the class.

However, in a scriptable object, while some of these functions are still called, they may not work in the way you expect them to.

For example, on a Monobehaviour, Awake is called when the instance of the script is first loaded.

While, in a scriptable object, it’s called when the instance is first created.

Which, when you’re working in the editor, is when you right-click to create the new instance while, in the game, it’s when the application first runs.

Likewise, On Enable is called when the scriptable object is loaded.

In the editor, this typically happens when the scriptable object is first created, when entering play mode and when recompiling code.

In the built game, On Enable appears to be called at the start of the first scene, regardless of whether or not it contains a reference to the scriptable object asset.

So can you use On Enable or Awake to manage your scriptable objects?

While it is possible to use Awake or On Enable for first-time set-up functions in scriptable objects, and while Unity does provide some guidance on when they might be called, it’s generally easier to use Monobehaviour classes in your scenes to control the data in your assets whenever you can.

After all, your game happens in your scene, which is why managing the order of functions with a Monobehaviour can be more predictable than trying to do it in a scriptable object.

What can’t you do with scriptable objects?

Scriptable objects can be extremely effective at building all kinds of data systems and can usually make creating the content of your game much easier to do.

However, they can’t do everything and, as useful as they are, there will often be times when a scriptable object isn’t the best option.

So what can’t you do with a scriptable object?

Attaching a scriptable object to a game object

It’s not possible to attach a scriptable object to a game object.

This is because scriptable objects are assets, not components, meaning that, while you can reference them from the scene, you can’t attach a scriptable object as a component.

Instead, in order to use a scriptable object inside of a scene, you will typically have to declare an instance of it inside a regular script first and then connect it in the inspector, just like you would with any other asset.

Modular code with scriptable objects

Scriptable objects are fantastic at creating modular systems, where it’s possible to swap out one instance of a data type for another easily.

However, they’re not necessarily suitable for creating modular code, for example, where the same interaction creates significantly different logic.

This is because the code inside a scriptable object is always the same, while its data, the values assigned to each variable, are unique to the asset instance.

Meaning that, while it is possible to create dynamic elements in a scriptable object, such as collections of data that can change, or by using modular points of contact between scripts, such as Unity Events, delegates or even other scriptable objects, generally if you want a single point of interaction to trigger a different set of logic, you may find it easier to use an Interface instead. 

An interface, by design, allows different scripts to receive the same function calls but interpret them in completely different ways, something you can’t easily do in a scriptable object. 

Saving data with scriptable objects

While scriptable object data does persist throughout a session, scriptable objects do not save data on their own.

In the editor, changes made inside play mode will not reset, which might trick you into thinking that the finished game will behave in a similar way. But, in the standalone player, scriptable object data is temporary, and will be lost as soon as the application closes.

This means that, while it may be useful to use scriptable objects to organise the data that you want to save, you’ll still need to use a file save system to actually save information from one session to the next.

Scriptable object best practice

Scriptable objects can be used in lots of different ways, which is great as they allow you to create data systems that would be difficult, or impossible to build using regular Monobehaviour classes.

However, because scriptable objects are so flexible it can sometimes be difficult to know if the way that you’re using them in your project is ok or not.

For example, if you use a lot of scriptable objects in your game, will you run into performance problems later on?

Are scriptable objects bad for performance?

Generally speaking, no.

There’s no significant difference between accessing a member variable in a script and accessing a variable in an instance of a scriptable object.

Even when using a large number of scriptable object instances, the performance and the amount of memory that’s used is typically the same2.

However, while there’s not necessarily a performance overhead to using scriptable objects, using them at scale can impact performance.

But… this is more to do with how you use them than because of what they are.

For example, an events system built using scriptable objects may technically be less efficient than an events system that’s built on C sharp events alone, given that it uses Unity Events, which are, reportedly, less efficient than other types of delegate.

However, in most cases, the benefit of making your project easier to work with is probably going to outweigh any real performance hit you might encounter as a result of using one system over another.

In which case, unless you’re building a very large project, or raw performance is a priority for you, it’s generally better to use whatever system makes your project easier to finish and simply avoid extremely wasteful practices wherever you can, such as constantly checking for changes in Update or creating lots of garbage.

When should you use a scriptable object?

Generally speaking, there are two main reasons why you might want to use a scriptable object for something.

  1. To create multiple instances of a type of data outside of the scene, such as settings configurations, stats profiles and inventory items.
  2. If you want to be able to point multiple scripts from inside the scene to shared data in the project. For example, if you want to create globally accessible data, but without the drawbacks of using statics.

Meaning that, if you’re trying to solve one or both of those problems, scriptable objects may be a good place to start.

However, if you’re new to Unity, it can still be tricky to know when it’s a good idea to use a scriptable object instead of something that works in a similar way.

For example, enums.

Scriptable objects vs enums

Enums are a custom data type that can be used to create a set of predefined options.

They can be useful when you want to define something using a human-readable naming convention, such as a player class for example.

Like this:

public enum ClassEnum 
{
    Mage,
    Samurai,
    Warrior,
}

Which, in a field, allows you to select a class as an option in the inspector.

Enum Player Class

Enums are often used to represent a list of named options, however, behind the scenes, they work using implicitly defined integer values.

What this means is, that the enum you select is referenced using a simple number, there’s no connection to the named option.

Meaning that if you add a new entry to the list and the order changes, previously allocated values may now be different without warning, because their integer number has changed.

While it’s possible to explicitly assign enum values with custom integers to leave space for new entries, depending on how you want to use the enum, it can sometimes be easier to use a scriptable object instead.

Like this:

[CreateAssetMenu]
public class ClassAsset : ScriptableObject
{
}

Then to create different options, simply add new instances of the asset.

Enum Asset classes

When using a scriptable object in this way, there’s no need to actually place anything in the body of the script, in which case the instance can be used in almost the exact same way as an enum, except that making new instances won’t affect others that have already been set.

However, an added benefit of using the scriptable object method over regular enums is that additional data can also be associated with it, such as statistics of the class for example.

Like this:

[CreateAssetMenu]
public class ClassAsset : ScriptableObject
{
    public int strength;
    public int magic;
    public int dexterity;
}

So which should you use?

Generally speaking, for a finite list of options, where all you want to do is differentiate between one option and another, enums are probably going to be simpler and easier to use.

However, if you’re likely to change or add to the list of options, or if you want to be able to associate additional data with each selection, scriptable objects are ideal for that instead.

Scriptable objects vs prefabs

Scriptable objects, like prefabs, allow you to create templates of data that can be reused in your project.

But when should you use a prefab, and when should you use a scriptable object?

Prefabs can be used to store an entire object, including a hierarchy of child objects, containing interconnected scripts and components, all with their own unique data.

Screenshot of a prefab in Unity

A Prefab can be useful when you want to store a set of objects together, while scriptable objects are useful for storing types of data.

While scriptable objects represent an instance of a particular data type only, but can be used to create multiple unique copies of it, without needing to be attached to an object.

Which means that, generally, the decision of whether to use a prefab or a scriptable object depends on what the asset is.

If the focus of the entity is as an object, or group of objects, then a prefab is the right choice for storing it as a template and reusing it in your project.

However, if the focus of the entity is data, a definition of a type of item or a stats class for particular type of enemy, then a scriptable object makes more sense.

While it’s possible to recreate some of the functionality of a scriptable object using prefabs, where prefab variants can be used to create instances of a prefab with different configurations, if the entity you’re trying to create instances of is mostly data, not a collection of objects, then it’s probably going to be better to store it as a scriptable object.

This will allow other scripts to interact with it as a data type, as opposed to a type of object, allowing you to share information around your scene more easily.

In practice, you’re likely to combine object prefabs with scriptable objects, where objects of a similar type hold different instances of data. 

Just like any system in your project, scriptable objects can be as complex as you want them to be.

They can be in-depth functional classes, or they can be basic data containers.

Ultimately, what you do with them is up to you and how you want to manage the data in your game.

But, if scriptable objects are new to you then, before you try using them for everything, it can be helpful to understand how they can be useful.

So what exactly can you do with scriptable objects?

Scriptable object examples

There are a lot of ways you could use scriptable objects to make managing data in your game easier.

However, if you’re still new to the idea of data assets, knowing how you might use them and what that might look like, might be a little tricky.

So, to help with that, here are a few examples of how you could use scriptable objects in your game.

Scriptable object global variables

Because scriptable objects are assets, they can be used to manage global data in your game.

Global data is, generally, data that can be accessed from anywhere in the scene.

While there are many different ways you could create global data in Unity, scriptable objects are a good fit because they exist outside of the scene, meaning that any script can access the variable, without needing a direct connection to the script that will control it, while avoiding some of the drawbacks of alternative methods, such as statics which, while globally accessible, can only typically be used for one-of-a-kind data.

This method works by creating a scriptable object asset that represents a type of variable, such as a float,

Like this:

[CreateAssetMenu]
public class FloatVariable : ScriptableObject
{
    public float value;
}

Then, whenever you need to use a global float variable, simply create an instance of one, such as a Player One Health float variable, for example.

Example of a scriptable object global variable

Then, when you want to use it in a script, simply declare a type of Float Variable and connect it manually in the inspector.

Like this:

public class Player : MonoBehaviour
{
    public FloatVariable playerHealth;
    private void Start()
    {
        playerHealth.value = 100;
    }
}

For more information on how to pass data around your scene, including using scriptable objects for global variables, try my article on getting a variable from another script.

Scriptable objects work well for global variables because any script in any scene can easily access them, without needing a tight connection to the object that controls the data.

For this same reason, scriptable objects can also be useful for creating global event systems.

Scriptable object game events

Scriptable objects can be used to create globally accessible events in your game.

But why use scriptable objects for this?

Event delegates can be used in Unity to allow one script to respond to the actions of another, without having to constantly check if something is different.

Which can be useful for connecting all of the different things that will happen in your game to the things that will happen as a result.

However…

To use an event delegate, the subscribing script, which is the component that will respond to something else when it happens, still needs a reference to the class that will trigger the event so that it can subscribe its own functions to it.

This means that, to use an event, you’ll still need to find a way to create a direct connection between the scripts in the scene or, if you’re using statics, it will mean that only one event of that type can exist.

Scriptable objects can help to solve both of those problems.

Using scriptable objects as game events allows you to create multiple instances of an event asset type and, because it’s an asset, multiple scripts can point to the same event instance, either to trigger it, or to subscribe a function to it, without needing references to each other.

scriptable object game event

Scriptable Objects can be used to create global game events that can be accessed from any object.

It works by creating a Game Event, which is a scriptable object type.

Which looks like this:

using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName ="Game Event")]
public class GameEvent : ScriptableObject
{
    private List<GameEventListener> listeners = new List<GameEventListener>();
    public void TriggerEvent()
    {
        for (int i = listeners.Count -1; i >= 0; i--)
        {
            listeners[i].OnEventTriggered();
        }
    }
    public void AddListener(GameEventListener listener)
    {
        listeners.Add(listener);
    }
    public void RemoveListener(GameEventListener listener)
    {
        listeners.Remove(listener);
    }
}

Then, in your scene, whenever you want to raise an event, such as to start or end the game, simply create a Game Event asset and call the Trigger Event function on it from its controlling script.

Adding game events to a script field

For example, you could use a game controller to trigger either an On Game Won or On Game Lost event when the game is over.

Like this:

public class GameController : MonoBehaviour
{
    public GameEvent onGameWon;
    public GameEvent onGameLost;
    void EndGame(bool gameWon)
    {
        if (gameWon)
        {
            onGameWon.TriggerEvent();
        }
        else {
            onGameLost.TriggerEvent();
        }
    }
}

The game event is, basically, a list of Game Event Listeners, which is a regular Monobehaviour script that can be attached to an object that should respond to an event.

Like this:

using UnityEngine;
using UnityEngine.Events;
public class GameEventListener : MonoBehaviour
{
    public GameEvent gameEvent;
    public UnityEvent onEventTriggered;
    void OnEnable()
    {
        gameEvent.AddListener(this);
    }
    void OnDisable()
    {
        gameEvent.RemoveListener(this);
    }
    public void OnEventTriggered()
    {
        onEventTriggered.Invoke();
    }
}

The game event listener subscribes itself to the game event’s list of listeners and, when the game event is triggered, its local Unity Event is called in response.

This works because the game event listener and the game event, although they are not directly connected, are referencing the same game event asset.

While the Unity Event can be used to connect the listener to a local script on the same object and trigger one, or more, of its functions in response.

Screenshot - Game Event Listener Unity Event

Using a Unity Event as a final response allows you to create a modular connection to the event.

This particular method of creating global events can be extremely easy to work with since, once it’s set up, it’s possible to create new events and connect them up to other local scripts without writing any more code.

Which, even if you’re comfortable with scripting, can help to make the process of building your game’s content easier.

For more information on how scriptable object events work, try my article on Events and Delegates in Unity.

For more information on how to use scriptable objects for global variables and game event data, try Ryan Hipple’s Unite Ausitn talk, where he describes these methods in more detail.

While scriptable objects can be useful for creating global data, such as for events and variables, that’s not typically how they’re supposed to be used.

A more traditional use of scriptable objects is to use them to create different configurations of data but in a common format, that can be easily passed around.

Such as creating inventory items, for example.

How to create an inventory system using scriptable objects

Scriptable objects are ideal for creating data assets, which are simply abstract configurations of the same type of data, and one example of this is for creating item profiles.

The reason this approach can be so useful is that it allows you to define what an item is in the project using the inspector, where you can set its stats, description, icon and visuals, all without needing to actually build the object in the scene.

After which, different scripts will be able to use an item definition to represent it in different ways, such as a collectable object in the scene, or as an item entry in an inventory system.

So how could that work?

To build a basic inventory system using scriptable objects, first, create an Item asset type.

Like this:

[CreateAssetMenu]
public class Item : ScriptableObject
{
    public string itemName;
    [field: TextArea]
    public string description;
    public GameObject model;
    public Sprite icon;
    public int cost;
    public bool canDiscard;
}

Then, create instances of the item class in your project as assets and enter their details in the inspector.

Scriptable Object Item

Then, in the game, you could use the item definition with Monobehaviour classes to create real instances of items that can be collected, for example.

Like this:

public class CollectableItem : MonoBehaviour
{
    public Item item;
    public Item CollectItem()
    {
        // returns the item type and destroys the game object
        Destroy(gameObject);
        return item;
    }
}

Then, when the item is collected, the same item type can be used to create a representation of it in the player’s inventory.

In this example, an collectable item can be picked up using its Collect Item function, which destroys the object in the world but returns a reference to the type of item it held.

Which can be used to allow an Inventory class to add an item of the same type to one of its item slots.

Like this:

public class Inventory : MonoBehaviour
{
    public Item[] items;
    public int maxSlots = 10;
    private void Start()
    {
        items = new Item[maxSlots];
    }
    public void AddItem(CollectableItem collectable)
    {
        for (int i = 0; i < items.Length; i++)
        {
            if (items[i] == null)
            {
                // There is space for the item, collect it & destroy the original
                items[i] = collectable.CollectItem();
                return;
            }
        }
        // There is no space, don't collect the item
    }
}

But why do it this way?

Basically, this works because it allows different classes to identify a type of item just by pointing to the same piece of data.

Which can be useful, as it allows you to transfer an item between different containers more easily, such as from a collectable class to an inventory slot, without needing to copy any specific information over with it.

This works because the item data is technically the same, it’s simply referenced from two places, which can make it easier to pass data around and easier to compare item types, allowing you to stack inventory items when storing them.

And, if you do want to create item-level data, meaning information that only applies to the individual instance of an item, not its type, such as an object’s condition, for example, you can.

One way to do it is to encapsulate the item’s type data and its own, unique data, side by side into a new container, such as an Item Instance struct.

Like this:

public class CollectableItem : MonoBehaviour
{
    public ItemInstance itemInstance;
    public ItemInstance CollectItem()
    {
        // returns the item type and destroys the game object
        Destroy(gameObject);
        return itemInstance;
    }
}
[System.Serializable]
public struct ItemInstance
{
    public int condition;
    public Item itemType;
    ItemInstance(int itemCondition, Item itemAssetType)
    {
        condition = itemCondition;
        itemType = itemAssetType;
    }
}

Then, instead of using an array to hold each entry, because structs are, basically, values and can’t be null-checked, you could use a list to store each unique item instance instead.

Like this:

public class Inventory : MonoBehaviour
{
    public List<ItemInstance> items = new List<ItemInstance>();
    public int maxSlots = 10;
    public void AddItem(CollectableItem collectable)
    {
        if (items.Count < maxSlots)
        {
            items.Add(collectable.CollectItem());
            return;
        }
        // There is no space, don't collect the item
    }
}

Inventory systems can sometimes be tricky to build, particularly if you don’t know where the information about each item is supposed to go.

Because scriptable objects allow you to define what an item is outside of your scene, in a format that can be easily referenced and compared by different types of item container, they can make solving this specific problem a little easier.

However, they can also be used to help with a problem that, in Unity, is much more common.

Scriptable object singletons

Singletons are often used in Unity to solve a very common problem.

Specifically, they solve the problem of how to access important scripts and systems in a scene without a direct reference.

And, while they are, occasionally, controversial, they can be extremely useful if you use them the right way.

However, singletons work on the same basis as static variables, where access to a singleton’s local data and systems are provided via a static reference that any class can use.

Which means that they also suffer from the same drawbacks as using static variables, generally that you can only create one of whatever it is you’re providing a connection to.

In many cases, when you’re building a truly one-of-a-kind system, such as an audio manager, for example, this may not be a problem. 

But, if you’re using a singleton to provide access to an object that you may want to duplicate later on, such as when creating multiple players, a singleton may not be the answer.

So what can you do instead?

One option is to create a Scriptable Object Singleton, which combines the convenience of access of a master singleton pattern, sometimes referred to as a service locator, with the flexibility of scriptable objects, specifically the ability to create multiple instances of the same system.

For example, imagine that you want to provide easy access to a number of different player systems via a single point of entry.

Such as the player’s stats, their inventory items and a set of live data that’s updated as the game runs.

It’s possible to provide access to all of those systems via one point of access that can be shared with any other script.

So how does it work?

First create a Player ID scriptable object.

Like this:

[CreateAssetMenu]
public class PlayerID : ScriptableObject
{
    
}

Then, create one, or more, player instances in your project using the Player ID asset type.

Player One ID asset

This is the top-level definition of a player and it will be used to access all of their data and subsystems, which will also be scriptable object assets, and that will each be referenced from the player ID.

For example, you could build a basic stats asset,

Like this:

[CreateAssetMenu]
public class PlayerStats : ScriptableObject
{
    public int str = 10;
    public int dex = 10;
    public int mag = 10;
}

Then, so that other scripts can access it, provide a reference to a Player Stats type, as well as any other subsystems you many want to include, in the Player ID class.

Like this:

[CreateAssetMenu]
public class PlayerID : ScriptableObject
{
    // In this example, I've created subsystem assets for the player's stats, inventory and live data.
    public PlayerStats stats;
    public PlayerInventory inventory;
    public PlayerLiveData data;
}

Essentially, these are scriptable objects within scriptable objects, allowing you to create a hierarchy structure of information that permanently exists outside of the scene.

Scriptable Object Player Systems

What this means is, that, all a script will need to use a player’s data is a reference to their Player ID, giving them access to all of their subsystems, and the variables within them.

Like this:

public PlayerID playerID;
private void Start()
{
    Debug.Log("The player's strength is " + playerID.stats.str);
}

However, while the player’s data may exist in the project, the player object will exist in the scene.

Which means that there needs to be a connection between a particular set of player data and the real object it belongs to.

To do this, create a Player Class and attach it to the root of your player object.

Like this:

public class Player : MonoBehaviour
{
    public PlayerID ID;
}

This script is a regular Monobehvaiour and will act as a link between a particular player object and the set of data that it’s associated with.

This can be useful for retrieving information dynamically, such as if an object collides or interacts with the player in the game and wants to access its data.

In which case, all it needs to do is lookup the subsystem and variable it wants via the player class.

What’s more, the player object’s local scripts can also use the player class to automatically access their own player’s data.

Like this:

public class PlayerSystem : MonoBehaviour
{
    Player player;
    void Start()
    {
        player = transform.root.GetComponent<Player>();
        Debug.Log("There are " + player.ID.inventory.items.Count + " items in my inventory");
    }
}

This works by getting a reference to the player class in Start, by getting the player script component directly from the root object using the Transform Root property.

This is generally easier than connecting each script on the player to the player ID asset directly, since it means that the player ID that a particular player object corresponds to can only be changed in one place, the player class, making it easy to swap out the player ID asset to create new, additional, players.

Visualisation of a scriptable object singleton

The Player ID is a definition of a specific player, such as Player One, while the linked subsystems are their specific data assets.

Nesting scriptable objects inside of scriptable objects can allow you to make modular systems using fixed asset types, where the type of data is the same, but the instances are different.

This approach can also be useful when you want to combine the convenience of scriptable object data with a game system that needs to be more flexible, such as a quest system.

How to build a quest system using scriptable objects

Most of your game’s content, such as models, sounds and other assets, exist in your project to be used by scripts and objects in your game’s scenes.

So, when your content is data, such as a quest or an objective, using scriptable objects to manage them can make a lot of sense.

For example, it’s possible to create a scriptable object Quest Asset that can be used by a Player Class, to store and track the status of a particular quest, and its objectives, in your game.

Like this:

using System;
[CreateAssetMenu]
public class Quest : ScriptableObject
{
    public event Action<Quest> OnQuestCompleted; 
    public bool active;
    public bool completed { get; private set; }
    [TextArea]
    public string questDescription;
    public List<Objective> objectives = new List<Objective>();
    public void TryEndQuest()
    {
        for (int i = 0; i < objectives.Count; i++)
        {
            if (objectives[i].Completed != true)
            {
                if (objectives[i].required)
                {
                    // the quest is not complete
                    return;
                }
            }
            completed = true;
            active = false;
            OnQuestCompleted?.Invoke(this);
        }
    }
    void OnEnable()
    {
        for (int i = 0; i < objectives.Count; i++)
        {
            objectives[i].parentQuest = this;
        }
    }
}

In this example, the quest asset contains boolean variables to check whether or not the quest is currently active, if it’s already been completed, and a simple text area to provide a description of the quest that other scripts can read.

Importantly, it also contains a list of Objectives, which are also scriptable object assets, and that make up the content of the quest.

Which look like this:

[CreateAssetMenu]
public class Objective : ScriptableObject
{
    [HideInInspector] public Quest parentQuest;
    public bool required = true;
    public bool Completed { get; private set; }
    public Transform waypoint;
    [TextArea]
    public string description;
    public void CompleteObjective()
    {
        Completed = true;
        parentQuest.TryEndQuest();
    }
}

Each objective asset represents a particular step of the quest which can be read in order, or considered as a whole, where the quest will be able to check if an objective is complete or not by testing to see if the required objectives in its list have been finished.

How does an objective become completed?

Each objective can be completed by any script that has a reference to it.

So, for example, if your quest’s objective was to talk to an NPC called Steve, that character, who would keep their own reference to the objective that relates to them, would be able to complete the objective once they’d been spoken to.

Like this:

public class npcSteve : MonoBehaviour
{
    public Objective objective;
    public void Start()
    {
        objective.waypoint = transform;
    }
    public void Talk()
    {
        // Do some talking
        objective.CompleteObjective();
    }
}

When this happens, the objective that’s been completed will call the Try End Quest function on the quest asset that owns it using a reference it was given in the On Enable function when the asset was first loaded.

Then, if enough of the objectives have been completed, the quest raises the On Quest Completed action, passing in a reference to itself.

This can be picked up by the Player Class, which is a Monobehaviour on the player object that manages both the current quest and any active, unfinished quests.

Like this:

public class Player : MonoBehaviour
{
    public Quest currentQuest;
    public List<Quest> openQuests;
    public void ReceiveNewQuest(Quest quest)
    {
        openQuests.Add(quest);
        quest.active = true;
        currentQuest = quest;
        quest.OnQuestCompleted += RemoveCompletedQuest;
    }
    void RemoveCompletedQuest(Quest quest)
    {
        if (currentQuest == quest)
        {
            currentQuest = null;
        }
        quest.OnQuestCompleted -= RemoveCompletedQuest;
        openQuests.Remove(quest);
        if (openQuests.Count > 0)
        {
            // sets the next quest in the list as the current quest
            currentQuest = openQuests[0];
        }
    }
    // Subscribes and unsubscribes to quest completion events when turned on or off...
    private void OnEnable()
    {
        for(int i = openQuests.Count -1; i>=0; i--)
        {
            if (openQuests[i].completed)
            {
                // clears out old quests that were completed while the player was disabled
                RemoveCompletedQuest(openQuests[i]);
            }
            else
            {
                openQuests[i].OnQuestCompleted += RemoveCompletedQuest;
            }
        }
    }
    void OnDisable()
    {
        foreach (Quest quest in openQuests)
        {
            quest.OnQuestCompleted -= RemoveCompletedQuest;
        }
    }
}

This script manages what will happen when a quest is completed by subscribing a Remove Completed Quest function to the On Quest Completed Action, allowing the player to remove quests when they are finished.

And, in the event that the quest is somehow completed while the player object is turned off, it checks to see if any of the quests in its list need to be removed when it is enabled.

The player class also provides a public Receive Quest function, allowing other scripts, on other objects, to activate their quests with a player when interacting with them.

Like this:

public class QuestOwner : MonoBehaviour
{
    public Quest myQuest;
    void GiveQuest(PlayerQuestTest player)
    {
        player.ReceiveNewQuest(myQuest);
    }
}

Normally, scriptable objects are limited by the type of data that they can store. However, by using dynamic variable types, such as Lists, combined with other scriptable object types, it’s possible to build a modular quest system, using easy-to-manage asset data.

From the experts

If you’d like more information on scriptable objects and how to use them, try the following videos:

  • Ryan Hipple – Unite Austin 2017 – Game Architecture with Scriptable Objects
  • Brackeys – Scriptable Objects in Unity
  • Unity – Better Data with Scriptable Objects in Unity! (Tutorial)

Now it’s your turn

Now I want to hear from you.

How are you using scriptable objects in your game?

Are you using them as data containers to store information about items and objects?

Or are you using them for live data, gameplay systems or to pass information around your scene?

And what have you learned about scriptable objects that you know someone else would find useful?

Whatever it is, let me know by leaving a comment.

by John Leonard French

Game audio professional and a keen amateur developer.

Get Game Development Tips, Straight to Your inbox

Get helpful tips & tricks and master game development basics the easy way, with deep-dive tutorials and guides.

My favourite time-saving Unity assets

Rewired (the best input management system)

Rewired is an input management asset that extends Unity's default input system, the Input Manager, adding much needed improvements and support for modern devices. Put simply, it's much more advanced than the default Input Manager and more reliable than Unity's new Input System. When I tested both systems, I found Rewired to be surprisingly easy to use and fully featured, so I can understand why everyone loves it.

DOTween Pro (should be built into Unity)

An asset so useful, it should already be built into Unity. Except it's not. DOTween Pro is an animation and timing tool that allows you to animate anything in Unity. You can move, fade, scale, rotate without writing Coroutines or Lerp functions.

Easy Save (there's no reason not to use it)

Easy Save makes managing game saves and file serialization extremely easy in Unity. So much so that, for the time it would take to build a save system, vs the cost of buying Easy Save, I don't recommend making your own save system since Easy Save already exists.

Annotations

  1. Reportedly Unity’s folder menu item has a value of 20 however, in 2021.3.6f I found this to be 19 instead.
  2. Measured script CPU time and total memory used for 100, 1000 and 10,000 class instances each accessing their own member variables vs the same number of class instances accessing a unique instance of a scriptable object holding the same values and performing the same task.

Image Attribution

  • Unity icons created by Freepik – Flaticon

Comments

  1. So well written and explained. I could immediately understand the principles and start up my own demo projects to test my understanding.

    Love your work.

Leave a Comment