Search Unity

Can I write my own a ScriptableObject into ProjectSettings folder?

Discussion in 'Editor & General Support' started by watsonsong, Jul 5, 2019.

  1. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    I see the new SettingProviderAttribute API in unity. And in the document it save the project wide data into a ScriptableObject, and write it to Asset/Editor/CustomSettings.asset.
    The *.asset files in the ProjectSettings folder seems also the ScriptableObject. I hope there could be a way to write my project wide setting properties into the ProjectSettings folder but not some hard code place like Asset/Editor/XXX.

    I find some one met the same problem before the SettingProviderAttribute introduced many years ago:
    https://answers.unity.com/questions/1283645/read-write-scriptableobject-in-projectsettings-fol.html

    But I think may be a feature request because it really very useful to make the project lint and clean.
     
  2. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    Stumbled upon the same requirement. I need to store my custom settings object in a clean way. ProjectSettings folder seems like a perfect place for it, but it seems I can't save/load ScriptableObjects there. Ideally I want to serialize file directly in YAML and save it. I don't really need ScriptableObject and/or any inspector functionality. This is pure data generated by my code.

    Maybe there is a way to use built-in serializer with normal C# classes?
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    You can use the built-in serializer with normal C# classes by going through JsonUtility. That generates Json instead of YAML, but otherwise it uses the same serialization backend (so you could eg. save a reference to an asset, and it'd be a reference rather than a deep copy).

    An alternative is to use the asset database to create the asset, and then copy the file to ProjectSettings through the normal File API. The JsonUtility path is probably a lot easier.
     
    aurelio-provedo-bnm likes this.
  4. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    Well, I finally use the official package: SettingsManager
     
    CodeSmile and fumobox like this.
  5. hyagogow

    hyagogow

    Joined:
    Apr 25, 2014
    Posts:
    26
    I recently had this requirement for a Scene Loader I was developing.
    The idea is to save some data globally using the Project Settings windows and use them later inside my components. It's important that the data is available globally in the editor and any other builds.

    So, I had to create:
    1. A ScriptableObject to hold my data information. The Project Settings windows will display this data.
    2. A Provider inheriting from AssetSettingsProvider. This class will also handle the ScriptableObject asset creation.
    3. A build class implementing the IPreprocessBuildWithReport interface where I can add my ScriptableObject asset to the Preloaded Assets list when the build process starts.
    Below are the main parts from each class:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public sealed class SceneLoaderSettings : ScriptableObject
    4. {
    5.     // data here...
    6.  
    7.  
    8.     public const string CONFIG_NAME = "com.actioncode.sceneloader.settings";
    9.  
    10.     private static SceneLoaderSettings instance;
    11.  
    12.     private void OnEnable()
    13.     {
    14.         if (instance == null) instance = this;
    15.     }
    16.  
    17.     public static SceneLoaderSettings Load()
    18.     {
    19.         if (instance) return instance;
    20. #if UNITY_EDITOR
    21.         UnityEditor.EditorBuildSettings.TryGetConfigObject(CONFIG_NAME, out instance);
    22. #else
    23.         // Loads from the memory.
    24.         instance = FindObjectOfType<SceneLoaderSettings>();
    25. #endif
    26.         return instance;
    27.     }
    28. }
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.UIElements;
    4.  
    5. public sealed class SceneLoaderSettingsProvider : AssetSettingsProvider
    6. {
    7.     private string searchContext;
    8.     private VisualElement rootElement;
    9.  
    10.     /// <summary>
    11.     /// The current SceneLoaderSettings used for this project.
    12.     /// <para>
    13.     /// This instance will be available in editor while playing and in any other player builds.
    14.     /// To do so, this CurrentSettings needs to be an asset to be added to the Preloaded Assets
    15.     /// list during the build process.
    16.     /// </para>
    17.     /// <para>See how this is done on <see cref="SceneLoaderBuildPlayer"/>.</para>
    18.     /// </summary>
    19.     public static SceneLoaderSettings CurrentSettings
    20.     {
    21.         get
    22.         {
    23.             EditorBuildSettings.TryGetConfigObject(SceneLoaderSettings.CONFIG_NAME, out SceneLoaderSettings settings);
    24.             return settings;
    25.         }
    26.         set
    27.         {
    28.             var remove = value == null;
    29.             if (remove)
    30.             {
    31.                 EditorBuildSettings.RemoveConfigObject(SceneLoaderSettings.CONFIG_NAME);
    32.             }
    33.             else
    34.             {
    35.                 EditorBuildSettings.AddConfigObject(SceneLoaderSettings.CONFIG_NAME, value, overwrite: true);
    36.             }
    37.         }
    38.     }
    39.  
    40.     public SceneLoaderSettingsProvider()
    41.         : base("Project/Scene Loader", () => CurrentSettings)
    42.     {
    43.         CurrentSettings = FindSceneLoaderSettings();
    44.         keywords = GetSearchKeywordsFromGUIContentProperties<SceneLoaderSettings>();
    45.     }
    46.  
    47.     public override void OnActivate(string searchContext, VisualElement rootElement)
    48.     {
    49.         this.rootElement = rootElement;
    50.         this.searchContext = searchContext;
    51.         base.OnActivate(searchContext, rootElement);
    52.     }
    53.  
    54.     public override void OnGUI(string searchContext)
    55.     {
    56.         DrawCurrentSettingsGUI();
    57.         EditorGUILayout.Space();
    58.  
    59.         var invalidSettings = CurrentSettings == null;
    60.         if (invalidSettings) DisplaySettingsCreationGUI();
    61.         else base.OnGUI(searchContext);
    62.     }
    63.  
    64.     private void DrawCurrentSettingsGUI()
    65.     {
    66.         EditorGUI.BeginChangeCheck();
    67.  
    68.         EditorGUI.indentLevel++;
    69.         var settings = EditorGUILayout.ObjectField("Current Settings", CurrentSettings,
    70.             typeof(SceneLoaderSettings), allowSceneObjects: false) as SceneLoaderSettings;
    71.         if (settings) DrawCurrentSettingsMessage();
    72.         EditorGUI.indentLevel--;
    73.  
    74.         var newSettings = EditorGUI.EndChangeCheck();
    75.         if (newSettings)
    76.         {
    77.             CurrentSettings = settings;
    78.             RefreshEditor();
    79.         }
    80.     }
    81.  
    82.     private void RefreshEditor() => base.OnActivate(searchContext, rootElement);
    83.  
    84.     private void DisplaySettingsCreationGUI()
    85.     {
    86.         const string message = "You have no Scene Loader Settings. Would you like to create one?";
    87.         EditorGUILayout.HelpBox(message, MessageType.Info, wide: true);
    88.         var openCreationdialog = GUILayout.Button("Create");
    89.         if (openCreationdialog)
    90.         {
    91.             CurrentSettings = SaveSceneLoaderAsset();
    92.         }
    93.     }
    94.  
    95.     private void DrawCurrentSettingsMessage()
    96.     {
    97.         const string message = "This is the current Scene Loader Settings and " +
    98.             "it will be automatically included into any builds.";
    99.         EditorGUILayout.HelpBox(message, MessageType.Info, wide: true);
    100.     }
    101.  
    102.     private static SceneLoaderSettings FindSceneLoaderSettings()
    103.     {
    104.         var filter = $"t:{typeof(SceneLoaderSettings).Name}";
    105.         var guids = AssetDatabase.FindAssets(filter);
    106.         var hasGuids = guids.Length > 0;
    107.         var path = hasGuids ? AssetDatabase.GUIDToAssetPath(guids[0]) : string.Empty;
    108.  
    109.         return AssetDatabase.LoadAssetAtPath<SceneLoaderSettings>(path);
    110.     }
    111.  
    112.     private static SceneLoaderSettings SaveSceneLoaderAsset()
    113.     {
    114.         var path = EditorUtility.SaveFilePanelInProject(
    115.             title: "Save Scene Loader Settings", defaultName: "DefaultSceneLoaderSettings", extension: "asset",
    116.             message: "Please enter a filename to save the projects Scene Loader settings.");
    117.         var invalidPath = string.IsNullOrEmpty(path);
    118.         if (invalidPath) return null;
    119.  
    120.         var settings = ScriptableObject.CreateInstance<SceneLoaderSettings>();
    121.         AssetDatabase.CreateAsset(settings, path);
    122.         AssetDatabase.SaveAssets();
    123.  
    124.         Selection.activeObject = settings;
    125.         return settings;
    126.     }
    127.  
    128.     [SettingsProvider]
    129.     private static SettingsProvider CreateProjectSettingsMenu() => new SceneLoaderSettingsProvider();
    130. }
    131.  
    Code (CSharp):
    1. using System.Linq;
    2. using UnityEditor;
    3. using UnityEditor.Build;
    4. using UnityEditor.Build.Reporting;
    5.  
    6. public sealed class SceneLoaderBuildPlayer : IPreprocessBuildWithReport
    7. {
    8.     public int callbackOrder => 0;
    9.  
    10.     public void OnPreprocessBuild(BuildReport report)
    11.     {
    12.         var settings = SceneLoaderSettingsProvider.CurrentSettings;
    13.         var settingsType = settings.GetType();
    14.         var preloadedAssets = PlayerSettings.GetPreloadedAssets().ToList();
    15.         // Removes all references from SceneLoaderSettings.
    16.         preloadedAssets.RemoveAll(settings => settings.GetType() == settingsType);
    17.         // Adds the Current SceneLoaderSettings.
    18.         preloadedAssets.Add(settings);
    19.  
    20.         PlayerSettings.SetPreloadedAssets(preloadedAssets.ToArray());
    21.     }
    22. }
    23.