ruk·si

⬜️ Unity
Scriptable Object

Updated at 2016-11-03 22:56

For most use-cases you can use plain C# classes as data objects. Use normal classes but make sure they are not derived from anything else except System.object as Unity serialization loses class information. Also, don't share the same plain class instance between multiple MonoBehaviours as the reference is lost on serialization, duplicating all enclosed data. You can add [Serializable] to make the class visible in Editor.

Don't share plain class instance with another script. It will be duplicated on deserialization. If you need to share some data or functionality between multiple MonoBehaviours as a separate code file, use scriptable objects.

[Serializable]
class Animal { public string name; }
class Doggo : MonoBehaviour { public List<Animal> dogs; }
class Catto : MonoBehaviour { public List<Animal> cats; }

var a new Animal();
Doggo.dogs.Add(a);
Catto.dogs.Add(a);
// These will become 2 separate objects upon deserialization.

Scriptable objects are serialized separate from the MonoBehaviour. A plain class with [Serializable] is similar, but Unity serializes the custom class as part of the MonoBehaviour, whereas ScriptableObject are serialized separately and are only referenced by MonoBehaviour.

  • The intended use case for using ScriptableObject is to reduce memory usage by avoiding copies of values on deserialization.
  • Scriptable objects are useful when you use Unity's serialization features, which are mainly used in Editor mode. At runtime you can't use Unity's serialization system to serialize anything.
  • If you have a variable of a base class in your MonoBehaviour but you hold an instance of a derived class, the class will be lost / converted into an instance of the base type when serialized / deserialized.
using UnityEngine;

public class Obi : ScriptableObject
{
    public string name;
    public int age;
    public GameObject parent;

    // When SO is created.
    private void Awake() { Debug.Log("Awake"); }

    // When SO is created.
    private void OnEnable() { Debug.Log("OnEnable"); }

    // When SO goes out of scope, e.g. scene changes.
    private void OnDisable() { Debug.Log("OnDisable"); }

    // When SO will be deleted soon e.g. application exit.
    private void OnDestroy() { Debug.Log("OnDestroy"); }

    // Note that SOs cannot have coroutines so you need to create extra GO for that:
    // Example.instance.StartCoroutine(MyCoroutine())
}
using UnityEngine;

public class Objector : MonoBehaviour
{
    public Obi obi;

    private void Awake()
    {
        obi = ScriptableObject.CreateInstance<Obi>();
        obi.name = "John";
        obi.age = 100;
        obi.parent = this.gameObject;
    }
}

Note that scriptable objects don't have Start callback.

using UnityEngine;

public class Gorilla : ScriptableObject
{
    public string matingCall = "RAWR!";
    public void Rawr() { Debug.LogWarning(matingCall, this); }
    private void Awake() { Debug.Log("Awake"); }
    private void OnEnable() { Debug.Log("OnEnable"); }
    private void OnDisable() { Debug.Log("OnDisable"); }
    private void OnDestroy() { Debug.Log("OnDestroy"); }
}

Execution order example:

  1. ScriptableObject.Awake()
  2. ScriptableObject.OnEnable()
  3. Gorilla.Rawr()
  4. ScriptableObject.OnDisable() (if destroyed)
  5. ScriptableObject.OnDestroy() (if destroyed)

Scriptable objects must be destroyed manually or they will stay in editor scope until you enter the play mode or change scene in editor.

using UnityEngine;
using NUnit.Framework;

[TestFixture]
public class GorillaTest
{
    [Test]
    public void GorillaRawrs()
    {
        var gorilla = ScriptableObject.CreateInstance<Gorilla>();
        gorilla.Rawr();
        Assert.AreNotEqual(true, false);
        ScriptableObject.DestroyImmediate(gorilla);
    }
}

ScriptableObject asset files are useful for storing data that doesn't fit naturally within the Unity prefab system. They are much easier to work with in Unity than XML, CSV, or other traditional ways of storing such data. You can edit the values of the SO like any prefab.

using UnityEngine;

public class Gorilla : ScriptableObject
{
    // ...
    [MenuItem("Assets/Create/Gorilla")]
    public static void CreateAsset ()
    {
        Gorilla gorilla = ScriptableObject.CreateInstance<Gorilla>();
        AssetDatabase.CreateAsset(gorilla, "Assets/Resources/MyGorilla.asset");
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = gorilla;
    }
}
// You load it using Resources:
var gorilla = Resources.Load("MyGorilla") as Gorilla;

Saving and loading scriptable object from Resources:

[Serializable]
public class GameData : ScriptableObject {
    public string SavePath;
    public static void Save() {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(SavePath);
        bf.Serialize(file, this);
        file.Close();
    }
}

public static T LoadScriptableObject<T>() where T : ScriptableObject
{
    System.Type type = typeof(T);
    return Resources.Load<T>(type.ToString());
}

Sources