Using ScriptableObject for data objects in Unity scenes

When you start developing with Unity, one of the basic thing you need to get your head around is the way Unity handles classes and data for your game.

I’m not going to in the details — you should read the great blog post at Unity3d.com about the serialization here. (They really should do a blog post / tutorial / documentation about how to store your data, like a flow chart to decide where to put your data ūüôā )

Common Data, Common Problem

Common problem is where to put data that you need to share between components. A neat solution for persistent, config-kind-of-data is to put it in a custom ScriptableObject that then is saved to the asset database. You can then reference this data from the components in the scene, and they all will share same values. For example if you need team colors (red and blue) instead of sprinkling the colors to every possible component you can just have a reference to a TeamColor ScriptableObject and keep the color there Рthe same way you do with PhysicsMaterial for example. I usually have a GameDatabase asset which contains the global configurable parameters for the game. This data can be anything like lists of enemy prefab names, in-game shop configuration, list of chapters and levels in the game and their respective scene names etc.

Scene Only, Please

But what if you need to have shared data in the scene? You might not want to create assets for each data in each of the scene, as this makes the asset database cluttered with data only relevant to a certain scene.

I guess many developers don’t know that they can actually instantiate ScriptableObjects and have them exist only in the scene.¬†If ScriptableObject is referenced by the scene and stored as an asset in the asset database, only a reference to the asset is stored in the scene file. But if ScriptableObject is NOT in the asset database, it actually gets serialized into the scene file. Let’s call these kind of ScriptableObjects by Scene ScriptableObjects (SSO’s ūüôā ). Note: this same applies to any assets, not only ScriptableObjects. You can also embed textures and meshes in scene files, many times by accident.

Having said that I haven’t used this feature much. Usually I end up putting scene specific data into a single component in the scene in some GameObject. For example if the each scene is a level in a game, I have a LevelInfo component that resides in a GameObject called “_levelInfo”. And all of the data goes there. Why do I do this? Let’s first see how to use ScriptableObject that goes only in a scene file.

How To

What we need first a the ScriptableObject to use:

using UnityEngine;
using System.Collections;
public class SOExample : ScriptableObject
{
public string text;
}

view raw
SOExample.cs
hosted with ❤ by GitHub

Then let’s create a component that holds a reference to this ScriptableObject. Note that this code creates a new ScriptableObject if it doesn’t have a reference. So by default each SOExampleBehaviour will have instance of their own SOExample:

using UnityEngine;
using System.Collections;
public class SOExampleBehaviour : MonoBehaviour
{
public SOExample test;
public void OnEnable()
{
// instantiate if needed
if (test == null)
test = ScriptableObject.CreateInstance<SOExample>();
}
}

If you now create a new GameObject and attach the SOExampleBehaviour script to it, you will see something like this:

so1

If you now double click the (SOExample) value in the behaviour, you will be able to inspect the ScriptableObject itself, and type a value to the text field :

so2

Now, if you save that scene and open it in a text editor (assuming that you’re using text serialization for the assets in Unity), you will see something like this:

….
— !u!114 &819407913 <<— this is our ScriptableObject id in the scene
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4a48e9db79a952f419ca79d71a03fe31, type: 3}
m_Name:
m_EditorClassIdentifier:
text: LOL <<— the text typed in the SO
….
— !u!114 &1539014338
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1539014337}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3032752457dc4fc438051e79d2e9d38f, type: 3}
m_Name:
m_EditorClassIdentifier:
test: {fileID: 819407913} <<— reference to the SO

view raw
SOExample scene
hosted with ❤ by GitHub

Nice, right? ..right?

Well, technically yes. But there’s a major UI/access problem.

 Accessing Scene ScriptableObjects

The first thing we want to do now is to share data between multiple instances of the behaviour. Just make a¬†new¬†game object and add the SOExampleBehaviour¬†and BOOM! You have another instance of the SOExample¬†— not what we wanted.

We can fix this easily, right? Just click the bull’s eye on the another and find the correct instance. The problem is that the scene finder will give you nothing:

os3

This is a major problem. It’s not even possible to drag the SOExample from first behaviour to the second behaviour. Normally this would work as the GameObjects and Components have spatial representation, but ScriptableObjects do not (unless they’re saved as assets).

We can’t access the scene¬†ScriptableObjects in any other way than using code. So let’s modify the SOExampleBehaviour a bit:

using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class SOExampleBehaviour : MonoBehaviour
{
public SOExample test;
public void OnEnable()
{
// find existing instance if needed
if (test == null)
test = Object.FindObjectOfType<SOExample>();
// then instantiate if needed
if (test == null)
test = ScriptableObject.CreateInstance<SOExample>();
}
}

Notice that I added [ExecuteInEditMode] to OnEnable work every time I tick the enabled flag for the component.

Which One of You I Meant

I’m also using Object.Find<SOExample>() to find a single instance of a SOExample, but I have no way of knowing which instance I get. If you already duplicated the SOExampleBehaviour previously, you’ll now have two instances of SOExample laying around and Unity returns either one by random. And if you need more than one instances of same type of ScriptableObject, you’re in trouble.

Conclusion

So using ScriptableObjects for shared data inside scene files is far from optimal, but possible. The problems are mainly UI/access issues. Nice thing about using ScriptableObjects in scenes is that you can later convert them into assets stored in the asset database, to be shared between scenes.

You probably understand why I’m not using SSO’s ūüôā Instead, I just create these specific data-only behaviours that I add to each scene. They get a GameObject and Transform (and they’re spatial), which is a bit of a waste. However, this hasn’t been a real issue, as the number of these objects is usually very limited. But on principle, it’s wrong.

NOTE: You can spawn and use¬†ScriptableObjects also run-time. However, because if you only need something during run-time, you don’t need that data to be serialized and using ScriptableObject for that does very little sense. It’s better to use plain C# classes for that.

ScriptableObjects do have one slight advantage over plain C# classes for run-time, and that is support for inspector. If you want inspector for plain C# data structures, you need to implement a custom editor, which can be quite tedious.

Also, I’m not sure how run-time ScriptableObjects behave during hot-loading (i.e. compiling scripts while the game is running inside editor), but I guess they would work better than plain C# classes which simply get wiped out due to the serialization.

In short: You can use ScriptableObjects for storing shared data inside scenes, but currently it’s clumsy and you should use other means like data-only behaviours or ScriptableObjects stored as assets.

Room for Improvement

Here are some issues I’d like to see addressed in the future regarding the ScriptableObjects:

  • Out of the box, it’s really difficult to instantiate ScriptableObjects when you’re using them as assets — you need to create or use custom editor code just for this purpose. It should be possible to spawn them using editor alone.
  • Finding and assigning scene ScriptableObjects is impossible, again without code. And even with code you can’t really pick specific instances if you have spawned more than one instance. The object finder in editor should list you all the SO instances in the scene (if you’re modifying a field for a scene object, that is)
  • There should be a view for the SSO’s similar to the scene hierarchy view, which you could then use to assign the SSO’s to behaviours.
  • SSO’s can be named in code, but the inspector for ScriptableObjects doesn’t display the SSO’s name, making working with multiple SSO’s really confusing…
  • Converting a SSO to an asset should be just matter of drag-n-drop, just the way like creating prefabs works.
  • The documentation. Don’t even get me started ūüôā The documentation is lacking, to put it mildly.

There. Hope this helps someone…

PS. Motivation for this post was Adam’s post about lack of support for runtime objects:¬†http://t-machine.org/index.php/2015/03/28/unity3ds-missing-core-no-runtime-objects