{"id":1726,"date":"2017-02-19T06:00:46","date_gmt":"2017-02-18T22:00:46","guid":{"rendered":"http:\/\/www.clonefactor.com\/wordpress\/?p=1726"},"modified":"2017-02-19T06:26:34","modified_gmt":"2017-02-18T22:26:34","slug":"singletonscriptableobject-can-it-be-done","status":"publish","type":"post","link":"https:\/\/www.clonefactor.com\/wordpress\/program\/unity3d\/1726\/","title":{"rendered":"SingletonScriptableObject, can it be done ?"},"content":{"rendered":"<p>I&#8217;m current studying the &#8220;SingletonScriptableObject&#8221;, with some luck, finally make it work with editor &amp; release.<\/p>\n<p>here is the code that I&#8217;m using in my library, it may not prefect, but seem working for me.<\/p>\n<p>I start from this article :\u00a0<a href=\"http:\/\/baraujo.net\/unity3d-making-singletons-from-scriptableobjects-automatically\/\">http:\/\/baraujo.net\/unity3d-making-singletons-from-scriptableobjects-automatically\/<\/a><\/p>\n<p>it&#8217;s a\u00a0talk\u00a0for using ScriptableObjects. with a nice example.<\/p>\n<p><iframe loading=\"lazy\" title=\"Unite Europe 2016 - Overthrowing the MonoBehaviour tyranny in a glorious ScriptableObject revolution\" width=\"1260\" height=\"709\" src=\"https:\/\/www.youtube.com\/embed\/VBA1QCoEAX4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><\/p>\n<pre class=\"brush:csharp\">using System.Linq;  \r\nusing UnityEngine;\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ Abstract class for making reload-proof singletons out of ScriptableObjects\r\n\/\/\/ Returns the asset created on the editor, or null if there is none\r\n\/\/\/ Based on https:\/\/www.youtube.com\/watch?v=VBA1QCoEAX4\r\n\/\/\/ &lt;\/summary&gt;\r\n\/\/\/ &lt;typeparam name=\"T\"&gt;Singleton type&lt;\/typeparam&gt;\r\n\r\npublic abstract class SingletonScriptableObject&lt;T&gt; : ScriptableObject where T : ScriptableObject {  \r\n    static T _instance = null;\r\n    public static T Instance\r\n    {\r\n        get\r\n        {\r\n            if (!_instance)\r\n                _instance = Resources.FindObjectsOfTypeAll&lt;T&gt;().FirstOrDefault();\r\n            return _instance;\r\n        }\r\n    }\r\n}<\/pre>\n<p>However I notices random error will throw, after 2 week,\u00a0the root cause are the ScriptableObject haven&#8217;t *ACTIVE* yet.<br \/>\nthe Resources.FindObjectsOfTypeAll&lt;T&gt; just not return the ScriptableObject that we needed.<br \/>\nfinally I locate the problem usually happen when I first start up the unity project.<br \/>\nhere is the workflow to make it happen.<\/p>\n<ol>\n<li>Write a script that will use SingletonScriptableObject in &#8220;UNITY_EDITOR&#8221; mode.<br \/>\nanything up to you,\u00a0[ExecuteInEditMode] or what ever you want.<br \/>\n&gt;&gt;\u00a0[ExecuteInEditMode]<br \/>\n&gt;&gt; public class Test<br \/>\n&gt;&gt; Update() { YOUR_SingletonScriptableObject.instance.foo(); } \/\/ something like this.<\/li>\n<li>close Unity3D, yeah. close it.<\/li>\n<li>Start Unity3D with project.<\/li>\n<li>put your Test.cs script on gameobject<\/li>\n<li>boom, null reference<\/li>\n<\/ol>\n<p>however, if you do this before step (4), like click\/select on the &#8220;YOUR_SingletonScriptableObject&#8221; in the project, the rest of the time will not trigger\u00a0the error.<br \/>\nbecause\u00a0Resources.FindObjectsOfTypeAll&lt;T&gt; now can return the correct instance of your scriptableObject.<br \/>\ntherefore we need to implement another part of the code to solve this issue.<br \/>\nmy idea is, put that into save record. playerPref&#8230;etc<\/p>\n<p>follow code are directly copy from my library,<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>SingletonScriptableObject.cs<\/p>\n<pre class=\"brush:csharp\">using System.IO;\r\nusing System.Linq;\r\nusing UnityEngine;\r\nusing Kit.IO;\r\n#if UNITY_EDITOR\r\nusing UnityEditor;\r\n#endif\r\n\r\nnamespace Kit\r\n{\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ For base singleton class for ScriptableObject\r\n\t\/\/\/ &lt;see cref=\"http:\/\/baraujo.net\/unity3d-making-singletons-from-scriptableobjects-automatically\/\"\/&gt; &lt;\/summary&gt;\r\n\t\/\/\/ &lt;typeparam name=\"T\"&gt;&lt;\/typeparam&gt;\r\n\tpublic abstract class SingletonScriptableObject&lt;T&gt; : ScriptableObject\r\n\t\twhere T : ScriptableObject\r\n\t{\r\n\t\tprivate class SingletonAssetPath : Record&lt;SingletonAssetPath&gt;\r\n\t\t{\r\n\t\t\tpublic SingletonAssetPath() : base(\"SingletonAssetPath\", FileSource.Application, \"json\") { }\r\n\t\t}\r\n\r\n\t\tprivate static T m_Instance = null;\r\n\t\tpublic static T Instance\r\n\t\t{\r\n\t\t\tget\r\n\t\t\t{\r\n\t\t\t\t\/\/ fast way to get instance if that object was *ACTIVE* before\r\n\t\t\t\t\/\/ fail case : the very first time load the project, and you haven't touch\/select that scriptable object\r\n\t\t\t\tif (m_Instance == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tm_Instance = Resources.FindObjectsOfTypeAll&lt;T&gt;().FirstOrDefault();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t\/\/ it never loaded before, let's search it in record.\r\n\t\t\t\t\/\/ fail case : change the name of asset after *ACTIVE*, path in record will not match.\r\n\t\t\t\tif (m_Instance == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tstring path = SingletonAssetPath.GetString(typeof(T).Name, string.Empty);\r\n\t\t\t\t\tif (!string.IsNullOrEmpty(path))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstring fileName = Path.GetFileNameWithoutExtension(path);\r\n\t\t\t\t\t\tm_Instance = Resources.Load&lt;T&gt;(fileName);\r\n\t\t\t\t\t\tif (m_Instance == null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tDebug.LogError(typeof(T).Name + \" can not be found in path: \" + path);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tDebug.Log(\"SingletonScriptableObject&lt;\" + typeof(T).Name + \"&gt; Loaded\");\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n#if UNITY_EDITOR\r\n\t\t\t\t\/\/ Update the asset path what even it was, to ensure the record are correct.\r\n\t\t\t\t\/\/ prevent fail case change name after *ACTIVE*.\r\n\t\t\t\tUpdateAssetPathRecord();\r\n\r\n\r\n\t\t\t\t\/\/ we don't have that object in project, and we are under developing mode.\r\n\t\t\t\t\/\/ ask developer to create it.\r\n\t\t\t\tif (m_Instance == null &amp;&amp; !EditorApplication.isPlaying)\r\n\t\t\t\t{\r\n\t\t\t\t\tbool isCreate = EditorUtility.DisplayDialog(\r\n\t\t\t\t\t\t\"SingletonScriptableObject&lt;\" + typeof(T).Name + \"&gt; not found\",\r\n\t\t\t\t\t\t\"Instance not found, what do you want to do ?\",\r\n\t\t\t\t\t\t\"Create asset\", \"Search it\");\r\n\r\n\t\t\t\t\tstring path = string.Empty;\r\n\t\t\t\t\tif (isCreate)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpath = ScriptableObjectUtility.CreateAsset&lt;T&gt;(\r\n\t\t\t\t\t\t\ttitle: \"Create Singleton ScriptableObject &lt;\" + typeof(T).Name + \"&gt;\",\r\n\t\t\t\t\t\t\tdefaultName: typeof(T).Name,\r\n\t\t\t\t\t\t\textension: \"asset\"\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpath = EditorUtility.OpenFilePanelWithFilters(\"Search ScriptableObject &lt;\" + typeof(T).Name + \"&gt;\",\r\n\t\t\t\t\t\t\t\"Assets\", new string[] { \"*\", \"asset\" });\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (!string.IsNullOrEmpty(path))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstring fileName = Path.GetFileNameWithoutExtension(path);\r\n\t\t\t\t\t\tm_Instance = Resources.Load&lt;T&gt;(fileName);\r\n\t\t\t\t\t\tUpdateAssetPathRecord();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n#endif\r\n\t\t\t\treturn m_Instance;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t[System.Diagnostics.Conditional(\"UNITY_EDITOR\")]\r\n\t\tprivate static void UpdateAssetPathRecord()\r\n\t\t{\r\n#if UNITY_EDITOR\r\n\t\t\tif (m_Instance == null)\r\n\t\t\t{\r\n\t\t\t\tSingletonAssetPath.DeleteString(typeof(T).Name);\r\n\t\t\t\tSingletonAssetPath.Save();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstring path = AssetDatabase.GetAssetPath(m_Instance);\r\n\t\t\t\tif (path != SingletonAssetPath.GetString(typeof(T).Name, string.Empty))\r\n\t\t\t\t{\r\n\t\t\t\t\tDebug.Log(\"Update &lt;\" + typeof(T).Name + \"&gt; singleton asset path To :\" + path);\r\n\t\t\t\t\tSingletonAssetPath.SetString(typeof(T).Name, path);\r\n\t\t\t\t\tSingletonAssetPath.Save();\r\n\t\t\t\t}\r\n\t\t\t\tif (!path.ToLower().Contains(\"resources\"))\r\n\t\t\t\t{\r\n\t\t\t\t\tDebug.LogError(typeof(T).Name + \" must place into \\\"Resources\\\" folder.\");\r\n\t\t\t\t}\r\n\t\t\t}\r\n#endif\r\n\t\t}\r\n\t}\r\n}\r\n<\/pre>\n<p>Record&lt;T&gt; are the library to handle the file I\/O, you guys can replace by PlayerPref or any other methods that can save\/load the string.<br \/>\nand\u00a0ScriptableObjectUtility.cs you can locate in :\u00a0<a href=\"http:\/\/wiki.unity3d.com\/index.php?title=CreateScriptableObjectAsset\">http:\/\/wiki.unity3d.com\/index.php?title=CreateScriptableObjectAsset<\/a><br \/>\nthose are very useful tools for create scriptableObject. (don&#8217;t recreate wheel)<\/p>\n<p>so\u00a0enjoy, and if there is any problem feel free to discuss here.<\/p>\n<p>&nbsp;<\/p>\n<p>and here is my test case, &#8220;SpeakerData&#8221;, are using the ScriptableObject to define the character id, name, texture.<\/p>\n<p><iframe loading=\"lazy\" title=\"UGUI Panel Manager, Log#3\" width=\"1260\" height=\"945\" src=\"https:\/\/www.youtube.com\/embed\/Nk206V7iaaQ?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m current studying the &#8220;SingletonScr &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[16,20,67,43],"class_list":["post-1726","post","type-post","status-publish","format-standard","hentry","category-unity3d","tag-c-2","tag-editor","tag-ugui","tag-unity3d-2"],"_links":{"self":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1726","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/comments?post=1726"}],"version-history":[{"count":0,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1726\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1726"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1726"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1726"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}