{"id":514,"date":"2021-09-11T16:03:14","date_gmt":"2021-09-11T16:03:14","guid":{"rendered":"https:\/\/bronsonzgeb.com\/?p=514"},"modified":"2022-12-23T22:41:09","modified_gmt":"2022-12-23T22:41:09","slug":"the-scriptable-object-asset-registry-pattern","status":"publish","type":"post","link":"https:\/\/bronsonzgeb.com\/index.php\/2021\/09\/11\/the-scriptable-object-asset-registry-pattern\/","title":{"rendered":"The Scriptable Object Asset Registry Pattern"},"content":{"rendered":"\n<p>This article describes a pattern to serialize prefabs or any complex data. Doing so allows us to store data to disk or send it over a network to either a game server or another game client. This article pairs well with the previous article on saving and loading data, so I recommend you read that one first if you haven&#8217;t already.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What problem are we solving?<\/h2>\n\n\n\n<p>We need a stable way to store and retrieve game objects using a simple reference. Doing so allows us to save the state of our world using a minimal amount of data. How? We store the data in a standard prefab or scriptable object in the project and reference it with an id, rather than keeping the entire structure in our saved state. Additionally, we can store any values that differ from the original, like health and position.<\/p>\n\n\n\n<p>At this point, you might be asking, &#8220;Hey, isn&#8217;t this what the Addressables package does?&#8221; There is some overlap; however, I&#8217;ve been using this pattern since before Addressables existed and continue to use it. Sometimes I even use both in the same project. Addressables is a more complex system focused on finding your assets no matter where they&#8217;re stored. This article describes a simple implementation of a pattern that you could execute in many ways, including using the Addressables system.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How do we do it?<\/h2>\n\n\n\n<p>We&#8217;ll create a registry to hold all the objects we need to serialize. In the simplest case, this is a Scriptable Object that contains a list of Descriptors, which is a type we&#8217;ll define. A Descriptor will also be a Scriptable Object that holds an id and references some data, such as a prefab. Then whenever we need to load a prefab from the Registry, we ask it to find the Descriptor with a given id and instantiate a new instance from the prefab reference.<\/p>\n\n\n\n<p>By the way, I used to use this pattern to create a homebrew version of nested prefabs and prefab variants. A Descriptor can contain as much data as we need, so it&#8217;s possible to reference a prefab and store a bunch of override data. Then when you instantiate the prefab, you immediately assign all the overridden data. As it turns out, this is precisely how nested prefabs work.<\/p>\n\n\n\n<p>We&#8217;ll start by defining a <code>SerializableScriptableObject<\/code> type, the base class for our Descriptors.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Serializable Scriptable Object<\/h2>\n\n\n\n<p>A <code>SerializableScriptableObject<\/code> is a Scriptable Object that automatically saves its GUID. Speaking of, GUID stands for Global Unique Identifier. It&#8217;s also sometimes called a UUID for Universal Unique Identifier. The Unity Editor maintains an Asset Database that assigns a GUID to every asset in the project and uses it to reference assets in scenes, prefabs, etc. Usually, the Asset Database and corresponding GUIDs are only available in the Editor, but we will store those ids to make them always available. Incidentally, you may notice that the Asset Database and GUID system sounds similar to what we&#8217;re building, and you&#8217;re right. We&#8217;re essentially re-implementing this functionality at runtime.<\/p>\n\n\n\n<p>Here&#8217;s the <code>SerializableScriptableObject<\/code> class.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using UnityEngine;\n#if UNITY_EDITOR\nusing UnityEditor;\n#endif\n\npublic class SerializableScriptableObject : ScriptableObject\n{\n    &#91;SerializeField] Guid _guid;\n    public Guid Guid =&gt; _guid;\n\n#if UNITY_EDITOR\n    void OnValidate()\n    {\n        var path = AssetDatabase.GetAssetPath(this);\n        _guid = new Guid(AssetDatabase.AssetPathToGUID(path));\n    }\n#endif\n}<\/code><\/pre>\n\n\n\n<p>If you&#8217;re not familiar, the Editor calls <code>OnValidate<\/code> whenever a value changes in the Inspector or when recompiles your scripts. So this is the perfect opportunity to save the GUID from the Asset Database. I&#8217;m sure you&#8217;re curious about the <code>Guid<\/code> type, so let&#8217;s do that next.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">GUID Type<\/h2>\n\n\n\n<p>When it comes to storing the GUID, there&#8217;s an easy way and a hard way. The easy way is to store a string that represents the GUID. That looks like this.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;System.Serializable]\npublic struct Guid\n{\n    public string guid;\n}<\/code><\/pre>\n\n\n\n<p>You could simplify it further by removing it altogether and just using a string field. Unfortunately, there are performance repercussions. Comparing two strings is relatively slow, and manipulating strings leads to memory allocations. That said, I&#8217;ve done it this way in production code, and it&#8217;s usually acceptable. If you want to do it the &#8220;right&#8221; way (just kidding, there is no&nbsp;<strong>right<\/strong>&nbsp;way, only the way that best suits your needs), then you need to store the GUID as four <code>uints<\/code>. That version looks like this.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using System;\nusing System.IO;\nusing UnityEngine;\n\n&#91;Serializable]\npublic struct Guid : IEquatable&lt;Guid>\n{\n    &#91;SerializeField, HideInInspector] uint m_Value0;\n    &#91;SerializeField, HideInInspector] uint m_Value1;\n    &#91;SerializeField, HideInInspector] uint m_Value2;\n    &#91;SerializeField, HideInInspector] uint m_Value3;\n\n    public uint Value0 => m_Value0;\n    public uint Value1 => m_Value1;\n    public uint Value2 => m_Value2;\n    public uint Value3 => m_Value3;\n\n    public Guid(uint val0, uint val1, uint val2, uint val3)\n    {\n        m_Value0 = val0;\n        m_Value1 = val1;\n        m_Value2 = val2;\n        m_Value3 = val3;\n    }\n\n    public Guid(string hexString)\n    {\n        m_Value0 = 0U;\n        m_Value1 = 0U;\n        m_Value2 = 0U;\n        m_Value3 = 0U;\n        TryParse(hexString, out this);\n    }\n\n    public string ToHexString()\n    {\n        return $\"{m_Value0:X8}{m_Value1:X8}{m_Value2:X8}{m_Value3:X8}\";\n    }\n\n    static void TryParse(string hexString, out Guid guid)\n    {\n        guid.m_Value0 = Convert.ToUInt32(hexString.Substring(0, 8), 16);\n        guid.m_Value1 = Convert.ToUInt32(hexString.Substring(8, 8), 16);\n        guid.m_Value2 = Convert.ToUInt32(hexString.Substring(16, 8), 16);\n        guid.m_Value3 = Convert.ToUInt32(hexString.Substring(24, 8), 16);\n    }\n   \n   public static bool operator ==(Guid x, Guid y) => x.m_Value0 == y.m_Value0 &amp;&amp; x.m_Value1 == y.m_Value1 &amp;&amp; x.m_Value2 == y.m_Value2 &amp;&amp; x.m_Value3 == y.m_Value3;\n   public static bool operator !=(Guid x, Guid y) => !(x == y);\n   public bool Equals(Guid other) => this == other;\n   public override bool Equals(object obj) => obj != null &amp;&amp; obj is Guid &amp;&amp; Equals((Guid) obj);\n   public override int GetHashCode() => (((int) m_Value0 * 397 ^ (int) m_Value1) * 397 ^ (int) m_Value2) * 397 ^ (int) m_Value3;\n}\n\n#region BinaryReader and BinaryWriter Extensions\npublic static class BinaryReaderExtensions\n{\n    public static Guid ReadGuid(this BinaryReader reader)\n    {\n        return new Guid(reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32());\n    }\n}\n\npublic static class BinaryWriterExtensions\n{\n    public static void Write(this BinaryWriter writer, Guid guid)\n    {\n        writer.Write(guid.Value0);\n        writer.Write(guid.Value1);\n        writer.Write(guid.Value2);\n        writer.Write(guid.Value3);\n    }\n}\n#endregion<\/code><\/pre>\n\n\n\n<p>With this, we can convert from a string representation to four <code>uints<\/code> and back. Converting back and forth is essential because the Asset Database API feeds us the id as a string. There are also extensions to BinaryReader and BinaryWriter to support serialization through those APIs. Using JSON serialization through <code>JsonUtility<\/code> works by default because the <code>uint<\/code> fields are serializable.<\/p>\n\n\n\n<p>We could stop there, but wouldn&#8217;t it be nice to prevent users from modifying the GUID in the Inspector? We&#8217;ll do that and display the id as the original hex string simultaneously using a custom <code>PropertyDrawer<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">GUID Property Drawer<\/h2>\n\n\n\n<p>Property drawers are a mechanism to control how a given type displays in the Inspector. We&#8217;ll use it to display the GUID as a label rather than a list of editable numbers. Create a new file, <code>GuidDrawer.cs<\/code>, inside any folder called Editor and add this code.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using UnityEditor;\nusing UnityEngine;\n\n&#91;CustomPropertyDrawer(typeof(Guid))]\npublic class GuidDrawer : PropertyDrawer\n{\n    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)\n    {\n        EditorGUI.BeginProperty(position, label, property);\n\n        var value0 = property.FindPropertyRelative(\"m_Value0\");\n        var value1 = property.FindPropertyRelative(\"m_Value1\");\n        var value2 = property.FindPropertyRelative(\"m_Value2\");\n        var value3 = property.FindPropertyRelative(\"m_Value3\");\n\n        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);\n        EditorGUI.SelectableLabel(position,\n            $\"{(uint) value0.intValue:X8} \n              {(uint) value1.intValue:X8} \n              {(uint) value2.intValue:X8} \n              {(uint) value3.intValue:X8}\");\n\n        EditorGUI.EndProperty();\n    }\n}<\/code><\/pre>\n\n\n\n<p>The attribute <code>CustomPropertyDrawer<\/code> specifies which property types to override. In our case, the type is <code>Guid<\/code>. Then, override <code>OnGUI<\/code>, and magically every time the Inspector encounters a <code>Guid<\/code> it&#8217;ll run this <code>OnGUI<\/code> method.<\/p>\n\n\n\n<p>The <code>SerializedProperty<\/code> is a generic class that wraps the associated property, our <code>Guid<\/code>. Working with Serialized Properties is a bit cumbersome, but that&#8217;s life. We usually use <code>FindPropertyRelative<\/code> to find each field by its name, but we can make it a bit safer through the power of <code>nameof<\/code>. With <code>nameof<\/code> we can store the names of fields to constants, which means if the names get refactored, our code won&#8217;t break (thanks for the tip Aiden). Let me show you what I mean. Add these fields to the <code>Guid<\/code> type.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public struct Guid : IEquatable&lt;Guid&gt;\n{\n    #if UNITY_EDITOR\n<strong>    public const string VALUE0_FIELDNAME = nameof(m_Value0);\n    public const string VALUE1_FIELDNAME = nameof(m_Value1);\n    public const string VALUE2_FIELDNAME = nameof(m_Value2);\n    public const string VALUE3_FIELDNAME = nameof(m_Value3);<\/strong>\n    #endif\n. \n.\n.<\/code><\/pre>\n\n\n\n<p>Here, we&#8217;re using <code>nameof<\/code> to store the name of the value fields in a string. Now modify the property drawer to use these strings instead.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)\n{\n    EditorGUI.BeginProperty(position, label, property);\n    var value0 = property.FindPropertyRelative(<strong>Guid.VALUE0_FIELDNAME<\/strong>);\n    var value1 = property.FindPropertyRelative(<strong>Guid.VALUE1_FIELDNAME<\/strong>);\n    var value2 = property.FindPropertyRelative(<strong>Guid.VALUE2_FIELDNAME<\/strong>);\n    var value3 = property.FindPropertyRelative(<strong>Guid.VALUE3_FIELDNAME<\/strong>);\n.\n.\n.<\/code><\/pre>\n\n\n\n<p>Now that we&#8217;re ready to serialize some Scriptable Objects let&#8217;s build our Asset Registry.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Registry Type<\/h2>\n\n\n\n<p>Fortunately, the Registry type is relatively simple. It&#8217;s a Scriptable Object with a List of <code>SerializableScriptableObjects<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using System.Collections.Generic;\nusing UnityEngine;\n\npublic abstract class Registry&lt;T&gt; : ScriptableObject where T : SerializableScriptableObject\n{\n    &#91;SerializeField] protected List&lt;T&gt; _descriptors = new List&lt;T&gt;();\n\n    public T FindByGuid(Guid guid)\n    {\n        foreach (var desc in _descriptors)\n        {\n            if (desc.Guid == guid)\n            {\n                return desc;\n            }\n        }\n\n        return null;\n    }\n}<\/code><\/pre>\n\n\n\n<p>There&#8217;s not much else to say here, so let&#8217;s put it all into practice.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Putting it together with Entities<\/h2>\n\n\n\n<p>Everything beyond this point is project-specific, but I&#8217;ll demonstrate how everything connects with an example. Let&#8217;s say we have an <code>Entity<\/code> component attached to all the dynamic objects in our game. To save and load entities, we need an <code>EntityDescriptor<\/code> and an <code>EntityRegistry<\/code>. The Descriptor has an id and references a prefab. The Registry holds a list of Descriptors. Here&#8217;s the associated code.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/EntityRegistry.cs\nusing UnityEngine;\n\n&#91;CreateAssetMenu]\npublic class EntityRegistry : Registry&lt;EntityDescriptor&gt;\n{\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/EntityDescriptor.cs\nusing UnityEngine;\n\n&#91;CreateAssetMenu]\npublic class EntityDescriptor : SerializableScriptableObject\n{\n    public Entity EntityPrefab;\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/Entity.cs\nusing UnityEngine;\n\npublic class Entity : MonoBehaviour\n{\n    public EntityDescriptor Descriptor;\n}<\/code><\/pre>\n\n\n\n<p>As you can see, there&#8217;s very little code because the base classes contain all the complexity. Now you can create a Registry and Descriptors for each of your prefabs from the Assets &gt; Create menu. By the way, adding the <code>CreateAssetMenu<\/code> attribute to a ScriptableObject type automatically adds a menu item to create the stated object in the Create menu.<\/p>\n\n\n\n<p>I used a simplified version of the <code>SaveManager<\/code> from the previous article to create a little test scene, which you can find in the GitHub repository linked at the end. This scene contains a bunch of entities spread around. On save, I grab all the entities in the scene and save their <code>Guid<\/code> and position to a binary file on disk. Then on load, I read all the ids and positions from the file, look up each prefab in the Registry and instantiate the associated prefab at its saved position.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Exciting Opportunities<\/h2>\n\n\n\n<p>So before I wrap up, I wanted to mention a few uses for this pattern. The most obvious, of course, is to save the state of the dynamic objects in your scene, as I showed in the example. In the past, I&#8217;ve also used this pattern to create a level editor tool. At their core, these two things are essentially the same. A level editor is a tool to spawn prefabs, manipulate them, and store them to disk.<\/p>\n\n\n\n<p>Another less obvious use is to store ability loadouts to use in an RPG, for example. Scriptable objects can contain code as well as data. Let&#8217;s say we define a <code>SerializableScriptableObject<\/code> type to represent an ability like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public abstract class Ability : SerializableScriptableObject\n{\n    public abstract void Execute();\n}<\/code><\/pre>\n\n\n\n<p>Now we can subclass this to create new abilities that contain all the necessary code and data. As a result, we can save a character&#8217;s ability loadout as a list of ids that link back to their corresponding Scriptable Object.<\/p>\n\n\n\n<p>Ok, I&#8217;m almost done, but here&#8217;s one last tip before I go\u2014this one is for those of you who are using the Addressables system. It&#8217;s not apparent, but you can load addressable assets by their GUID as well as their address. It&#8217;s so unclear that I&#8217;m worried this functionality may disappear one day, but it works for now. What this means is if you&#8217;re using Addressables, you can forego the <code>Registry<\/code> entirely and use <code>Addressables.LoadAssetAsync()<\/code> to load your assets. What&#8217;s more, since you&#8217;re loading by GUID instead of a string address, you can freely reorganize your project&#8217;s folder structure without your asset addresses going stale.<\/p>\n\n\n\n<p>Ok, I&#8217;ve prattled on long enough; I&#8217;m outta here.<\/p>\n\n\n\n<p><strong>Explore the example project <\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/github.com\/bzgeb\/SerializableScriptableObject\"><strong>here on Github<\/strong><\/a><strong>. If you like my work,&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/bronsonzgeb.com\/index.php\/join-my-mailing-list\/\"><strong>join my mailing list<\/strong><\/a><strong>&nbsp;to be notified when I write a new post.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article describes a pattern to serialize prefabs or any complex data. Doing so allows us to store data to disk or send it over a network to either a game server or another game client. This article pairs well with the previous article on saving and loading data, so I recommend you read that [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":516,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,1],"tags":[30,8,49,29,5],"class_list":["post-514","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-architecture","category-unity-programming","tag-architecture","tag-game-development","tag-persistent-data","tag-scriptable-objects","tag-unity"],"_links":{"self":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/514","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/comments?post=514"}],"version-history":[{"count":18,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/514\/revisions"}],"predecessor-version":[{"id":638,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/514\/revisions\/638"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media\/516"}],"wp:attachment":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media?parent=514"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/categories?post=514"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/tags?post=514"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}