Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

SettingsProvider and AssetSettingsProvider API

Discussion in '2018.3 Beta' started by bdovaz, Oct 7, 2018.

  1. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,030
    I'm playing with it and learning from github UnityCsReference.

    My use case is that I want from different assemblies build a unique SettingsProvider, is it possible?

    The only case I thought that Unity does it like this was PlayerSettings (each platform has it's own settings) but I see that it uses "partial" class approach that it's not possible when you are on a different assembly.
     
  2. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,030
    Related to this, is there a way to create a *.asset file outside "Assets" folder? In Unity examples they create all settings inside "ProjectSettings" folder but if I use "AssetDatabase.CreateAsset" API it throws an error.
     
  3. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,157
    Sorry you can not create assets outside of the Assets folder, all the ProjectSettings assets have been set up especially in C++. Its not a generic thing users can do. You could potentially use a package to store your settings although I would not recommend it.
    We have actually started to move away from storing assets in the ProjectsFolder and now store them with assets. For example, the addressable package does this.

    I do not believe it is possible to combine multiple partial classes from different assemblies into one settings class.
    Perhaps you could

    An alternative approach could be to have an abstract settings class that inherits from ScriptableObject.
    Then have the multiple assemblies create their own settings class implementations and have your master settings class find all classes that implement the abstract class, instantiate them and keep them inside of a container scriptable object asset.
     
  4. jonathans42

    jonathans42

    Unity Technologies

    Joined:
    Jan 25, 2018
    Posts:
    514
    The SettingsProvider are not bound to an *.asset file. They are flexible enough that you can provide your own data model. That said, if you want to save data outside of the project Assets/ folder itself you can do your own asset serialization. Here's an example that uses JSON file to save data:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Globalization;
    5. using System.IO;
    6. using System.Linq;
    7. using UnityEngine;
    8. using Random = System.Random;
    9.  
    10. namespace UnityEditor
    11. {
    12.     [Serializable]
    13.     public class SettingsPropertyData
    14.     {
    15.         public string name;
    16.         public string label;
    17.         public string type;
    18.         public string value;
    19.         public float min;
    20.         public float max;
    21.     }
    22.  
    23.     [Serializable]
    24.     public class SettingsData
    25.     {
    26.         public string name;
    27.         public string label;
    28.         public string scope;
    29.         public SettingsPropertyData[] settings;
    30.     }
    31.  
    32.     public class GenericSettingsProvider : SettingsProvider
    33.     {
    34.         private string m_LocalFilePath;
    35.         private SettingsPropertyData[] m_Properties;
    36.  
    37.         public GenericSettingsProvider(string path, string label, string scope, SettingsPropertyData[] properties, string jsonFilePath = null)
    38.             : base(path)
    39.         {
    40.             this.label = label;
    41.             if (scope == null)
    42.                 scopes = SettingsScopes.Any;
    43.             else if (scope.ToLower() == "project")
    44.                 scopes = SettingsScopes.Project;
    45.             else if (scope.ToLower() == "user")
    46.                 scopes = SettingsScopes.User;
    47.             m_Properties = properties;
    48.             m_LocalFilePath = jsonFilePath;
    49.         }
    50.  
    51.         public override void OnGUI(string searchContext)
    52.         {
    53.             using (new SettingsWindow.GUIScope())
    54.             {
    55.                 foreach (var property in m_Properties)
    56.                 {
    57.                     if (searchContext.Length > 0 && !SearchUtils.MatchSearchGroups(searchContext, property.label))
    58.                         continue;
    59.  
    60.                     using (new EditorGUILayout.HorizontalScope())
    61.                         DrawPropertyControl(property);
    62.                 }
    63.  
    64.                 GUILayout.Space(10.0f);
    65.  
    66.                 using (new EditorGUILayout.HorizontalScope())
    67.                 {
    68.                     GUILayout.FlexibleSpace();
    69.                     if (GUILayout.Button("Show in Project"))
    70.                     {
    71.                         var asset = AssetDatabase.LoadMainAssetAtPath(m_LocalFilePath);
    72.                         if (asset)
    73.                             EditorGUIUtility.PingObject(asset);
    74.                     }
    75.                 }
    76.             }
    77.         }
    78.  
    79.         public override bool HasSearchInterest(string searchContext)
    80.         {
    81.             if (SettingsTestsUtils.HasSearchInterest(m_Properties, searchContext))
    82.             {
    83.                 return true;
    84.             }
    85.  
    86.             return base.HasSearchInterest(searchContext);
    87.         }
    88.  
    89.         private string GetPropertyKey(SettingsPropertyData property)
    90.         {
    91.             return SettingsTestsUtils.GetPropertyKey(settingsPath, property);
    92.         }
    93.  
    94.         private void DrawPropertyControl(SettingsPropertyData property)
    95.         {
    96.             switch (property.type.ToLower())
    97.             {
    98.                 case "string": DrawStringPropertyControl(property); break;
    99.                 case "number": DrawNumberPropertyControl(property); break;
    100.                 case "bool": DrawBooleanPropertyControl(property); break;
    101.                 case "color": DrawColorPropertyControl(property); break;
    102.                 default: throw new NotSupportedException("Property type " + property.type + " not supported");
    103.             }
    104.         }
    105.  
    106.         private void DrawColorPropertyControl(SettingsPropertyData property)
    107.         {
    108.             EditorGUI.BeginChangeCheck();
    109.             var key = GetPropertyKey(property);
    110.  
    111.             var colorComponents = SettingsTestsUtils.GetFloats(property);
    112.             Color colorValue = new Color();
    113.             if (colorComponents != null && colorComponents.Length == 4)
    114.                 colorValue = new Color(colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3]);
    115.  
    116.             if (IsUserSettings())
    117.             {
    118.                 colorValue.r = EditorPrefs.GetFloat(key + ".r", colorValue.r);
    119.                 colorValue.g = EditorPrefs.GetFloat(key + ".g", colorValue.g);
    120.                 colorValue.b = EditorPrefs.GetFloat(key + ".b", colorValue.b);
    121.                 colorValue.a = EditorPrefs.GetFloat(key + ".a", colorValue.a);
    122.             }
    123.  
    124.             Color newColor = EditorGUILayout.ColorField(property.label, colorValue);
    125.             if (EditorGUI.EndChangeCheck())
    126.             {
    127.                 property.value = Json.Serialize(new[] {newColor.r, newColor.g, newColor.b, newColor.a});
    128.  
    129.                 if (IsUserSettings())
    130.                 {
    131.                     EditorPrefs.SetFloat(key + ".r", newColor.r);
    132.                     EditorPrefs.SetFloat(key + ".g", newColor.g);
    133.                     EditorPrefs.SetFloat(key + ".b", newColor.b);
    134.                     EditorPrefs.SetFloat(key + ".a", newColor.a);
    135.                 }
    136.                 else
    137.                     PersistSettings();
    138.             }
    139.         }
    140.  
    141.         private void DrawBooleanPropertyControl(SettingsPropertyData property)
    142.         {
    143.             EditorGUI.BeginChangeCheck();
    144.             var key = GetPropertyKey(property);
    145.             bool boolValue;
    146.             if (property.value == null || !Boolean.TryParse(property.value, out boolValue))
    147.                 boolValue = false;
    148.             var newValue = EditorGUILayout.Toggle(property.label, IsUserSettings() ? EditorPrefs.GetBool(key, boolValue) : boolValue);
    149.             if (EditorGUI.EndChangeCheck())
    150.             {
    151.                 property.value = Convert.ToString(newValue, CultureInfo.InvariantCulture);
    152.  
    153.                 if (IsUserSettings())
    154.                     EditorPrefs.SetBool(key, newValue);
    155.                 else
    156.                     PersistSettings();
    157.             }
    158.         }
    159.  
    160.         private void DrawNumberPropertyControl(SettingsPropertyData property)
    161.         {
    162.             EditorGUI.BeginChangeCheck();
    163.             var key = GetPropertyKey(property);
    164.             var defaultValue = property.value != null ? Convert.ToSingle(property.value) : 0;
    165.             var floatValue = IsUserSettings() ? EditorPrefs.GetFloat(key, defaultValue) : defaultValue;
    166.             float newValue = floatValue;
    167.  
    168.             if (property.min < property.max)
    169.                 newValue = EditorGUILayout.Slider(property.label, floatValue, property.min, property.max);
    170.             else
    171.                 newValue = EditorGUILayout.FloatField(property.label, IsUserSettings() ? EditorPrefs.GetFloat(key, floatValue) : floatValue);
    172.             if (EditorGUI.EndChangeCheck())
    173.             {
    174.                 property.value = Convert.ToString(newValue, CultureInfo.InvariantCulture);
    175.  
    176.                 if (IsUserSettings())
    177.                     EditorPrefs.SetFloat(key, newValue);
    178.                 else
    179.                     PersistSettings();
    180.             }
    181.         }
    182.  
    183.         private void DrawStringPropertyControl(SettingsPropertyData property)
    184.         {
    185.             EditorGUI.BeginChangeCheck();
    186.             var uniquePropertyKey = GetPropertyKey(property);
    187.             var newValue = EditorGUILayout.TextField(property.label, IsUserSettings() ? EditorPrefs.GetString(uniquePropertyKey, property.value) : property.value);
    188.             if (EditorGUI.EndChangeCheck())
    189.             {
    190.                 property.value = newValue;
    191.  
    192.                 if (IsUserSettings())
    193.                     EditorPrefs.SetString(uniquePropertyKey, newValue);
    194.                 else
    195.                     PersistSettings();
    196.             }
    197.         }
    198.  
    199.         private void PersistSettings()
    200.         {
    201.             if (String.IsNullOrEmpty(m_LocalFilePath))
    202.                 throw new FileNotFoundException("Project settings data cannot be saved.");
    203.             var dataAsJson = File.ReadAllText(m_LocalFilePath);
    204.             var data = JsonUtility.FromJson<SettingsData>(dataAsJson);
    205.             data.settings = m_Properties;
    206.             File.WriteAllText(m_LocalFilePath, JsonUtility.ToJson(data, true));
    207.         }
    208.  
    209.         public static GenericSettingsProvider LoadFromFile(string path)
    210.         {
    211.             var dataAsJson = File.ReadAllText(path);
    212.             var data = JsonUtility.FromJson<SettingsData>(dataAsJson);
    213.             return new GenericSettingsProvider(data.name, data.label, data.scope, data.settings, path);
    214.         }
    215.  
    216.         private bool IsUserSettings()
    217.         {
    218.             return (scopes & SettingsScopes.User) != 0;
    219.         }
    220.  
    221.         [SettingsProviderGroup]
    222.         internal static SettingsProvider[] FetchGenericSettingsProviderList()
    223.         {
    224.             var genericSettingsFilePaths = AssetDatabase.GetAllAssetPaths().Where(path => path.EndsWith(".settings")).ToArray();
    225.             var genericSettingsProviders = new List<SettingsProvider>(genericSettingsFilePaths.Length);
    226.  
    227.             foreach (var genericSettingsFilePath in genericSettingsFilePaths)
    228.             {
    229.                 if (!File.Exists(genericSettingsFilePath))
    230.                     continue;
    231.                 try
    232.                 {
    233.                     genericSettingsProviders.Add(LoadFromFile(genericSettingsFilePath));
    234.                 }
    235.                 catch
    236.                 {
    237.                     Debug.LogError("Failed to parse settings file " + genericSettingsFilePath);
    238.                 }
    239.             }
    240.  
    241.             return genericSettingsProviders.ToArray();
    242.         }
    243.     }
    244. }
    245.  
     
    karl_jones and Peter77 like this.
  5. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,030
    I know that there is a overload where you can pass directly an Object.

    I mean that Unity stores all it's settings in ProjectSettings folder so I supposed that it's the standard that's why I asked how to store my serialized settings
     
  6. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,030
    Your example doesn't compile. This classes doesn't exists:
    - SettingsWindow
    - SearchUtils
    - SettingsTestsUtils
    - Json