This Friday we are going to be discussing a topic I know many of you are eager to learn more about: Scriptable Objects.
You may have heard rumors about the mystical powers Scriptable Objects hold or how they will change your game development. Well I’m here to tell you that Scriptable Objects are not magic, however they are still an extremely useful tool we can add to our knowledgebase as game developers.
So today I’m going to talk about:
- What are Scriptable Objects?
- How to Create and Use Scriptable Objects
- Advanced Uses of Scriptable Objects
And make sure you read all the way to the end because I am going to share with you a well-known talk on Scriptable Objects that really put this feature on the map for a lot of people. Let’s get into it!
1. What are Scriptable Objects?
The purpose of a Scriptable Object is to store data. These Scriptable Objects have special properties that give them specific advantages over other classes to be used in certain scenarios.
Scriptable Objects are just another special class in Unity. Much like MonoBehaviours, they inherit from the base “Object” class (another special class in Unity, not to be confused with the C# Object class).
Like MonoBehaviours, we configure Scriptable Objects in a C# script in our code editor of choice. Here we can configure variables and functions associated with the Scriptable Object. They also do have a basic lifecycle, which we will talk more about later.
However, unlike MonoBehaviours, we cannot attach Scriptable Objects directly to GameObjects.
So let’s discuss some of the advantages Scriptable Objects have over other forms of data storage.
For one, Scriptable Objects are a great central store for data. If you have a lot of similar objects in your scene you can have them all reference one Scriptable Object to avoid repeating data when you can.
For example, let’s say we are making a real-time strategy game where we have thousands of individual soldiers that all share similar properties. For all the things they have in common, such as maximum health, movement speed, attack type, etc. this could all be stored in one single Scriptable Object.
Instead of having every instance of the soldier in the game hold its own values for max health, movement speed, and so on, each soldier would reference the one Scriptable Object to get these unchanging properties. This reduces the amount of memory your game consumes.
Now keep in mind that all the unique and changing variables, such as current health or current weapon, would still need to be set for each instance of the soldier. Scriptable Objects are not magic – they’re just a useful tool.
Another thing to keep in mind is that Scriptable Objects are not the actual data we are trying to store – more accurately, they are a framework for the data we wish to store.
To demonstrate this, let’s go back to our RTS example from above – as of now we have many soldiers of one type that are all referencing their common values off of a single Scriptable Object.
But what if we want to create a new type of soldier? Do we have to write a new Scriptable Object?
No, we simply create a new instance of the Scriptable Object, modify the values to our liking, then assign that new Scriptable Object instance to our new soldier’s prefab.
So we could have a “light soldier” where the values are set on the Scriptable Object to have a low maximum health but a high movement speed; and a “heavy soldier” with high maximum health, but a slow movement speed.
So in a sense, we only wrote one Scriptable Object, that can have infinitely many variations, and infinitely many GameObjects that can reference each of these different variations.
And best of all, editing the values is done right in the inspector window, so new variations can easily be created by anyone regardless of programming ability.
Click here to learn more about the inspector window
So that is more or less the theory behind Scriptable Objects. Don’t worry if it doesn’t quite make sense to you just yet – next I’m going to walk you through creating and using Scriptable Objects, and you’ll have a much more tangible understanding of how they work.
2. How to Create and Use Scriptable Objects
To begin creating a new Scriptable Object, first create a new C# script the same way you normally would. It can be helpful (though not necessary) to name the class in such a way that lets you know it is a special class. I often append the word “properties” to my Scriptable Object classes, i.e. SoldierProperites.cs
Alright, now open up the script and clear out the Start() and Update() functions. All you need to do now is change the script to inherit from ScriptableObject instead of MonoBehaviour. So your script should look like this:
public class SoldierProperties : ScriptableObject
{
}
In general, Scriptable Objects contain mainly just properties as they are stores for data. However, just like any other C# class they can have methods too. Although, remember that if a method changes any data on a Scriptable Object, that value will be changed for ALL classes that reference the Scriptable Object.
This would also be a good time to mention that Scriptable Objects do have a basic lifecycle. Each of these functions will be called by Unity in the following order:
- Awake() – Called when the Scriptable Object instance is initialized
- OnEnable() – Called when Scriptable Object instance is loaded
- OnDisable() – Called when Scriptable Object instance goes out of scope
- OnDestroy() – Called when Scriptable Object instance is destroyed
But again, Scriptable Objects mainly consist of properties. If we were to add a few properties in, it looks like this:
public class SoldierProperties : ScriptableObject
{
public int maxHealth {get; private set;}
public float moveSpeed {get; private set;}
public string soldierRank {get; private set;}
}
Great, you now have a perfectly functioning Scriptable Object! Mostly your Scriptable Objects won’t need more than a few properties – though you can add more over time or even make more complex getters – but this is the bare bones.
If you were to pop back over to Unity now, you’d see the script you just created, however you may notice that you cannot attach the Scriptable Object class to a GameObject just like you could with a MonoBehaviour. This is because we first need to create an instance of the Scriptable Object.
We can create new instances through code (which I’ll show you how to do later), but we can also create them through the Unity editor – this empowers the designers, artists, and other non-programmers on your team to create new variations of things in your game.
To do this, we just need to add the CreateAssetMenu attribute to our Scriptable Object class as such:
[CreateAssetMenu(fileName = "SoldierProperties",
menuName = "Scriptable Objects/Soldier Shared Values",
order = 0)]
public class SoldierProperties : ScriptableObject
{
public int maxHealth {get; private set;}
public float moveSpeed {get; private set;}
public string soldierRank {get; private set;}
}
Click here if you want to learn more about C# attributes.
I’ve broken the attribute up into multiple lines for clarity, but you can have them all on the same line if you’d like. The options on the attribute to point out are:
- fileName – This is the default file name of your new Scriptable Object instance.
- menuName – This is the menu path you’ll follow when creating a new Scriptable Object instance
- order – Lower order values show up higher in the list when creating Scriptable Object instances
Let me show you what this all looks like. So now when you go back to the Unity editor, simply right click in the project hierarchy – when you hover the “Create” menu item, you’ll now see an option a the top for “Scriptable Objects” and hovering that, you’ll see an option for “Soldier Shared Values.” Click on that and a new instance of the Scriptable Object is created with a filename of: SoldierProperties.asset
Instances of ScriptableObjects use the .asset file type
Of course, you are free to change the filename to something a bit more specific to your needs such as StandardSoldierProperties. And clicking on this instance will allow you to modify all the values in the inspector for the Standard Soldier.
Want to make one for a more powerful soldier? Just create a new instance the same way you did before, but maybe rename the file to SpecOpsSoldierProperties and override the values to be much stronger.
As a best practice, I like to keep all instances of like Scriptable Objects in their own folders for good organization. In this case, I’d store both of these instances in a folder at Assets > ScriptableObjects > SoldierProperties
Now the one last thing I should mention is how to reference these Scriptable Objects from another class. Well it’s actually just as simple as adding our Scriptable Object type to a MonoBehaviour class and accessing the values as we would expect. For example, if we had a SoldierController class, we could do this:
public class SoldierController : MonoBehaviour
{
public SoldierProperties _soldierProperties;
private int _currentHealth;
private void Start()
{
_currentHealth = _soldierProperties.maxHealth;
}
}
In the simple example above, when the soldier is spawned, it’s current health value gets set to the maximum health value of the Scriptable Object assigned to it.
Assigning a Scriptable Object to a component is as simple as dragging in the desired Scriptable Object instance (.asset file) to the serialized or public field for the type of Scriptable Object it is.
By now you can start to see the power of Scriptable Objects – if we wanted, we could have a basic soldier prefab that we could vary indefinitely by creating new instances of the Scriptable Objects and assigning those to instances of our prefab.
With all that you have learned to this point, you could make a lot of improvements to your current game projects and workflow. But there are still some more advanced topics we can go into if you are already feeling comfortable with Scriptable Objects.
3. Advanced Uses of Scriptable Objects
One useful thing to know how to do is to create new instances of your Scriptable Object through code. This can be helpful for dynamically creating new variations on the fly or for unit testing.
To do this, all you need to do is call the static method on the ScriptableObject class CreateInstance<T>() where T is the type of Scriptable Object you’d like to instantiate. For example:
SoldierProperties fastSoldierProperties = ScriptableObject.CreateInstance<SoldierProperties>();
We could then set new values and assign it to a SoldierController class all through code.
Another instance Scriptable Objects are particularly useful is for setting preferences. For example, let’s say you are creating a game with two main control types – TypeA and TypeB.
You could create a Scriptable Object to act as a framework for these different control types and create two instances to store unique input values for different actions in the game.
When the player chooses a control type they want to use, your game would set the associated Scriptable Object instance in the input manager. Then when the player inputs commands, the input manager would look at the currently set Scriptable Object instance for the control type and perform the proper actions.
This means that the control schemes can be swapped in and out at any point during gameplay without any issues.
Combining this with creating Scriptable Object instances through code means that you could let the player create their own custom control scheme, save it, and set it as the active control set whenever they like.
Speaking of saving, there are many ways to save things in Unity, but one easy way that works well with Scriptable Objects is JSON Serialization. JSON Serialization is the process by which an object is converted into a JSON format – this JSON format is returned in a string data type and can be stored in player prefs. If we wanted to save a control type Scriptable Object instance called customControl, we could do the following:
string customControlJson = JsonUtility.ToJson(customControl);
PlayerPrefs.SetString("currentControlType", customControlJson);
To load the custom control set, we would essentially do the opposite:
string customControlJson = PlayerPrefs.GetString("currentControlType");
customControl = JsonUtility.FromJson(customControlJson);
Don’t worry if all the saving and loading doesn’t make sense quite yet, we’ll get to that in a future edition of Feature Friday.
The last thing I wanted to share you may be one of the most practical uses/features of Scriptable Objects. Much like MonoBehaviours, instances of Scriptable Objects can have their values edited at runtime during play mode testing, and the updated values are reflected in real time.
However, with Scriptable Objects, when you exit play mode, the values you set during play mode persist – they don’t get reset to their initial values like MonoBehaviours do!
So this means that if you are balancing your game in play mode, you can edit as many values as you want to dial in that perfect gameplay feel; then when you exit play mode all your changes are saved.
So those are a few more practical use cases for Scriptable Objects – I hope that now you can see the incredible value that these bring to your project.
Featured Unity Tutorial of the Week
And finally I do just want to leave you with a really interesting video on how you can use Scriptable Objects as core architectural components of your game. This talk comes from game developer Ryan Hipple during the Unite conference a few years back; but its message remains valuable today. At the time, this was a really important talk as it showed many people how Scriptable Objects can be used as key parts to your game. Check it out:
So I hope you can now see that Scriptable Objects are not magic, however they are still extremely useful to use in your game projects. Let me know if you’ve found any good uses for Scriptable Objects or if there are any other features of the Unity game engine you’d like to learn more about.
Anyways I hope you are all staying well, and as always, keep on creating!
-Johnny Thompson
Turbo Makes Games
Recent Comments